基于GPUImage的自定義相機2.0版

前言

  • 在之前的文章里已經(jīng)闡述過基于GPUImage實現(xiàn)自定義相機的1.0版本——基礎(chǔ)功能版夷陋,我們先來簡單回顧一下之前已經(jīng)實現(xiàn)的功能:
    1.拍照
    2.實時濾鏡
    3.準(zhǔn)確聚焦
    4.調(diào)整焦距
    5.調(diào)整曝光
    6.閃光燈設(shè)置
    7.翻轉(zhuǎn)前后相機
    8.拍照后的濾鏡調(diào)整

好的爹谭,那現(xiàn)在就來看一下我們的2.0版本做了什么改進吧!

  • 1.封裝Camera類,以方便大家的使用
    2.設(shè)計手勢識別狀態(tài)機,對不同的手勢添加相應(yīng)功能棍苹,并且實現(xiàn)流暢切換
    3.閃光燈狀態(tài)由三種增加到四種,并且實現(xiàn)展開動畫效果
    4.聚焦曝光分開控制茵汰,新增手動調(diào)整聚焦和曝光功能
    5.用KVO機制(Key-Value Observing)得到開始聚焦到聚焦結(jié)束的時間間隔

預(yù)覽

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

(次奧枢里。。蹂午。好大的圖片@覆颉)

正文

  • 好了,看完圖下面進入正題

Camera類的封裝

  • Camera類的封裝主要是一個封裝的思想豆胸,我們把Camera的功能等都從原來的一個ViewController中抽離出來奥洼,并且抽象為一個StillCamera類,作為模型層晚胡。然后在控制層中實例化這個模型灵奖,再對其進行自己想要的設(shè)置及方法調(diào)用嚼沿。封裝的最關(guān)鍵一點是要搞清楚自己想要讓別人調(diào)用什么接口,不想讓別人看見或者調(diào)用什么接口瓷患。把這些做到合理了骡尽,封裝也就做好了。
  • 下面給出封裝后Camera的.h文件
#import "GPUImageStillCamera.h"


/**
 * 相機閃光燈模式
 */
typedef NS_ENUM(NSInteger, CameraManagerFlashMode) {
    
    CameraManagerFlashModeAuto,  /**< 自動模式 */
    
    CameraManagerFlashModeOff,  /**< 閃光燈關(guān)閉模式 */
    
    CameraManagerFlashModeOn,  /**< 閃光燈打開模式 */
    
    CameraManagerFlashModeOpen  /**< 閃光燈常亮模式 */
};


/**
 * 聚焦?fàn)顟B(tài)
 */
typedef NS_ENUM(NSInteger,TouchState){
    
    AutoFocusAndExpose,/**< 自動聚焦曝光狀態(tài) */
    
    ManualFocusAndExpose,/**< 手動聚焦曝光狀態(tài) */
    
    PartFocusAndExpose/**< 聚焦曝光分離狀態(tài) */
};


/**
 *  自定義相機類
 */
@interface MTStillCamera : GPUImageStillCamera

@property (strong ,nonatomic) UIView *preview;//預(yù)覽視圖
@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;

/**
 *   初始化相機
 *   默認(rèn)初始化相機為前置相機擅编,前置攝像為鏡像攀细,后置非鏡像 
 *   默認(rèn)閃光燈為自動閃光模式
 *   默認(rèn)聚焦?fàn)顟B(tài)為自動聚焦
 *   @param     cameraPosition  相機位置
 *
 *   @return  id 相機實例
 */
- (id)initWithCameraPosition:(AVCaptureDevicePosition) cameraPosition;

/**
 *   設(shè)置閃光燈模式功能
 *
 *   @param     flashMode  閃光燈模式
 *
 *   @return  無
 */
- (void)setFlashMode:(CameraManagerFlashMode)flashMode;


/**
 *   轉(zhuǎn)置相機
 *
 *   @param   無
 *
 *   @return  無
 */
