基于GPUImage的自定义相机2.0版

前言

  • 在之前的文章里已经阐述过基于GPUImage实现自定义相机的1.0版本——基础功能版,我们先来简单回顾一下之前已经实现的功能:
    1.拍照
    2.实时滤镜
    3.准确聚焦
    4.调整焦距
    5.调整曝光
    6.闪光灯设置
    7.翻转前后相机
    8.拍照后的滤镜调整

好的,那现在就来看一下我们的2.0版本做了什么改进吧!

  • 1.封装Camera类,以方便大家的使用
    2.设计手势识别状态机,对不同的手势添加相应功能,并且实现流畅切换
    3.闪光灯状态由三种增加到四种,并且实现展开动画效果
    4.聚焦曝光分开控制,新增手动调整聚焦和曝光功能
    5.用KVO机制(Key-Value Observing)得到开始聚焦到聚焦结束的时间间隔

预览

IMG_2873.PNG
IMG_2874.PNG
IMG_2875.PNG
IMG_2876.PNG

(次奥。。。好大的图片!)

正文

  • 好了,看完图下面进入正题

Camera类的封装

  • Camera类的封装主要是一个封装的思想,我们把Camera的功能等都从原来的一个ViewController中抽离出来,并且抽象为一个StillCamera类,作为模型层。然后在控制层中实例化这个模型,再对其进行自己想要的设置及方法调用。封装的最关键一点是要搞清楚自己想要让别人调用什么接口,不想让别人看见或者调用什么接口。把这些做到合理了,封装也就做好了。
  • 下面给出封装后Camera的.h文件
#import "GPUImageStillCamera.h"


/**
 * 相机闪光灯模式
 */
typedef NS_ENUM(NSInteger, CameraManagerFlashMode) {
    
    CameraManagerFlashModeAuto,  /**< 自动模式 */
    
    CameraManagerFlashModeOff,  /**< 闪光灯关闭模式 */
    
    CameraManagerFlashModeOn,  /**< 闪光灯打开模式 */
    
    CameraManagerFlashModeOpen  /**< 闪光灯常亮模式 */
};


/**
 * 聚焦状态
 */
typedef NS_ENUM(NSInteger,TouchState){
    
    AutoFocusAndExpose,/**< 自动聚焦曝光状态 */
    
    ManualFocusAndExpose,/**< 手动聚焦曝光状态 */
    
    PartFocusAndExpose/**< 聚焦曝光分离状态 */
};


/**
 *  自定义相机类
 */
@interface MTStillCamera : GPUImageStillCamera

@property (strong ,nonatomic) UIView *preview;//预览视图
@property (strong ,nonatomic) UIView *cameraView;
@property (strong ,nonatomic) UIImageView *focusImageView;//聚焦ImageView
@property (strong ,nonatomic) UIImageView *exposeImageView;//曝光ImageView
@property (strong ,nonatomic) UIImageView *autoFocusImageView;//自动聚焦曝光ImageView

@property (nonatomic , copy)  UITapGestureRecognizer *singleTap;
@property (nonatomic , copy)  UITapGestureRecognizer *doubleTap;
@property (nonatomic , copy)  UIPanGestureRecognizer *panOfAutoImageView;
@property (nonatomic , copy)  UIPinchGestureRecognizer *pinch;
@property (nonatomic , copy)  UIPanGestureRecognizer *panOfPartFocusView;
@property (nonatomic , copy)  UIPanGestureRecognizer *panOfPartExposeView;

@property AVCaptureStillImageOutput *photoOutput;

@property (nonatomic , assign) TouchState touchState;
@property (nonatomic , assign) CameraManagerFlashMode flashMode;

/**
 *   初始化相机
 *   默认初始化相机为前置相机,前置摄像为镜像,后置非镜像 
 *   默认闪光灯为自动闪光模式
 *   默认聚焦状态为自动聚焦
 *   @param     cameraPosition  相机位置
 *
 *   @return  id 相机实例
 */
- (id)initWithCameraPosition:(AVCaptureDevicePosition) cameraPosition;

/**
 *   设置闪光灯模式功能
 *
 *   @param     flashMode  闪光灯模式
 *
 *   @return  无
 */
- (void)setFlashMode:(CameraManagerFlashMode)flashMode;


/**
 *   转置相机
 *
 *   @param   无
 *
 *   @return  无
 */
- (void) rotateCamera;

