iOS 实现八倍镜

800 阅读2分钟

废话开篇:利用 openCV 简单实现一下八倍镜效果

一、实现效果

场景图片

image.png

实现效果

屏幕录制2022-01-21 上午8.52.44.gif

二、步骤分析

1、将 场景图 转为矩阵

2、重写 UIPanGestureRecognizer,解决 UIGestureRecognizerStateBegan 状态不响应问题。

3、获取手势拖动过程中的点,进行半径为 100 圆内的全部像素点。

4、重新绘制,生成指定半径圆内 UIImage 图像,替换 UIImageViewimage

三、外部调用 WSLPaintFlow 八倍镜类

    //场景图片
    NSString * bundleImage = [[NSBundle mainBundle] pathForResource:@"sence1" ofType:@"jpeg"];
    UIImage * image = [UIImage imageWithContentsOfFile:bundleImage];
    UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 300, self.view.frame.size.width, self.view.frame.size.width * image.size.height  / image.size.width)];
    imageView.image = image;
    [self.view addSubview:imageView];
    self.paintFlow = [[WSLPaintFlow alloc] init];
    [self.paintFlow amplificationImageView:imageView];

四、重写 UIPanGestureRecognizer

这里重写 WSLPanGestureRecognizer 类,继承自 UIPanGestureRecognizer

因为 UIPanGestureRecognizer 拖拽手势的状态无法捕获点击开始下 UIGestureRecognizerStateBegan 状态,因此简单重写一下。

1、WSLPanGestureRecognizer.m
#import "WSLPanGestureRecognizer.h"

@implementation WSLPanGestureRecognizer

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [super touchesBegan:touches withEvent:event];
    self.state = UIGestureRecognizerStateBegan;
}

@end

五、WSLPaintFlow 类实现

1、WSLPaintFlow.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
@interface WSLPaintFlow : NSObject

//放大动作
- (void)amplificationImageView:(UIImageView *)imageView;

@end

NS_ASSUME_NONNULL_END
2、WSLPaintFlow.m
#ifdef __cplusplus

#import <opencv2/opencv.hpp>

#import <opencv2/imgcodecs/ios.h> // Mat 和 UIImage互转

#endif

//命名空间
using namespace cv;

#import "WSLPaintFlow.h"
#import "WSLPanGestureRecognizer.h"

@interface WSLPaintFlow()
{
    Mat _originalImage;//场景原图
    int _r;//八倍镜观察范围
}

//原图Image
@property (nonatomic,strong) UIImage * originalUIImage;
//原图ImageView
@property (nonatomic,weak) UIImageView * imageView;

@end

@implementation WSLPaintFlow

- (oid)amplificationImageView:(UIImageView *)imageView
{
    self.imageView = imageView;
    if (self.imageView.image) {
        //保存原图Image转换后的矩阵信息
        self.originalUIImage = imageView.image;
        UIImageToMat(self.originalUIImage, _originalImage);
    }

    self.imageView.userInteractionEnabled = YES;
    self.imageView.backgroundColor = [UIColor blackColor];
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    
    //添加手势
    WSLPanGestureRecognizer * pan = [[WSLPanGestureRecognizer alloc] initWithTarget:self action: @selector(panAction:)];
    [self.imageView addGestureRecognizer:pan];
}

- (void)panAction:(WSLPanGestureRecognizer *)sender
{
    switch (sender.state) {
        case UIGestureRecognizerStateBegan:
        {
            //手势开始点击,进行替换渲染
            CGPoint currentPoint = [sender locationInView:sender.view];
            self.imageView.image = [self amplificationImageWithPoint:currentPoint];
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            //手势移动中,进行替换渲染
            CGPoint currentPoint = [sender locationInView:sender.view];
            self.imageView.image = [self amplificationImageWithPoint:currentPoint];
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            //手势抬起,恢复原图
            self.imageView.image = self.originalUIImage;
        }
            break;
        default:
            break;
    }
}

//放大镜
- (UIImage *)amplificationImageWithPoint:(CGPoint)point
{
    UIImage * resultImage;
    if (self.originalUIImage) {
        //按比例获取手势触点对应的图片像素点
        int touchX = _originalImage.cols * (point.x / self.imageView.frame.size.width);
        int touchY = _originalImage.rows * (point.y / self.imageView.frame.size.height);
        
        //原图转换为RGB
        Mat originalShowImg(cvRound(_originalImage.rows), cvRound(_originalImage.cols), CV_8UC1 );
        cvtColor(_originalImage, originalShowImg, COLOR_BGR2RGB);
        
        //创建八倍镜小图矩阵,默认黑色背景
        Mat circleImg = Mat(cvRound(_r * 2), cvRound(_r * 2), CV_8UC3,CV_RGB(0,0,0));
        //八倍镜小图转换为RGB
        Mat originalCircleImg = Mat(cvRound(_r), cvRound(_r), CV_8UC1);
        cvtColor(circleImg, originalCircleImg, COLOR_BGR2RGB);
        
        //进行像素采集
        int r = _r
        int rectMinX = touchX - r;
        rectMinX = rectMinX < 0 ? 0 : rectMinX;

        int rectMinY = touchY - r;
        rectMinY = rectMinY < 0 ? 0 : rectMinY;

        int rectMaxX = touchX + r;
        rectMaxX = rectMaxX > _originalImage.cols ? _originalImage.cols : rectMaxX;

        int rectMaxY = touchY + r;
        rectMaxY = rectMaxY > _originalImage.rows ? _originalImage.rows : rectMaxY;

        for (int x = rectMinX; x < rectMaxX; x++) {
            for (int y = rectMinY; y < rectMaxY; y++) {

                int distanceX = fabs(x - touchX);

                int distanceY = fabs(y - touchY);

                //获取当前点距离圆心直线距离,判断像素是否在规定圆的范围内
                int distance = hypot(distanceX, distanceY);
                if (distance < r) {
                    //在圆内
                    int b = originalShowImg.at<Vec3b>(y,x)[0];
                    int g = originalShowImg.at<Vec3b>(y,x)[1];
                    int r = originalShowImg.at<Vec3b>(y,x)[2];
                    
                    //原图上的像素坐标转换为小圆的像素坐标
                    int circleImgX = x - rectMinX;
                    int circleImgY = y - rectMinY;
                                       
                    if (circleImgX > _r - 2 && circleImgX < _r + 2 && circleImgY > _r - 2 && circleImgY < _r + 2) {
                            //绘制准心
                            originalCircleImg.at<Vec3b>(circleImgY,circleImgX)[0] = 0;
                            originalCircleImg.at<Vec3b>(circleImgY,circleImgX)[1] = 0;
                            originalCircleImg.at<Vec3b>(circleImgY,circleImgX)[2] = 255;
                    } else {    
                            //绘制原图
                            originalCircleImg.at<Vec3b>(circleImgY,circleImgX)[0] = b;
                            originalCircleImg.at<Vec3b>(circleImgY,circleImgX)[1] = g;
                            originalCircleImg.at<Vec3b>(circleImgY,circleImgX)[2] = r;
                    }
                }
            }
        }
        
        //转换为RGB
        Mat resultShowImg(cvRound(originalCircleImg.rows), cvRound(originalCircleImg.cols), CV_8UC1 );
        cvtColor(originalCircleImg, resultShowImg, cv::COLOR_BGR2RGB, 3);
        resultImage = MatToUIImage(resultShowImg);
    }
    return resultImage;

}

@end

六、总结与思考

简单的八倍镜效果就完成了,个人总结随笔,大神勿笑[抱拳][抱拳][抱拳]