- (void) rotateCamera;

/**
 *   設(shè)置聚焦圖片
 *
 *   @param    image  自動聚焦圖片(包括曝光)
 *
 *   @return  無
 */
- (void)setAutoFocusImage:(UIImage *)image;

/**
 *   設(shè)置聚焦圖片
 *
 *   @param    focusImage 聚焦圖片  exposeImage 曝光圖片
 *
 *   @return  無
 */
- (void)setFocusAndExposeImage:(UIImage *)focusImage and:(UIImage *)exposeImage;

/**
 *   調(diào)整焦距功能
 *
 *   @param    sliderValue  浮點值,通常為slider控件的value值
 *
 *   @return  無
 */
- (void)focusDisdanceWithSliderValue:(float)sliderValue;


/**
 *   調(diào)整曝光值功能
 *
 *   @param    sliderValue  浮點值爱态,通常為slider控件的value值
 *
 *   @return  無
 */
- (void)exposeRateWithSliderValue:(float)sliderValue;

/**
 *   調(diào)整聚焦值功能
 *
 *   @param    sliderValue  浮點值谭贪,通常為slider控件的value值
 *
 *   @return  無
 */
- (void)focusRateWithSliderValue:(float)sliderValue;

/**
 *   添加所有默認(rèn)手勢(包括以下六種)
 *
 *   @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

設(shè)計手勢識別狀態(tài)機,對不同的手勢添加相應(yīng)功能

  • 隨著我們對手勢的需求逐漸增多锦担,設(shè)計一個手勢識別狀態(tài)機就越來越必要了俭识。剛開始我們的手勢只有tap和pinch兩種,前者用來設(shè)置焦點吆豹,后者用來控制焦距鱼的。但是現(xiàn)在我們的需求是這樣的:
    1.單擊屏幕可進行焦點及曝光調(diào)整
    2.拖動聚焦框也可以進行調(diào)整
    2.通過pinch手勢可將焦點設(shè)置及曝光設(shè)置分離
    3.分離后通過點按方式設(shè)置焦點,同時也可以通過拖動聚焦框設(shè)置焦點
    4.分離后通過拖動曝光框手勢設(shè)置曝光
    5.通過雙擊屏幕回到聚焦曝光合并狀態(tài)痘煤,同時焦點自動調(diào)整為屏幕中心點

為了理清邏輯凑阶,于是以下這張手勢轉(zhuǎn)換狀態(tài)機圖就誕生了

IMG_2814.JPG

如圖所示,我們設(shè)置了三個狀態(tài)衷快,分別是自動聚焦曝光狀態(tài)宙橱,聚焦曝光狀態(tài)以及聚焦曝光分離狀態(tài)。
首先我們要設(shè)置對應(yīng)的圖片,即先調(diào)用以下方法

/** 設(shè)置聚焦曝光圖片 */
- (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;
    }
}

/** 分別設(shè)置聚焦圖片和曝光圖片 */
- (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];
}

在設(shè)置對應(yīng)聚焦和曝光的圖片后蘸拔,我們就可以給他們增加手勢啦师郑。

手勢的添加過程中,有時候會碰到手勢沖突的問題调窍,舉個例子說就是你在用pinch手勢的時候宝冕,由于系統(tǒng)先識別了pan手勢,導(dǎo)致最終的結(jié)果變成了拖動效果而不是pinch產(chǎn)生的效果邓萨。解決這個問題的方法是調(diào)用 [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.");
    }
}

/** 給聚焦曝光圖片設(shè)置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.");
    }
}

當(dāng)然出于對懶逼的考慮缔恳,博主特地設(shè)置了一個全能方法??

/** 一次給相機添加所有手勢 */
- (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.");
    }
}

之后就是對應(yīng)手勢調(diào)用的方法實現(xiàn):
這里例舉一個focus方法,其中block部分可以忽略過去宝剖,那是之前做回調(diào)聚焦時間的時候添加上去的代碼,但是后來聚焦時間用KVO做了歉甚,然后這邊的block回調(diào)就先放著万细,沒有刪掉。