/**
 *   设置聚焦图片
 *
 *   @param    image  自动聚焦图片(包括曝光)
 *
 *   @return  无
 */
- (void)setAutoFocusImage:(UIImage *)image;

/**
 *   设置聚焦图片
 *
 *   @param    focusImage 聚焦图片  exposeImage 曝光图片
 *
 *   @return  无
 */
- (void)setFocusAndExposeImage:(UIImage *)focusImage and:(UIImage *)exposeImage;

/**
 *   调整焦距功能
 *
 *   @param    sliderValue  浮点值,通常为slider控件的value值
 *
 *   @return  无
 */
- (void)focusDisdanceWithSliderValue:(float)sliderValue;


/**
 *   调整曝光值功能
 *
 *   @param    sliderValue  浮点值,通常为slider控件的value值
 *
 *   @return  无
 */
- (void)exposeRateWithSliderValue:(float)sliderValue;

/**
 *   调整聚焦值功能
 *
 *   @param    sliderValue  浮点值,通常为slider控件的value值
 *
 *   @return  无
 */
- (void)focusRateWithSliderValue:(float)sliderValue;

/**
 *   添加所有默认手势(包括以下六种)
 *
 *   @param   无
 *
 *   @return  无
 */
- (void)addGesturesToCamera;

/**
 *   手势添加功能
 *
 *   @param   无
 *
 *   @return  无
 */
- (void)addSingleTapToPreview;//在preview上添加tap手势
- (void)addDoubleTapToPreview;//在preview上添加双击手势
- (void)addPinchGestureToPreview;//在preview上添加pinch手势

- (void)addPanGestureToAutoImageView;//在autoImageView上添加拖动手势
- (void)addPanGestureToFocusImageView;//在focusImageView上添加拖动手势
- (void)addPanGestureToExposeImageView;//在exposeImageView上添加拖动手势

@end

设计手势识别状态机,对不同的手势添加相应功能

  • 随着我们对手势的需求逐渐增多,设计一个手势识别状态机就越来越必要了。刚开始我们的手势只有tap和pinch两种,前者用来设置焦点,后者用来控制焦距。但是现在我们的需求是这样的:
    1.单击屏幕可进行焦点及曝光调整
    2.拖动聚焦框也可以进行调整
    2.通过pinch手势可将焦点设置及曝光设置分离
    3.分离后通过点按方式设置焦点,同时也可以通过拖动聚焦框设置焦点
    4.分离后通过拖动曝光框手势设置曝光
    5.通过双击屏幕回到聚焦曝光合并状态,同时焦点自动调整为屏幕中心点

为了理清逻辑,于是以下这张手势转换状态机图就诞生了

IMG_2814.JPG

如图所示,我们设置了三个状态,分别是自动聚焦曝光状态,聚焦曝光状态以及聚焦曝光分离状态。
首先我们要设置对应的图片,即先调用以下方法

/** 设置聚焦曝光图片 */
- (void)setAutoFocusImage:(UIImage *)image{
    if (!image) return;
    if (!_focusLayer) {
        _autoFocusImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
        _autoFocusImageView.image = image;
        CALayer *layer = _autoFocusImageView.layer;
        layer.hidden = YES;
        [self.preview.layer addSublayer:layer];
        _focusLayer = layer;
    }
}

/** 分别设置聚焦图片和曝光图片 */
- (void)setFocusAndExposeImage:(UIImage *)focusImage and:(UIImage *)exposeImage {
    _focusImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0,70.0f,70.0f)];
    _exposeImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0,80.0f,80.0f)];
    _focusImageView.image = focusImage;
    _exposeImageView.image = exposeImage;
    [self.preview addSubview:_exposeImageView];
    [self.preview addSubview:_focusImageView];
    [_exposeImageView setHidden:YES];
    [_focusImageView setHidden:YES];
}

在设置对应聚焦和曝光的图片后,我们就可以给他们增加手势啦。

手势的添加过程中,有时候会碰到手势冲突的问题,举个例子说就是你在用pinch手势的时候,由于系统先识别了pan手势,导致最终的结果变成了拖动效果而不是pinch产生的效果。解决这个问题的方法是调用 [pan requireGestureRecognizerToFail:pinch]方法,这样pan手势就会在pinch手势识别失败之后进行识别。

