前言
- 在之前的文章里已經闡述過基于GPUImage實現(xiàn)自定義相機的1.0版本——基礎功能版,我們先來簡單回顧一下之前已經實現(xiàn)的功能:
1.拍照
2.實時濾鏡
3.準確聚焦
4.調整焦距
5.調整曝光
6.閃光燈設置
7.翻轉前后相機
8.拍照后的濾鏡調整
好的,那現(xiàn)在就來看一下我們的2.0版本做了什么改進吧!
- 1.封裝Camera類,以方便大家的使用
2.設計手勢識別狀態(tài)機,對不同的手勢添加相應功能,并且實現(xiàn)流暢切換
3.閃光燈狀態(tài)由三種增加到四種,并且實現(xiàn)展開動畫效果
4.聚焦曝光分開控制,新增手動調整聚焦和曝光功能
5.用KVO機制(Key-Value Observing)得到開始聚焦到聚焦結束的時間間隔
預覽
(次奧。。。好大的圖片?。?/p>
正文
- 好了,看完圖下面進入正題
Camera類的封裝
- Camera類的封裝主要是一個封裝的思想,我們把Camera的功能等都從原來的一個ViewController中抽離出來,并且抽象為一個StillCamera類,作為模型層。然后在控制層中實例化這個模型,再對其進行自己想要的設置及方法調用。封裝的最關鍵一點是要搞清楚自己想要讓別人調用什么接口,不想讓別人看見或者調用什么接口。把這些做到合理了,封裝也就做好了。
- 下面給出封裝后Camera的.h文件
#import "GPUImageStillCamera.h"
/**
* 相機閃光燈模式
*/
typedef NS_ENUM(NSInteger, CameraManagerFlashMode) {
CameraManagerFlashModeAuto, /**< 自動模式 */
CameraManagerFlashModeOff, /**< 閃光燈關閉模式 */
CameraManagerFlashModeOn, /**< 閃光燈打開模式 */
CameraManagerFlashModeOpen /**< 閃光燈常亮模式 */
};
/**
* 聚焦狀態(tài)
*/
typedef NS_ENUM(NSInteger,TouchState){
AutoFocusAndExpose,/**< 自動聚焦曝光狀態(tài) */
ManualFocusAndExpose,/**< 手動聚焦曝光狀態(tài) */
PartFocusAndExpose/**< 聚焦曝光分離狀態(tài) */
};
/**
* 自定義相機類
*/
@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;
/**
* 初始化相機
* 默認初始化相機為前置相機,前置攝像為鏡像,后置非鏡像
* 默認閃光燈為自動閃光模式
* 默認聚焦狀態(tài)為自動聚焦
* @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
設計手勢識別狀態(tài)機,對不同的手勢添加相應功能
- 隨著我們對手勢的需求逐漸增多,設計一個手勢識別狀態(tài)機就越來越必要了。剛開始我們的手勢只有tap和pinch兩種,前者用來設置焦點,后者用來控制焦距。但是現(xiàn)在我們的需求是這樣的:
1.單擊屏幕可進行焦點及曝光調整
2.拖動聚焦框也可以進行調整
2.通過pinch手勢可將焦點設置及曝光設置分離
3.分離后通過點按方式設置焦點,同時也可以通過拖動聚焦框設置焦點
4.分離后通過拖動曝光框手勢設置曝光
5.通過雙擊屏幕回到聚焦曝光合并狀態(tài),同時焦點自動調整為屏幕中心點
為了理清邏輯,于是以下這張手勢轉換狀態(tài)機圖就誕生了
如圖所示,我們設置了三個狀態(tài),分別是自動聚焦曝光狀態(tài),聚焦曝光狀態(tài)以及聚焦曝光分離狀態(tài)。
首先我們要設置對應的圖片,即先調用以下方法
/** 設置聚焦曝光圖片 */
- (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手勢的時候,由于系統(tǒng)先識別了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.");
}
}
之后就是對應手勢調用的方法實現(xiàn):
這里例舉一個focus方法,其中block部分可以忽略過去,那是之前做回調聚焦時間的時候添加上去的代碼,但是后來聚焦時間用KVO做了,然后這邊的block回調就先放著,沒有刪掉。
在focus方法中,最主要的還是上一篇中提到的屏幕觸點和聚焦點的映射問題。其他不難,調用系統(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坐標交換是為了解決照相機焦點坐標軸和屏幕坐標軸的映射問題
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版本中是只有閃光燈開,閃光燈關,自動閃光燈狀態(tài)的,現(xiàn)在我們新增一種狀態(tài)--就是--打開手電筒??功能??!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;
}
}
- 其實這里最主要的是展開動畫的效果
關于動畫的學習這里推薦一篇博客:iOS動畫(Core Animation)總結
展開動畫的原理其實很簡單,做粗略點就是一個位移動畫,做稍微花哨點就是位移+縮放動畫。所有的復雜的動畫都可以分解為簡單動畫的組合,也就是組動畫。原理上動畫無非就是簡單動畫、關鍵幀動畫、過渡動畫、組動畫、粒子動畫這幾種,學會怎么分解怎么組合動畫就可以做出自己想要的酷炫的效果。當然說起來容易,這東西還是得靠多多的實踐才行。
以下是按鈕展開方法的實現(xiàn):
- (void)showButtons {
if ([self.delegate respondsToSelector:@selector(bubbleMenuButtonWillExpand:)]) {
[self.delegate bubbleMenuButtonWillExpand:self];
}
[self _prepareForButtonExpansion];
self.userInteractionEnabled = NO;
[CATransaction begin];//首先開始動畫的事務
[CATransaction setAnimationDuration:_animationDuration];//設置動畫的持續(xù)時間
[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上出現(xiàn)兩個圖標,分別控制聚焦和曝光功能,對圖標進行拖動也可以做出相應的自動調整。
/** 給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;
}
接下來是手動調整聚焦以及曝光功能的實現(xiàn)。也就是AF和AE功能。
在很多比較專業(yè)的相機App中,AF以及AWB、ISO、曝光時間設置的功能都是需要收費的。大家可以看一下Camera+和ProCam等軟件,Camera+免費版中這些功能是鎖定掉的,要想解鎖就得付費。ProCam就更不用說了,沒有免費版,要想用也行,交30軟妹幣,或者自己從別的途徑解決??
其實簡單的AF、AWB、ISO等功能的實現(xiàn)也是很簡單的,同樣只是調用接口而已,但是要做出很好的用戶體驗和效果來就比較難了。目前這邊只是做了AF、AE的調整,其他功能之后再實現(xiàn)
首先是AF
MTStiilCamera對系統(tǒng)調整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做的,那我當然可以通過監(jiān)聽adjustingFocus的值來檢測到相機是否正在聚焦。當相機鏡片開始移動時,adjustingFocus的值就是YES,當聚焦成功后值就變成NO。然后在開始聚焦的時候獲取系統(tǒng)時間,在聚焦結束后再次獲取系統(tǒng)時間,相減,就是我們想要的結果了。
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實現(xiàn)自定義相機的介紹就先到這里啦,有不對或者可以改進的地方歡迎大家提出來,共同學習。
- 后面可能會做一些類似白平衡、ISO調整等功能以及用戶體驗更好的聚焦效果 大家3.0版本再見????