在focus方法中纸泄,最主要的還是上一篇中提到的屏幕觸點和聚焦點的映射問題赖钞。其他不難腰素,調(diào)用系統(tǒng)接口即可。


- (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坐標(biāo)交換是為了解決照相機焦點坐標(biāo)軸和屏幕坐標(biāo)軸的映射問題
            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;
    }
    
}

閃光燈狀態(tài)由三種增加到四種耸弄,并且實現(xiàn)展開動畫效果

  • 閃光燈的狀態(tài)在1.0版本中是只有閃光燈開,閃光燈關(guān)卓缰,自動閃光燈狀態(tài)的计呈,現(xiàn)在我們新增一種狀態(tài)--就是--打開手電筒??功能!征唬!0.0

不多說捌显,代碼如下:

/** 設(shè)置閃光燈模式 */
- (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;
    }
}

展開動畫的原理其實很簡單,做粗略點就是一個位移動畫总寒,做稍微花哨點就是位移+縮放動畫扶歪。所有的復(fù)雜的動畫都可以分解為簡單動畫的組合,也就是組動畫摄闸。原理上動畫無非就是簡單動畫善镰、關(guān)鍵幀動畫、過渡動畫年枕、組動畫炫欺、粒子動畫這幾種,學(xué)會怎么分解怎么組合動畫就可以做出自己想要的酷炫的效果熏兄。當(dāng)然說起來容易品洛,這東西還是得靠多多的實踐才行。
以下是按鈕展開方法的實現(xiàn):

- (void)showButtons {
    if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonWillExpand:)]) {
        [self.delegate bubbleMenuButtonWillExpand:self];
    }
    
    [self _prepareForButtonExpansion];
    
    self.userInteractionEnabled = NO;
    
    [CATransaction begin];//首先開始動畫的事務(wù)
    [CATransaction setAnimationDuration:_animationDuration];//設(shè)置動畫的持續(xù)時間
    [CATransaction setCompletionBlock:^{
        for (UIButton *button in _buttonContainer) {
            button.transform = CGAffineTransformIdentity;
        }//設(shè)置回調(diào)
        
        if (self.delegate != nil) {
            if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonDidExpand:)]) {
                [self.delegate bubbleMenuButtonDidExpand:self];
            }
        }
        
        self.userInteractionEnabled = YES;
    }];
    //然后以下就是對動畫展開方向的判斷并設(shè)置按鈕的起始坐標(biāo)摩桶、終止坐標(biāo)桥状,動畫開始時間,動畫模式吧啦吧啦硝清。
    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;
}

聚焦曝光分開控制芦拿,新增手動調(diào)整聚焦和曝光功能

  • 關(guān)于聚焦和曝光的分開控制砾肺,之前已經(jīng)提到過。就是在preview上增加一個pinch手勢的識別防嗡,然后識別成功后在preview上出現(xiàn)兩個圖標(biāo),分別控制聚焦和曝光功能侠坎,對圖標(biāo)進行拖動也可以做出相應(yīng)的自動調(diào)整蚁趁。