/** 给preview增加pinch手势 */
- (void)addPinchGestureToPreview{
    if (_preview) {
        _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(focusAndExpose:)];
        [self.preview addGestureRecognizer:_pinch];
        //pinch.delegate = self;
    }
    else{
        NSLog(@"Please init the preview first.");
    }
}

/** 给preview增加tap手势(单击)*/
- (void)addSingleTapToPreview{
    if (_preview) {
        _singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(focusFunction:)];
        [_singleTap setNumberOfTapsRequired:1];
        [self.preview addGestureRecognizer:_singleTap];
//        if (_doubleTap) {
//            [_singleTap requireGestureRecognizerToFail:_doubleTap];
//        }
    }
    else{
        NSLog(@"Please init the preview first.");
    }
}

/** 给preview增加tap手势(双击)*/
- (void)addDoubleTapToPreview{
    if (_preview) {
        _doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toFocus:)];
        [_doubleTap setNumberOfTapsRequired:2];
        [self.preview addGestureRecognizer:_doubleTap];
    }
    else{
        NSLog(@"Please init the preview first.");
    }
}

/** 给聚焦曝光图片设置pan手势 */
- (void)addPanGestureToAutoImageView{
    if (_autoFocusImageView) {
        _panOfAutoImageView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panAutoFocus:)];
        [_autoFocusImageView setUserInteractionEnabled:YES];
        [_autoFocusImageView addGestureRecognizer:_panOfAutoImageView];
        //pan.delegate = self;
    }
    else{
        NSLog(@"Please init the autoFocusImageView first.");
    }
}

/** 给聚焦图片增加pan手势 */
- (void)addPanGestureToFocusImageView{
    if (_focusImageView) {
        _panOfPartFocusView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panPartFocus:)];
        [_focusImageView addGestureRecognizer:_panOfPartFocusView];
        [_focusImageView setUserInteractionEnabled:YES];
        // pan1.delegate = self;
    }
    else{
        NSLog(@"Please init the focusImageView first.");
    }
}

/** 给曝光图片增加pan手势 */
- (void)addPanGestureToExposeImageView{
    if (_exposeImageView) {
        _panOfPartExposeView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panPartExpose:)];
        [_exposeImageView addGestureRecognizer:_panOfPartExposeView];
        [_exposeImageView setUserInteractionEnabled:YES];
        //pan2.delegate = self;
    }
    else{
        NSLog(@"Please init the exposeImageView first.");
    }
}

当然出于对懒逼的考虑,博主特地设置了一个全能方法😄

/** 一次给相机添加所有手势 */
- (void)addGesturesToCamera{
    if (_preview) {
        _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(focusAndExpose:)];
        [self.preview addGestureRecognizer:_pinch];
        //pinch.delegate = self;
        
        _singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(focusFunction:)];
        [_singleTap setNumberOfTapsRequired:1];
        [self.preview addGestureRecognizer:_singleTap];
        
        _doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toFocus:)];
        [_doubleTap setNumberOfTapsRequired:2];
        [self.preview addGestureRecognizer:_doubleTap];
        
//        if (_doubleTap) {
//            [_singleTap requireGestureRecognizerToFail:_doubleTap];
//        }
//        
//        if (_pinch) {
//            [_singleTap requireGestureRecognizerToFail:_pinch];
//        }
        
    }
    else{
        NSLog(@"Please init the preview first.");
    }
    
    if (_focusImageView) {
        _panOfPartFocusView = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panPartFocus:)];
        [_focusImageView addGestureRecognizer:_panOfPartFocusView];
        [_focusImageView setUserInteractionEnabled:YES];
        // pan1.delegate = self;
    }
    else{
        NSLog(@"Please init the focusImageView first.");
    }
    if (_exposeImageView) {
        _panOfPartExposeView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panPartExpose:)];
        [_exposeImageView addGestureRecognizer:_panOfPartExposeView];
        [_exposeImageView setUserInteractionEnabled:YES];
        //pan2.delegate = self;
    }
    else{
        NSLog(@"Please init the exposeImageView first.");
    }
    
    if (_autoFocusImageView) {
        _panOfAutoImageView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panAutoFocus:)];
        [_autoFocusImageView setUserInteractionEnabled:YES];
        [_autoFocusImageView addGestureRecognizer:_panOfAutoImageView];
        //pan.delegate = self;
    }
    else{
        NSLog(@"Please init the autoFocusImageView first.");
    }
}

