前言
- 这是基于第三方库GPUImage实现的自定义相机,主要是功能方面的实现,在UI上暂时并没有做很好的完善。主要包括功能有实时滤镜,准确聚焦,调整焦距,调整曝光,闪光灯设置,翻转前后相机以及拍照后的滤镜调整等
引入GPUImage库
- 主要有工程以依赖的形式加入GPUImage或者用Pod安装GPUImage的方法,网上类似教程很多,这里就不赘述了
制作自定义相机
新建一个CameraViewController,作为实时相机界面的Controller,并且做以下预处理及声明变量
#define HEIGHT 667.0
#define WIDTH 375.0
static int CameraFilterCount = 9;//滤镜的数量
///滤镜View的显示状态
typedef NS_ENUM(NSInteger, FilterViewState) {
FilterViewHidden,/**<隐藏*/
FilterViewUsing /**<显示*/
};
///闪光灯状态
typedef NS_ENUM(NSInteger, CameraManagerFlashMode) {
CameraManagerFlashModeAuto, /**<自动*/
CameraManagerFlashModeOff, /**<关闭*/
CameraManagerFlashModeOn /**<打开*/
};
@interface CameraViewController() <UIGestureRecognizerDelegate>
{
CALayer *_focusLayer; //聚焦层
}
///新建相机cameraManager
@property (nonatomic,strong) GPUImageStillCamera *cameraManager;
/**
*根据storyboard上将界面分为三个View,预览View,底部View以及整体的cameraView,也可以用代码实现
*/
@property (strong, nonatomic) IBOutlet UIView *cameraView;
@property (weak, nonatomic) IBOutlet UIView *preview;
@property (weak, nonatomic) IBOutlet UIView *bottomView;
@property CameraFilterView *cameraFilterView;//自定义滤镜视图
@property (nonatomic , assign) CameraManagerFlashMode flashMode;
@property (nonatomic , assign) FilterViewState filterViewState;
@property (nonatomic , weak) IBOutlet UIButton *flashButton;//闪光灯按钮
@property (nonatomic , assign) CGFloat beginGestureScale;//开始的缩放比例
@property (nonatomic , assign) CGFloat effectiveScale;//最后的缩放比例
@property GPUImageOutput *filter;//滤镜
@property GPUImageView *filterView;//实时滤镜预览视图
@property AVCaptureStillImageOutput *photoOutput;//用于保存原图
@property CheckViewController *checkVC;//拍照之后跳转的ViewController
@end
控制器视图方法
- (void)viewDidLoad{
//隐藏导航栏
[self.navigationController setNavigationBarHidden:YES];
//从SB初始化CheckController,后续解释FilterCode
_checkVC = [[self storyboard] instantiateViewControllerWithIdentifier:@"Check"];
[_checkVC setFilterCode:0];
//初始化相机,默认为前置相机
_cameraManager = [[MyCamera alloc] initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionFront];
_cameraManager.outputImageOrientation = UIInterfaceOrientationPortrait;//设置照片的方向为设备的定向
_cameraManager.horizontallyMirrorFrontFacingCamera = YES;//设置是否为镜像
_cameraManager.horizontallyMirrorRearFacingCamera = NO;
_filter = [[GPUImageFilter alloc] init];//初始化滤镜,默认初始化为原图GPUImageFilter
[self setfocusImage:[UIImage imageNamed:@"touch_focus_x"]];//初始化聚焦图片
/**
*设置cameraManager的输出对象为filter,然后将preview强制转换为filterView添加到filter的输出对象中,这样在filterView中显示的就是相机捕捉到的并且经过filter滤镜处理的实时图像了
*/
[self.cameraManager addTarget:_filter];
_filterView = (GPUImageView *)self.preview;
[_filter addTarget:_filterView];
//初始化闪光灯模式为Auto
[self setFlashMode:CameraManagerFlashModeAuto];
//默认滤镜视图为隐藏,就是点击滤镜的按钮才会出现让你选择滤镜的那个小视图
[self setFilterViewState:FilterViewHidden];
//初始化开始及结束的缩放比例都为1.0
[self setBeginGestureScale:1.0f];
[self setEffectiveScale:1.0f];
//启动相机捕获
[self.cameraManager startCameraCapture];}
UI方法
- 这里先给出CameraFilterView的实现方法,其实就是一个简单的一行的CollectionView,博主比较懒,引用了ShawnDu大哥的代码,在此表示感谢🙏
///.h文件
#import <UIKit/UIKit.h>
@protocol CameraFilterViewDelegate;
@interface CameraFilterView : UICollectionView <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (strong, nonatomic) NSMutableArray *picArray;//图片数组
@property (strong, nonatomic) id <CameraFilterViewDelegate> cameraFilterDelegate;
@end
@protocol CameraFilterViewDelegate <NSObject>
- (void)switchCameraFilter:(NSInteger)index;//滤镜选择方法
@end
\\\.m文件
#import "CameraFilterView.h"
static const float CELL_HEIGHT = 84.0f;
static const float CELL_WIDTH = 56.0f;
@implementation CameraFilterView
#pragma mark 初始化方法
- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout{
self = [super initWithFrame:frame collectionViewLayout:layout];
if (self) {
self.delegate = self;
self.dataSource = self;
}
return self;
}
#pragma mark collection 方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [_picArray count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = @"cameraFilterCellID";
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:identifier];
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, CELL_WIDTH, CELL_HEIGHT)];
imageView.image = [_picArray objectAtIndex:indexPath.row];
[cell addSubview:imageView];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(CELL_WIDTH, CELL_HEIGHT);
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
[_cameraFilterDelegate switchCameraFilter:indexPath.row];
}
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
@end
- 关于Collection控件不懂的同学可以先复习一下CollectionView或者用其他的方法实现自定义控件
//添加滤镜视图到主视图上
- (void)addCameraFilterView {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_cameraFilterView = [[CameraFilterView alloc] initWithFrame:CGRectMake(0, HEIGHT - _bottomView.frame.size.height - (WIDTH-4)/5 - 4, WIDTH, (WIDTH-4)/5) collectionViewLayout:layout];
NSMutableArray *filterNameArray = [[NSMutableArray alloc] initWithCapacity:CameraFilterCount];
for (NSInteger index = 0; index < CameraFilterCount; index++) {
UIImage *image = [UIImage imageNamed:@"girl"];
[filterNameArray addObject:image];
}
_cameraFilterView.cameraFilterDelegate = self;
_cameraFilterView.picArray = filterNameArray;
[self.view addSubview:_cameraFilterView];
}
//使用滤镜
- (IBAction)useFilter:(id)sender {
if (self.filterViewState == FilterViewHidden) {
[self addCameraFilterView];
[self setFilterViewState:FilterViewUsing];
}
else {
[_cameraFilterView removeFromSuperview];
[self setFilterViewState:FilterViewHidden];
}
}
//设置聚焦图片
- (void)setfocusImage:(UIImage *)focusImage {
if (!focusImage) return;
if (!_focusLayer) {
//增加tap手势,用于聚焦及曝光
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(focus:)];
[self.preview addGestureRecognizer:tap];
//增加pinch手势,用于调整焦距
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(focusDisdance:)];
[self.preview addGestureRecognizer:pinch];
pinch.delegate = self;
}
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, focusImage.size.width, focusImage.size.height)];
imageView.image = focusImage;
CALayer *layer = imageView.layer;
layer.hidden = YES;
[self.preview.layer addSublayer:layer];
_focusLayer = layer;
}
//点击闪光灯按钮
- (IBAction)changeFlash:(id)sender {
[self changeFlashMode:_flashButton];
}
滤镜选择方法的实现
/**
*以下的滤镜是GPUImage自带的滤镜,后面有几个效果不是很明显,我只是拿来做测试用。
*GPUImage自带的滤镜很多,大家可以都试试看,当然也可以自己写滤镜,这就需要后续深入学习了
*对checkViewController中filtercode属性的说明:filtercode是在选择滤镜的时候设置,
*在拍照的时候传值给CheckViewController,实际上拍照传递给CheckVC的是原图加上当前设置的filtercode,
*所以在后续CheckVC中改变滤镜的时候是基于原图进行改变。
*/
- (void)switchCameraFilter:(NSInteger)index {
[self.cameraManager removeAllTargets];
switch (index) {
case 0:
_filter = [[GPUImageFilter alloc] init];//原图
[_checkVC setFilterCode:0];
break;
case 1:
_filter = [[GPUImageHueFilter alloc] init];//绿巨人
[_checkVC setFilterCode:1];
break;
case 2:
_filter = [[GPUImageColorInvertFilter alloc] init];//负片
[_checkVC setFilterCode:2];
break;
case 3:
_filter = [[GPUImageSepiaFilter alloc] init];//老照片
[_checkVC setFilterCode:3];
break;
case 4: {
_filter = [[GPUImageGaussianBlurPositionFilter alloc] init];
[(GPUImageGaussianBlurPositionFilter*)_filter setBlurRadius:40.0/320.0];
[_checkVC setFilterCode:4];
}
break;
case 5:
_filter = [[GPUImageSketchFilter alloc] init];//素描
[_checkVC setFilterCode:5];
break;
case 6:
_filter = [[GPUImageVignetteFilter alloc] init];//黑晕
[_checkVC setFilterCode:6];
break;
case 7:
_filter = [[GPUImageGrayscaleFilter alloc] init];//灰度
[_checkVC setFilterCode:7];
break;
case 8:
_filter = [[GPUImageToonFilter alloc] init];//卡通效果 黑色粗线描边
[_checkVC setFilterCode:8];
default:
_filter = [[GPUImageFilter alloc] init];
[_checkVC setFilterCode:9];
break;
}
[self.cameraManager addTarget:_filter];
[_filter addTarget:_filterView];
}
拍照方法的实现
- (IBAction)takePhoto:(id)sender {
_photoOutput = [_cameraManager getPhotoOutput];//得到源数据输出流
NSDictionary * outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey, nil];
//这是输出流的设置参数AVVideoCodecJPEG参数表示以JPEG的图片格式输出图片
[_photoOutput setOutputSettings:outputSettings];
AVCaptureConnection *captureConnection=[_photoOutput connectionWithMediaType:AVMediaTypeVideo];
//根据连接取得设备输出的数据
[_photoOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer) {
NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image=[UIImage imageWithData:imageData];
//将输出流数据转换成NSData,然后通过NSData将数据转换为UIImage传递给checkVC
_checkVC.image = image;
[self.navigationController pushViewController:_checkVC animated:YES];
}
}];
}
转置摄像头
#pragma mark 转置摄像头
- (IBAction)turn:(id)sender {
[self.cameraManager stopCameraCapture];
if (_cameraManager.cameraPosition == AVCaptureDevicePositionFront) {
[_cameraManager initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionBack];
}
else if(_cameraManager.cameraPosition == AVCaptureDevicePositionBack)
{
[_cameraManager initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionFront];
}
_cameraManager.outputImageOrientation = UIInterfaceOrientationPortrait;
_cameraManager.horizontallyMirrorFrontFacingCamera = YES;
_cameraManager.horizontallyMirrorRearFacingCamera = NO;
[self.cameraManager addTarget:_filter];
_filterView = (GPUImageView *)self.preview;
[_filter addTarget:_filterView];
[self setBeginGestureScale:1.0f];//在转换摄像头的时候把摄像头的焦距调回1.0
[self setEffectiveScale:1.0f];
[self.cameraManager startCameraCapture];
}
调整焦距
//调整焦距方法
-(void)focusDisdance:(UIPinchGestureRecognizer*)pinch {
self.effectiveScale = self.beginGestureScale * pinch.scale;
if (self.effectiveScale < 1.0f) {
self.effectiveScale = 1.0f;
}
CGFloat maxScaleAndCropFactor = 3.0f;//设置最大放大倍数为3倍
if (self.effectiveScale > maxScaleAndCropFactor)
self.effectiveScale = maxScaleAndCropFactor;
[CATransaction begin];
[CATransaction setAnimationDuration:.025];
NSError *error;
if([self.cameraManager.inputCamera lockForConfiguration:&error]){
[self.cameraManager.inputCamera setVideoZoomFactor:self.effectiveScale];
[self.cameraManager.inputCamera unlockForConfiguration];
}
else {
NSLog(@"ERROR = %@", error);
}
[CATransaction commit];
}
//手势代理方法
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ( [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] ) {
self.beginGestureScale = self.effectiveScale;
}
return YES;
}
对焦
//对焦方法
- (void)focus:(UITapGestureRecognizer *)tap {
self.preview.userInteractionEnabled = NO;
CGPoint touchPoint = [tap locationInView:tap.view];
// CGContextRef *touchContext = UIGraphicsGetCurrentContext();
[self layerAnimationWithPoint:touchPoint];
/**
*下面是照相机焦点坐标轴和屏幕坐标轴的映射问题,这个坑困惑了我好久,花了各种方案来解决这个问题,以下是最简单的解决方案也是最准确的坐标转换方式
*/
if(_cameraManager.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);
/*以下是相机的聚焦和曝光设置,前置不支持聚焦但是可以曝光处理,后置相机两者都支持,下面的方法是通过点击一个点同时设置聚焦和曝光,当然根据需要也可以分开进行处理
*/
if([self.cameraManager.inputCamera isExposurePointOfInterestSupported] && [self.cameraManager.inputCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
{
NSError *error;
if ([self.cameraManager.inputCamera lockForConfiguration:&error]) {
[self.cameraManager.inputCamera setExposurePointOfInterest:touchPoint];
[self.cameraManager.inputCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
if ([self.cameraManager.inputCamera isFocusPointOfInterestSupported] && [self.cameraManager.inputCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
[self.cameraManager.inputCamera setFocusPointOfInterest:touchPoint];
[self.cameraManager.inputCamera setFocusMode:AVCaptureFocusModeAutoFocus];
}
[self.cameraManager.inputCamera unlockForConfiguration];
} else {
NSLog(@"ERROR = %@", error);
}
}
}
//对焦动画
- (void)layerAnimationWithPoint:(CGPoint)point {
if (_focusLayer) {
///聚焦点聚焦动画设置
CALayer *focusLayer = _focusLayer;
focusLayer.hidden = NO;
[CATransaction begin];
[CATransaction setDisableActions:YES];
[focusLayer setPosition:point];
focusLayer.transform = CATransform3DMakeScale(2.0f,2.0f,1.0f);
[CATransaction commit];
CABasicAnimation *animation = [ CABasicAnimation animationWithKeyPath: @"transform" ];
animation.toValue = [ NSValue valueWithCATransform3D: CATransform3DMakeScale(1.0f,1.0f,1.0f)];
animation.delegate = self;
animation.duration = 0.3f;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[focusLayer addAnimation: animation forKey:@"animation"];
}
}
//动画的delegate方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
// 1秒钟延时
[self performSelector:@selector(focusLayerNormal) withObject:self afterDelay:0.5f];
}
//focusLayer回到初始化状态
- (void)focusLayerNormal {
self.preview.userInteractionEnabled = YES;
_focusLayer.hidden = YES;
}
闪光灯设置
//设置闪光灯模式
- (void)setFlashMode:(CameraManagerFlashMode)flashMode {
_flashMode = flashMode;
switch (flashMode) {
case CameraManagerFlashModeAuto: {
[self.cameraManager.inputCamera lockForConfiguration:nil];
if ([self.cameraManager.inputCamera isFlashModeSupported:AVCaptureFlashModeAuto]) {
[self.cameraManager.inputCamera setFlashMode:AVCaptureFlashModeAuto];
}
[self.cameraManager.inputCamera unlockForConfiguration];
}
break;
case CameraManagerFlashModeOff: {
[self.cameraManager.inputCamera lockForConfiguration:nil];
[self.cameraManager.inputCamera setFlashMode:AVCaptureFlashModeOff];
[self.cameraManager.inputCamera unlockForConfiguration];
}
break;
case CameraManagerFlashModeOn: {
[self.cameraManager.inputCamera lockForConfiguration:nil];
[self.cameraManager.inputCamera setFlashMode:AVCaptureFlashModeOn];
[self.cameraManager.inputCamera unlockForConfiguration];
}
break;
default:
break;
}
}
#pragma mark 改变闪光灯状态
- (void)changeFlashMode:(UIButton *)button {
switch (self.flashMode) {
case CameraManagerFlashModeAuto:
self.flashMode = CameraManagerFlashModeOn;
[button setImage:[UIImage imageNamed:@"flashing_on"] forState:UIControlStateNormal];
break;
case CameraManagerFlashModeOff:
self.flashMode = CameraManagerFlashModeAuto;
[button setImage:[UIImage imageNamed:@"flashing_auto"] forState:UIControlStateNormal];
break;
case CameraManagerFlashModeOn:
self.flashMode = CameraManagerFlashModeOff;
[button setImage:[UIImage imageNamed:@"flashing_off"] forState:UIControlStateNormal];
break;
default:
break;
}
}
对拍照之后的照片进行滤镜处理
- 这个其实很简单,处理方案和之前的实时滤镜差不多,也是通过FilterController来实现滤镜的选择,下面就给出选择滤镜之后进行处理的函数
- (void)switchCameraFilter:(NSInteger)index {
UIImage *inputImage = self.image;
switch (index) {
case 0:
_filter = [[GPUImageFilter alloc] init];//原图
break;
case 1:
_filter = [[GPUImageHueFilter alloc] init];//绿巨人
break;
case 2:
_filter = [[GPUImageColorInvertFilter alloc] init];//负片
break;
case 3:
_filter = [[GPUImageSepiaFilter alloc] init];//老照片
break;
case 4: {
_filter = [[GPUImageGaussianBlurPositionFilter alloc] init];
[(GPUImageGaussianBlurPositionFilter*)_filter setBlurRadius:40.0/320.0];
}
break;
case 5:
_filter = [[GPUImageMedianFilter alloc] init];
break;
case 6:
_filter = [[GPUImageVignetteFilter alloc] init];//黑晕
break;
case 7:
_filter = [[GPUImageKuwaharaRadius3Filter alloc] init];
break;
case 8:
_filter = [[GPUImageBilateralFilter alloc] init];
default:
_filter = [[GPUImageFilter alloc] init];
break;
}
//到这里为止和实时滤镜的处理都一样,不一样的就只有下面几句
[_filter forceProcessingAtSize:inputImage.size];
[_filter useNextFrameForImageCapture];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage smoothlyScaleOutput:YES];
[stillImageSource addTarget:_filter];
[stillImageSource processImage];
_currentImage = [_filter imageFromCurrentFramebuffer];
//这里得到的currentImage就是添加了滤镜之后的照片,可以放在imageView中直接显示,也可以稍加处理保存到相册
[self.imageView setImage:_currentImage];
}
总结
- 以上是最近在做自定义相机时做的一点总结,之前在做的时候也查阅了很多文章,感觉相对比较分散,所以这里就稍微做一下整理,希望大家在做类似需求的开发时能少碰到点坑。当然我做的也不怎么完善,风格也比较杂乱,后续会对此做些改进。
- 有什么不对的地方欢迎大家批评指正,共同学习