/** 給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坐標(biāo)交換是為了解決照相機焦點坐標(biāo)軸和屏幕坐標(biāo)軸的映射問題
        
        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坐標(biāo)交換是為了解決照相機焦點坐標(biāo)軸和屏幕坐標(biāo)軸的映射問題
        
        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;
}
  • 接下來是手動調(diào)整聚焦以及曝光功能的實現(xiàn)他嫡。也就是AF和AE功能番官。

  • 在很多比較專業(yè)的相機App中,AF以及AWB钢属、ISO徘熔、曝光時間設(shè)置的功能都是需要收費的。大家可以看一下Camera+和ProCam等軟件淆党,Camera+免費版中這些功能是鎖定掉的酷师,要想解鎖就得付費。ProCam就更不用說了染乌,沒有免費版山孔,要想用也行,交30軟妹幣荷憋,或者自己從別的途徑解決??

  • 其實簡單的AF台颠、AWB、ISO等功能的實現(xiàn)也是很簡單的勒庄,同樣只是調(diào)用接口而已串前,但是要做出很好的用戶體驗和效果來就比較難了。目前這邊只是做了AF实蔽、AE的調(diào)整荡碾,其他功能之后再實現(xiàn)

  • 首先是AF
    MTStiilCamera對系統(tǒng)調(diào)整AF的接口進行了一個封裝,對外提供- (void)focusRateWithSliderValue:(float)sliderValue盐须;接口玩荠,大家需要調(diào)用這個接口的時候只需要提供一個float值sliderValue即可,sliderValue 值一般可以是slider控件的value 值贼邓,當(dāng)然也可以是其他阶冈,不做要求,自己設(shè)定塑径。

/** 調(diào)整聚焦值 */
- (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 調(diào)整曝光值
/** 調(diào)整曝光值 */
- (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)得到開始聚焦到聚焦結(jié)束的時間間隔

  • 假設(shè)提出這樣一個需求,我們需要得到用戶點擊屏幕進行聚焦统舀,到相機聚焦完成之間的時間段匆骗,怎么做?

GPUImageStillCamera是基于AVCaptureDevice做的誉简,那我當(dāng)然可以通過監(jiān)聽adjustingFocus的值來檢測到相機是否正在聚焦碉就。當(dāng)相機鏡片開始移動時,adjustingFocus的值就是YES闷串,當(dāng)聚焦成功后值就變成NO瓮钥。然后在開始聚焦的時候獲取系統(tǒng)時間,在聚焦結(jié)束后再次獲取系統(tǒng)時間,相減碉熄,就是我們想要的結(jié)果了桨武。

 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);
        }
    }
}

如此。即可锈津。

結(jié)語

  • 本期對GPUImage實現(xiàn)自定義相機的介紹就先到這里啦呀酸,有不對或者可以改進的地方歡迎大家提出來,共同學(xué)習(xí)琼梆。
  • 后面可能會做一些類似白平衡性誉、ISO調(diào)整等功能以及用戶體驗更好的聚焦效果 大家3.0版本再見????
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叮叹,隨后出現(xiàn)的幾起案子艾栋,更是在濱河造成了極大的恐慌,老刑警劉巖蛉顽,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝗砾,死亡現(xiàn)場離奇詭異,居然都是意外死亡携冤,警方通過查閱死者的電腦和手機悼粮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曾棕,“玉大人扣猫,你說我怎么就攤上這事∏痰兀” “怎么了申尤?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵都办,是天一觀的道長抒痒。 經(jīng)常有香客問我吃既,道長桶错,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任雁佳,我火速辦了婚禮言秸,結(jié)果婚禮上曹体,老公的妹妹穿的比我還像新娘厅瞎。我一直安慰自己饰潜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布和簸。 她就那樣靜靜地躺著彭雾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锁保。 梳的紋絲不亂的頭發(fā)上冠跷,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天南誊,我揣著相機與錄音,去河邊找鬼蜜托。 笑死,一個胖子當(dāng)著我的面吹牛霉赡,可吹牛的內(nèi)容都是我干的橄务。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼穴亏,長吁一口氣:“原來是場噩夢啊……” “哼蜂挪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗓化,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棠涮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刺覆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體严肪,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年谦屑,在試婚紗的時候發(fā)現(xiàn)自己被綠了驳糯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡氢橙,死狀恐怖酝枢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悍手,我是刑警寧澤帘睦,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站坦康,受9級特大地震影響竣付,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涝焙,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一卑笨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仑撞,春花似錦赤兴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沮翔,卻和暖如春陨帆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工疲牵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留承二,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓纲爸,卻偏偏與公主長得像亥鸠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子识啦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容