之后就是对应手势调用的方法实现:
这里例举一个focus方法,其中block部分可以忽略过去,那是之前做回调聚焦时间的时候添加上去的代码,但是后来聚焦时间用KVO做了,然后这边的block回调就先放着,没有删掉。

在focus方法中,最主要的还是上一篇中提到的屏幕触点和聚焦点的映射问题。其他不难,调用系统接口即可。


- (void)focusFunction:(UITapGestureRecognizer *)tap{
    [self focus:tap complete:^(double intervalTime) {
        //NSLog(@"聚焦时间:%f",intervalTime);
    }];
}
- (void)focus:(UITapGestureRecognizer *)tap complete:(CompleteHandleBlock)completeHandlBlock {
    self.preview.userInteractionEnabled = NO;
    CGPoint touchPoint = [tap locationInView:tap.view];
    
    switch (self.touchState) {
        case AutoFocusAndExpose:
        case ManualFocusAndExpose:
            self.touchState = ManualFocusAndExpose;
            [self layerAnimationWithPoint:touchPoint];
            
            if(self.cameraPosition == AVCaptureDevicePositionBack){
                touchPoint = CGPointMake( touchPoint.y /tap.view.bounds.size.height ,1-touchPoint.x/tap.view.bounds.size.width);
            }
            else
                touchPoint = CGPointMake(touchPoint.y /tap.view.bounds.size.height ,touchPoint.x/tap.view.bounds.size.width);
            
            //将x、y坐标交换是为了解决照相机焦点坐标轴和屏幕坐标轴的映射问题
            if([self.inputCamera isExposurePointOfInterestSupported] && [self.inputCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
            {
                NSError *error;
                if ([self.inputCamera lockForConfiguration:&error]) {
                    
                    [self.inputCamera setExposurePointOfInterest:touchPoint];
                    [self.inputCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
                    if ([self.inputCamera isFocusPointOfInterestSupported] && [self.inputCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
                        [self.inputCamera setFocusPointOfInterest:touchPoint];
                        [self.inputCamera setFocusMode:AVCaptureFocusModeAutoFocus];
                    }
                    [self.inputCamera unlockForConfiguration];
                    
                    completeHandlBlock(_intervalTime);
                } else {
                    NSLog(@"ERROR = %@", error);
                }
            }
            break;
        case PartFocusAndExpose:
            self.touchState = PartFocusAndExpose;
            [_focusImageView setCenter:touchPoint];
            
            if(self.cameraPosition == AVCaptureDevicePositionBack){
                touchPoint = CGPointMake( touchPoint.y /tap.view.bounds.size.height ,1-touchPoint.x/tap.view.bounds.size.width);
            }
            else
                touchPoint = CGPointMake(touchPoint.y /tap.view.bounds.size.height ,touchPoint.x/tap.view.bounds.size.width);
            NSError *error;
            if ([self.inputCamera lockForConfiguration:&error]) {
                if ([self.inputCamera isFocusPointOfInterestSupported] && [self.inputCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
                    [self.inputCamera setFocusPointOfInterest:touchPoint];
                    [self.inputCamera setFocusMode:AVCaptureFocusModeAutoFocus];
                }
                [self.inputCamera unlockForConfiguration];
            }else{
                NSLog(@"ERROR = %@",error);
            }
            self.preview.userInteractionEnabled = YES;
            break;
    }
    
}

闪光灯状态由三种增加到四种,并且实现展开动画效果

  • 闪光灯的状态在1.0版本中是只有闪光灯开,闪光灯关,自动闪光灯状态的,现在我们新增一种状态--就是--打开手电筒🔦功能!!0.0

不多说,代码如下:

/** 设置闪光灯模式 */
- (void)setFlashMode:(CameraManagerFlashMode)flashMode {
    _flashMode = flashMode;
    
    switch (flashMode) {
        case CameraManagerFlashModeAuto: {
            [self.inputCamera lockForConfiguration:nil];
            if ([self.inputCamera isFlashModeSupported:AVCaptureFlashModeAuto]) {
                [self.inputCamera setFlashMode:AVCaptureFlashModeAuto];
                if (self.inputCamera.torchMode == AVCaptureTorchModeOn) {
                    [self.inputCamera setTorchMode:AVCaptureTorchModeOff];
                }
            }
            [self.inputCamera unlockForConfiguration];
        }
            break;
        case CameraManagerFlashModeOff: {
            [self.inputCamera lockForConfiguration:nil];
            if ([self.inputCamera isFlashModeSupported:AVCaptureFlashModeOff]) {
            [self.inputCamera setFlashMode:AVCaptureFlashModeOff];
            if (self.inputCamera.torchMode == AVCaptureTorchModeOn) {
                [self.inputCamera setTorchMode:AVCaptureTorchModeOff];
            }
            }
            [self.inputCamera unlockForConfiguration];
        }
            
            break;
        case CameraManagerFlashModeOn: {
            [self.inputCamera lockForConfiguration:nil];
            if ([self.inputCamera isFlashModeSupported:AVCaptureFlashModeOff]) {
                [self.inputCamera setFlashMode:AVCaptureFlashModeOn];
                if (self.inputCamera.torchMode == AVCaptureTorchModeOn) {
                    [self.inputCamera setTorchMode:AVCaptureTorchModeOff];
                }
  
            }
            [self.inputCamera unlockForConfiguration];
        }
            break;
        case CameraManagerFlashModeOpen:{
            [self.inputCamera lockForConfiguration:nil];
            if ([self.inputCamera isTorchModeSupported:AVCaptureTorchModeOn]) {
                [self.inputCamera setTorchMode:AVCaptureTorchModeOn];
            }
            if ([self.inputCamera isFlashModeSupported:AVCaptureFlashModeOff]) {
                [self.inputCamera setFlashMode:AVCaptureFlashModeOff];
            }
            [self.inputCamera unlockForConfiguration];
        }
        default:
            break;
    }
}

展开动画的原理其实很简单,做粗略点就是一个位移动画,做稍微花哨点就是位移+缩放动画。所有的复杂的动画都可以分解为简单动画的组合,也就是组动画。原理上动画无非就是简单动画、关键帧动画、过渡动画、组动画、粒子动画这几种,学会怎么分解怎么组合动画就可以做出自己想要的酷炫的效果。当然说起来容易,这东西还是得靠多多的实践才行。
以下是按钮展开方法的实现:

- (void)showButtons {
    if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonWillExpand:)]) {
        [self.delegate bubbleMenuButtonWillExpand:self];
    }
    
    [self _prepareForButtonExpansion];
    
    self.userInteractionEnabled = NO;
    
    [CATransaction begin];//首先开始动画的事务
    [CATransaction setAnimationDuration:_animationDuration];//设置动画的持续时间
    [CATransaction setCompletionBlock:^{
        for (UIButton *button in _buttonContainer) {
            button.transform = CGAffineTransformIdentity;
        }//设置回调
        
        if (self.delegate != nil) {
            if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonDidExpand:)]) {
                [self.delegate bubbleMenuButtonDidExpand:self];
            }
        }
        
        self.userInteractionEnabled = YES;
    }];
    //然后以下就是对动画展开方向的判断并设置按钮的起始坐标、终止坐标,动画开始时间,动画模式吧啦吧啦。
    NSArray *buttonContainer = _buttonContainer;
    
    if (self.direction == DirectionUp || self.direction == DirectionLeft) {
        buttonContainer = [self _reverseOrderFromArray:_buttonContainer];
    }
    
    for (int i = 0; i < buttonContainer.count; i++) {
        int index = (int)buttonContainer.count - (i + 1);
        
        UIButton *button = [buttonContainer objectAtIndex:index];
        button.hidden = NO;
        
        // position animation
        CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
        
        CGPoint originPosition = CGPointZero;
        CGPoint finalPosition = CGPointZero;
        
        switch (self.direction) {
            case DirectionLeft:
                originPosition = CGPointMake(self.frame.size.width - self.homeButtonView.frame.size.width, self.frame.size.height/2.f);
                finalPosition = CGPointMake(self.frame.size.width - self.homeButtonView.frame.size.width - button.frame.size.width/2.f - self.buttonSpacing
                                            - ((button.frame.size.width + self.buttonSpacing) * index),
                                            self.frame.size.height/2.f);
                break;
                
            case DirectionRight:
                originPosition = CGPointMake(self.homeButtonView.frame.size.width, self.frame.size.height/2.f);
                finalPosition = CGPointMake(self.homeButtonView.frame.size.width + self.buttonSpacing + button.frame.size.width/2.f
                                            + ((button.frame.size.width + self.buttonSpacing) * index),
                                            self.frame.size.height/2.f);
                break;
                
            case DirectionUp:
                originPosition = CGPointMake(self.frame.size.width/2.f, self.frame.size.height - self.homeButtonView.frame.size.height);
                finalPosition = CGPointMake(self.frame.size.width/2.f,
                                            self.frame.size.height - self.homeButtonView.frame.size.height - self.buttonSpacing - button.frame.size.height/2.f
                                            - ((button.frame.size.height + self.buttonSpacing) * index));
                break;
                
            case DirectionDown:
                originPosition = CGPointMake(self.frame.size.width/2.f, self.homeButtonView.frame.size.height);
                finalPosition = CGPointMake(self.frame.size.width/2.f,
                                            self.homeButtonView.frame.size.height + self.buttonSpacing + button.frame.size.height/2.f
                                            + ((button.frame.size.height + self.buttonSpacing) * index));
                break;
                
            default:
                break;
        }
        
        positionAnimation.duration = _animationDuration;
        positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        positionAnimation.fromValue = [NSValue valueWithCGPoint:originPosition];
        positionAnimation.toValue = [NSValue valueWithCGPoint:finalPosition];
        positionAnimation.beginTime = CACurrentMediaTime() + (_animationDuration/(float)_buttonContainer.count * (float)i);
        positionAnimation.fillMode = kCAFillModeForwards;
        positionAnimation.removedOnCompletion = NO;
        
        [button.layer addAnimation:positionAnimation forKey:@"positionAnimation"];
        
        button.layer.position = finalPosition;
        
        // scale animation
        CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        
        scaleAnimation.duration = _animationDuration;
        scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        scaleAnimation.fromValue = [NSNumber numberWithFloat:0.01f];
        scaleAnimation.toValue = [NSNumber numberWithFloat:1.f];
        scaleAnimation.beginTime = CACurrentMediaTime() + (_animationDuration/(float)_buttonContainer.count * (float)i) + 0.03f;
        scaleAnimation.fillMode = kCAFillModeForwards;
        scaleAnimation.removedOnCompletion = NO;
        
        [button.layer addAnimation:scaleAnimation forKey:@"scaleAnimation"];
        
        button.transform = CGAffineTransformMakeScale(0.01f, 0.01f);
    }
    
    [CATransaction commit];//提交动画,完成。
    
    _isCollapsed = NO;
}

聚焦曝光分开控制,新增手动调整聚焦和曝光功能

  • 关于聚焦和曝光的分开控制,之前已经提到过。就是在preview上增加一个pinch手势的识别,然后识别成功后在preview上出现两个图标,分别控制聚焦和曝光功能,对图标进行拖动也可以做出相应的自动调整。
/** 给preview增加pinch手势 */
- (void)addPinchGestureToPreview{
    if (_preview) {
        _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(focusAndExpose:)];
        [self.preview addGestureRecognizer:_pinch];
        //pinch.delegate = self;
    }
    else{
        NSLog(@"Please init the preview first.");
    }
}

/** 给聚焦图片增加pan手势 */
- (void)addPanGestureToFocusImageView{
    if (_focusImageView) {
        _panOfPartFocusView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panPartFocus:)];
        [_focusImageView addGestureRecognizer:_panOfPartFocusView];
        [_focusImageView setUserInteractionEnabled:YES];
        // pan1.delegate = self;
    }
    else{
        NSLog(@"Please init the focusImageView first.");
    }
}

/** 给曝光图片增加pan手势 */
- (void)addPanGestureToExposeImageView{
    if (_exposeImageView) {
        _panOfPartExposeView = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panPartExpose:)];
        [_exposeImageView addGestureRecognizer:_panOfPartExposeView];
        [_exposeImageView setUserInteractionEnabled:YES];
        //pan2.delegate = self;
    }
    else{
        NSLog(@"Please init the exposeImageView first.");
    }
}
/** 对焦与曝光分离 */
- (void)focusAndExpose:(UIPinchGestureRecognizer *)pinch {
    switch (self.touchState) {
        case AutoFocusAndExpose:
        case ManualFocusAndExpose:
        case PartFocusAndExpose:
            [self setTouchState:PartFocusAndExpose];
            self.preview.userInteractionEnabled = NO;
            _focusLayer.hidden = YES;
            int touchCount = pinch.numberOfTouches;
            if (touchCount == 2) {
                CGPoint point1 = [pinch locationOfTouch:0 inView:pinch.view];
                CGPoint point2 = [pinch locationOfTouch:1 inView:pinch.view];
                [_exposeImageView setHidden:NO];
                [_focusImageView setHidden:NO];
                [_exposeImageView setCenter:point2];
                [_focusImageView setCenter:point1];
            }
            self.preview.userInteractionEnabled = YES;
            break;
        default:
            break;
    }
}

/** 拖动分离对焦框 */
- (void)panPartFocus:(UIPanGestureRecognizer *)pan {
    CGPoint touchPoint;
    self.focusImageView.userInteractionEnabled = NO;
    if (pan.state != UIGestureRecognizerStateFailed) {
        CGPoint translation=[pan translationInView:self.cameraView];
        float x = _focusImageView.center.x + translation.x;
        float y = _focusImageView.center.y + translation.y;
        if (x < 0) {
            x = 0;
        }
        if (x > self.preview.frame.size.width) {
            x = self.preview.frame.size.width;
        }
        if (y < 0) {
            y = 0;
        }
        if (y > self.preview.frame.size.height) {
            y = self.preview.frame.size.height;
        }
        touchPoint = CGPointMake(x,y);
        _focusImageView.center = touchPoint;
        [pan setTranslation:CGPointZero inView:self.cameraView];
    }
    
    if(pan.state == UIGestureRecognizerStateEnded)
    {
        if(self.cameraPosition == AVCaptureDevicePositionBack){
            touchPoint = CGPointMake( touchPoint.y /_preview.bounds.size.height ,1-touchPoint.x/_preview.bounds.size.width);
        }
        else
            touchPoint = CGPointMake(touchPoint.y /_preview.bounds.size.height ,touchPoint.x/_preview.bounds.size.width);
        //将x、y坐标交换是为了解决照相机焦点坐标轴和屏幕坐标轴的映射问题
        
        NSError *error;
        if ([self.inputCamera lockForConfiguration:&error]) {
            if ([self.inputCamera isFocusPointOfInterestSupported] && [self.inputCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
                [self.inputCamera setFocusPointOfInterest:touchPoint];
                [self.inputCamera setFocusMode:AVCaptureFocusModeAutoFocus];
            }
            [self.inputCamera unlockForConfiguration];
        }else{
            NSLog(@"ERROR = %@",error);
        }
        
    }
    self.focusImageView.userInteractionEnabled = YES;
}

/** 拖动分离曝光框 */
- (void)panPartExpose:(UIPanGestureRecognizer *)pan {
    CGPoint touchPoint;
    self.exposeImageView.userInteractionEnabled = NO;
    if (pan.state != UIGestureRecognizerStateFailed) {
        CGPoint translation=[pan translationInView:self.cameraView];
        NSLog(@"x = %f,y = %f",translation.x,translation.y);
        float x = _exposeImageView.center.x + translation.x;
        float y = _exposeImageView.center.y + translation.y;
        if (x < 0) {
            x = 0;
        }
        if (x > self.preview.frame.size.width) {
            x = self.preview.frame.size.width;
        }
        if (y < 0) {
            y = 0;
        }
        if (y > self.preview.frame.size.height) {
            y = self.preview.frame.size.height;
        }
        touchPoint = CGPointMake(x,y);
        _exposeImageView.center = touchPoint;
        [pan setTranslation:CGPointZero inView:self.cameraView];
    }
    
    if(pan.state == UIGestureRecognizerStateEnded)
    {
        if(self.cameraPosition == AVCaptureDevicePositionBack){
            touchPoint = CGPointMake( touchPoint.y /_preview.bounds.size.height ,1-touchPoint.x/_preview.bounds.size.width);
        }
        else
            touchPoint = CGPointMake(touchPoint.y /_preview.bounds.size.height ,touchPoint.x/_preview.bounds.size.width);
        
        //将x、y坐标交换是为了解决照相机焦点坐标轴和屏幕坐标轴的映射问题
        
        if([self.inputCamera isExposurePointOfInterestSupported] && [self.inputCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
        {
            NSError *error;
            if ([self.inputCamera lockForConfiguration:&error]) {
                [self.inputCamera setExposurePointOfInterest:touchPoint];
                [self.inputCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
                [self.inputCamera unlockForConfiguration];
            } else {
                NSLog(@"ERROR = %@", error);
            }
        }
        
    }
    self.exposeImageView.userInteractionEnabled = YES;
}
  • 接下来是手动调整聚焦以及曝光功能的实现。也就是AF和AE功能。

  • 在很多比较专业的相机App中,AF以及AWB、ISO、曝光时间设置的功能都是需要收费的。大家可以看一下Camera+和ProCam等软件,Camera+免费版中这些功能是锁定掉的,要想解锁就得付费。ProCam就更不用说了,没有免费版,要想用也行,交30软妹币,或者自己从别的途径解决😊

  • 其实简单的AF、AWB、ISO等功能的实现也是很简单的,同样只是调用接口而已,但是要做出很好的用户体验和效果来就比较难了。目前这边只是做了AF、AE的调整,其他功能之后再实现

  • 首先是AF
    MTStiilCamera对系统调整AF的接口进行了一个封装,对外提供- (void)focusRateWithSliderValue:(float)sliderValue;接口,大家需要调用这个接口的时候只需要提供一个float值sliderValue即可,sliderValue 值一般可以是slider控件的value 值,当然也可以是其他,不做要求,自己设定。

/** 调整聚焦值 */
- (void)focusRateWithSliderValue:(float)sliderValue{
    NSError *error;
    if ([self.inputCamera lockForConfiguration:&error]) {
        if([self.inputCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]){
            [self.inputCamera setFocusModeLockedWithLensPosition:sliderValue completionHandler:^(CMTime syncTime) {
                NSLog(@"手动聚焦时间戳:");
                CMTimeShow(syncTime);//所施加的透镜位置获取第一个图像缓存的时间戳
            }];
        }
        [self.inputCamera unlockForConfiguration];
    } else {
        NSLog(@"ERROR = %@", error);
    }
    
}

AE提供接口也是一样的格式,如下

#pragma mark 调整曝光值
/** 调整曝光值 */
- (void)exposeRateWithSliderValue:(float)sliderValue{
    NSError *error;
    if ([self.inputCamera lockForConfiguration:&error]) {
        [self.inputCamera setExposureTargetBias:self.minExposureRate * sliderValue completionHandler:^(CMTime syncTime) {
            NSLog(@"手动曝光时间戳:");
            CMTimeShow(syncTime);
        }];
        
//        [self.inputCamera setFocusModeLockedWithLensPosition:sliderValue completionHandler:^(CMTime syncTime) {
//            CMTimeShow(syncTime);
//        }];
        [self.inputCamera unlockForConfiguration];
    } else {
        NSLog(@"ERROR = %@", error);
    }
    
}

用KVO机制(Key-Value Observing)得到开始聚焦到聚焦结束的时间间隔

  • 假设提出这样一个需求,我们需要得到用户点击屏幕进行聚焦,到相机聚焦完成之间的时间段,怎么做?

GPUImageStillCamera是基于AVCaptureDevice做的,那我当然可以通过监听adjustingFocus的值来检测到相机是否正在聚焦。当相机镜片开始移动时,adjustingFocus的值就是YES,当聚焦成功后值就变成NO。然后在开始聚焦的时候获取系统时间,在聚焦结束后再次获取系统时间,相减,就是我们想要的结果了。

 AVCaptureDevice *camDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    int flags = NSKeyValueObservingOptionNew;
    [camDevice addObserver:self forKeyPath:@"adjustingFocus" options:flags context:nil];


-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    if([keyPath isEqualToString:@"adjustingFocus"]){
        BOOL adjustingFocus =[[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1]];
        NSLog(@"Is adjusting focus? %@", adjustingFocus ?@"YES":@"NO");
        NSLog(@"Change dictionary: %@", change);
        if (adjustingFocus) {
            NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
            NSTimeInterval a=[dat timeIntervalSince1970];
            NSString *timeString = [NSString stringWithFormat:@"%f", a];
            _startTime = [timeString doubleValue];
        }
        else{
            NSDate* dat1 = [NSDate dateWithTimeIntervalSinceNow:0];
            NSTimeInterval b=[dat1 timeIntervalSince1970];
            NSString *timeString2 = [NSString stringWithFormat:@"%f", b];
            _endTime = [timeString2 doubleValue];
            _intervalTime = _endTime - _startTime;
            
            NSLog(@"聚焦时间为%f",_intervalTime);
        }
    }
}

如此。即可。

结语

  • 本期对GPUImage实现自定义相机的介绍就先到这里啦,有不对或者可以改进的地方欢迎大家提出来,共同学习。
  • 后面可能会做一些类似白平衡、ISO调整等功能以及用户体验更好的聚焦效果 大家3.0版本再见👋👋
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容