一、VideoCamera的定義
OpenCV提供了一個(gè)類 CvVideoCamera十气,它實(shí)現(xiàn)了高級(jí)相機(jī)控制功能和預(yù)覽GUI励背,但是支持高度定制。CvVideoCamera 建立在 AVFoundation 之上砸西,并提供對(duì)一些底層類的訪問叶眉。因此,應(yīng)用程序開發(fā)人員可以選擇使用高級(jí) CvVideoCamera 功能和低級(jí) AVFoundation 功能的組合芹枷。應(yīng)用程序開發(fā)人員實(shí)現(xiàn)大多數(shù)GUI衅疙,并且可以禁用視頻預(yù)覽,或者指定 CvVideoCamera 將呈現(xiàn)它的父視圖鸳慈。此外饱溢,應(yīng)用程序可以在捕獲每個(gè)視頻幀時(shí)對(duì)其進(jìn)行處理,并且如果應(yīng)用程序就地編輯捕獲的幀走芋,則 CvVideoCamera 將在預(yù)覽中顯示結(jié)果绩郎。因此潘鲫,CvVideoCamera 是工程一個(gè)合適的起點(diǎn)。
- OpenCV還提供了一個(gè)名為 CvPhotoCamera 的類肋杖,用于捕獲高質(zhì)量的靜態(tài)圖像而不是連續(xù)的視頻流溉仑。與CvVideoCamera不同的是,CvPhotoCamera不允許我們將自定義圖像處理應(yīng)用到實(shí)時(shí)預(yù)覽状植。
- 自定義CvVideoCamera
我們將創(chuàng)建一個(gè)稱為 CvVideoCamera 的子類浊竟,名為 VideoCamera.h 的新頭文件。在這里津畸,我們將聲明子類的公共接口振定,包括新的屬性和方法,如下面的代碼所示:
#import <opencv2/videoio/cap_ios.h>
@interface VideoCamera : CvVideoCamera
@property (nonatomic,assign) BOOL letterboxPreview;
- (void)setPointOfInterestInParentViewSpace:(CGPoint)point;
@end
- (void)setPointOfInterestInParentViewSpace:(CGPoint)point; 方法將為相機(jī)的自動(dòng)對(duì)焦和自動(dòng)曝光算法設(shè)置一個(gè)關(guān)注點(diǎn)洼畅。在簡單搜索最佳解決方案之后吩案,相機(jī)應(yīng)該重新配置自己,以便其焦距和中間色調(diào)水平匹配給定點(diǎn)的鄰域帝簇,該鄰域在預(yù)覽的父視圖中用像素坐標(biāo)表示徘郭。換句話說,在調(diào)整之后丧肴,點(diǎn)和它的鄰域應(yīng)該是聚焦的残揉,并且大約為50%灰色。然而芋浮,良好的自動(dòng)曝光算法可以允許根據(jù)顏色和場景的其他區(qū)域的亮度變化抱环。
類的實(shí)現(xiàn)文件 VideoCamera.m 代碼中,我們將添加一個(gè)帶有屬性的私有接口纸巷,customPreviewLayer 層镇草,如下面的代碼所示:
#import "VideoCamera.h"
@interface VideoCamera ()
@property (nonatomic, strong) CALayer *customPreviewLayer;
@end
為了定制預(yù)覽層的布局,我們將重寫 CvVideoCamera 的以下方法:
- (int)imageWidth 和 (int)imageHeight: 這些方法應(yīng)該返回相機(jī)當(dāng)前使用的水平分辨率和垂直分辨率瘤旨。超類的實(shí)現(xiàn)是錯(cuò)誤的(在OpenCV 3.1中)梯啤,因?yàn)樗蕾囉陉P(guān)于各種質(zhì)量模式中的默認(rèn)分辨率的一組假設(shè),而不是直接查詢當(dāng)前分辨率存哲。
- (void)updateSize: 超類使用這種方法來假設(shè)相機(jī)的分辨率因宇。這實(shí)際上是一種適得其反的方法。正如前面的子彈點(diǎn)所描述的祟偷,假設(shè)是不可靠的和不必要的察滑。
- (void)layoutPreviewLayer: 該方法應(yīng)該以尊重當(dāng)前設(shè)備方向的方式布置預(yù)覽。超類的實(shí)現(xiàn)是錯(cuò)誤的(在OpenCV 3.1中)修肠。在某些情況下贺辰,預(yù)覽被拉伸或不正確地定向。
為了獲得正確的分辨率,我們可以通過一個(gè)名為 AVCaptureVideoDataOutput 的 AVFoundation 類查詢相機(jī)的當(dāng)前捕獲參數(shù)魂爪。請(qǐng)參考下面的代碼先舷,該代碼重寫了 imageWidth 的getter方法:
- (int)imageWidth {
AVCaptureVideoDataOutput *output = [self.captureSession.outputs lastObject];
NSDictionary *videoSettings = [output videoSettings];
int videoWidth = [[videoSettings objectForKey:@"Width"] intValue];
return videoWidth;
}
類似地,讓我們重寫 imageHeight 的getter方法:
- (int)imageHeight {
AVCaptureVideoDataOutput *output = [self.captureSession.outputs lastObject];
NSDictionary *videoSettings = [output videoSettings];
int videoHeight = [[videoSettings objectForKey:@"Height"] intValue];
return videoHeight;
}
現(xiàn)在滓侍,我們已經(jīng)充分解決了查詢相機(jī)分辨率的問題蒋川。因此,我們可以用一個(gè)空實(shí)現(xiàn)重寫 updateSize 方法:
- (void)updateSize {
// Do nothing.
}
當(dāng)放映視頻預(yù)覽時(shí)撩笆,首先我們將其放在父視圖中捺球。然后,我們發(fā)現(xiàn)它的長寬比和選擇預(yù)覽大小方面的縱橫比夕冲。如果 letterboxpreview 值為 YES氮兵,預(yù)覽顯示可能小于其在父視圖的尺寸。否則歹鱼,它可能大于父視圖其中的一個(gè)維度泣栈,在這種情況下,它的邊緣可以因此出現(xiàn)視圖之外弥姻。下面的代碼演示了如何定位和縮小預(yù)覽:
- (void)layoutPreviewLayer {
if (self.parentView != nil) {
// Center the video preview.
self.customPreviewLayer.position = CGPointMake(0.5 * self.parentView.frame.size.width, 0.5 * self.parentView.frame.size.height);
// Find the video's aspect ratio.
CGFloat videoAspectRatio = self.imageWidth / (CGFloat)self.imageHeight;
// Scale the video preview while maintaining its aspect ratio.
CGFloat boundsW;
CGFloat boundsH;
if (self.imageHeight > self.imageWidth) {
if (self.letterboxPreview) {
boundsH = self.parentView.frame.size.height;
boundsW = boundsH * videoAspectRatio;
} else {
boundsW = self.parentView.frame.size.width;
boundsH = boundsW / videoAspectRatio;
}
} else {
if (self.letterboxPreview) {
boundsW = self.parentView.frame.size.width;
boundsH = boundsW / videoAspectRatio;
} else {
boundsH = self.parentView.frame.size.height;
boundsW = boundsH * videoAspectRatio;
}
}
self.customPreviewLayer.bounds = CGRectMake(0.0, 0.0, boundsW, boundsH);
}
}
現(xiàn)在我們來看 - (void)setPointOfInterestInParentViewSpace:(CGPoint)point; 方法南片。AVFoundation 允許我們?yōu)榻裹c(diǎn)和曝光指定一個(gè)興趣點(diǎn),下面是我們的方法的實(shí)現(xiàn)庭敦,它檢查相機(jī)的自動(dòng)曝光和自動(dòng)對(duì)焦能力疼进,執(zhí)行坐標(biāo)轉(zhuǎn)換,驗(yàn)證坐標(biāo)秧廉,并通過AVFoundation功能設(shè)置關(guān)注點(diǎn):
- (void)setPointOfInterestInParentViewSpace:(CGPoint)parentViewPoint {
if (!self.running) {
return;
}
// Find the current capture device.
NSArray *captureDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *captureDevice;
for (captureDevice in captureDevices) {
if (captureDevice.position == self.defaultAVCaptureDevicePosition) {
break;
}
}
BOOL canSetFocus = [captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus] && captureDevice.isFocusPointOfInterestSupported;
BOOL canSetExposure = [captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose] && captureDevice.isExposurePointOfInterestSupported;
if (!canSetFocus && !canSetExposure) {
return;
}
if (![captureDevice lockForConfiguration:nil]) {
return;
}
// Find the preview's offset relative to the parent view.
CGFloat offsetX = 0.5 * (self.parentView.bounds.size.width – self.customPreviewLayer.bounds.size.width);
CGFloat offsetY = 0.5 * (self.parentView.bounds.size.height – self.customPreviewLayer.bounds.size.height);
// Find the focus coordinates, proportional to the preview size.
CGFloat focusX = (parentViewPoint.x - offsetX) / self.customPreviewLayer.bounds.size.width;
CGFloat focusY = (parentViewPoint.y - offsetY) / self.customPreviewLayer.bounds.size.height;
if (focusX < 0.0 || focusX > 1.0 || focusY < 0.0 || focusY > 1.0) {
// The point is outside the preview.
return;
}
// Adjust the focus coordinates based on the orientation.
// They should be in the landscape-right coordinate system.
switch (self.defaultAVCaptureVideoOrientation) {
case AVCaptureVideoOrientationPortraitUpsideDown: {
CGFloat oldFocusX = focusX;
focusX = 1.0 - focusY;
focusY = oldFocusX;
break;
}
case AVCaptureVideoOrientationLandscapeLeft: {
focusX = 1.0 - focusX;
focusY = 1.0 - focusY;
break;
}
case AVCaptureVideoOrientationLandscapeRight: {
// Do nothing.
break;
}
default: { // Portrait
CGFloat oldFocusX = focusX;
focusX = focusY;
focusY = 1.0 - oldFocusX;
break;
}
}
if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) {
// De-mirror the X coordinate.
focusX = 1.0 - focusX;
}
CGPoint focusPoint = CGPointMake(focusX, focusY);
if (canSetFocus) {
// Auto-focus on the selected point.
captureDevice.focusMode = AVCaptureFocusModeAutoFocus;
captureDevice.focusPointOfInterest = focusPoint;
}
if (canSetExposure) {
// Auto-expose for the selected point.
captureDevice.exposureMode = AVCaptureExposureModeAutoExpose;
captureDevice.exposurePointOfInterest = focusPoint;
}
[captureDevice unlockForConfiguration];
}
現(xiàn)在伞广,我們已經(jīng)實(shí)現(xiàn)了一個(gè)類,它能夠配置相機(jī)和捕捉幀疼电。但是嚼锄,我們?nèi)匀恍枰獙?shí)現(xiàn)另一個(gè)類來選擇配置和接收幀。
二蔽豺、ViewController的定義
- 定義 ViewController 類的私有接口灾票。此外,它依賴于定義我們自己類的公共接口的頭部茫虽,ViewController 和 VideoCamera。讓我們通過在 ViewController 內(nèi)添加以下代碼來導(dǎo)入這些依賴項(xiàng):
#import <Photos/Photos.h>
#import <Social/Social.h>
#import <opencv2/core.hpp>
#import <opencv2/imgcodecs.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/imgproc.hpp>
#import "ViewController.h"
#import "VideoCamera.h"
- 定義 ViewController 類的實(shí)例變量既们。我們將使用多個(gè) cv::Mat 對(duì)象來存儲(chǔ)靜態(tài)圖像和相機(jī)圖像的顏色或灰度格式濒析。我們的GUI對(duì)象將包括圖像視圖、活動(dòng)指示器(繁忙的旋轉(zhuǎn)器)和工具欄啥纸。我們將使用一個(gè)視頻攝像機(jī)類的實(shí)例來控制攝像機(jī)号杏,抓取并顯示視頻圖像。最后,我們將使用布爾變量來跟蹤用戶是否按下保存按鈕來保存即將到來的幀盾致。下面是相關(guān)的變量聲明:
@interface ViewController () <CvVideoCameraDelegate> {
cv::Mat originalStillMat;
cv::Mat updatedStillMatGray;
cv::Mat updatedStillMatRGBA;
cv::Mat updatedVideoMatGray;
cv::Mat updatedVideoMatRGBA;
}
@property IBOutlet UIImageView *imageView;
@property IBOutlet UIActivityIndicatorView *activityIndicatorView;
@property IBOutlet UIToolbar *toolbar;
@property VideoCamera *videoCamera;
@property BOOL saveNextFrame;
@end
注意主经,類名后面跟著 <CvVideoCameraDelegate>,這意味著該類實(shí)現(xiàn)了名為 CvVideoCameraDelegate 的協(xié)議庭惜。該協(xié)議是OpenCV的一部分罩驻,并定義了一種方法,- (void)processImage:(cv::Mat &)mat护赊,用于處理視頻幀惠遏。稍后,在控制相機(jī)部分骏啰,我們將討論這種回調(diào)方法如何與我們的視頻攝像機(jī)類相關(guān)节吮。
- 定義視頻攝像機(jī)的方法。有些方法是回調(diào)來處理GUI事件判耕,比如按下一個(gè)按鈕透绩。讓我們聲明視頻預(yù)覽的點(diǎn)擊對(duì)焦特性、顏色或灰色分段控件壁熄、切換相機(jī)按鈕和保存按鈕的以下回調(diào):
- (IBAction)onTapToSetPointOfInterest:(UITapGestureRecognizer *)tapGesture;
- (IBAction)onColorModeSelected:(UISegmentedControl *)segmentedControl;
- (IBAction)onSwitchCameraButtonPressed;
- (IBAction)onSaveButtonPressed;
除了手勢交互的回調(diào)方法帚豪,視頻攝像機(jī)還有幾種方法。在相機(jī)狀態(tài)或圖像處理設(shè)置的改變之后请毛,我們將調(diào)用刷新方法來更新顯示志鞍。其他方法將有助于處理、保存和共享圖像方仿,以及啟動(dòng)和停止應(yīng)用程序的忙碌模式固棚。以下是相關(guān)聲明:
- (void)refresh;
- (void)processImage:(cv::Mat &)mat;
- (void)processImageHelper:(cv::Mat &)mat;
- (void)saveImage:(UIImage *)image;
- (void)showSaveImageFailureAlertWithMessage:(NSString *)message;
- (void)showSaveImageSuccessAlertWithImage:(UIImage *)image;
- (UIAlertAction *)shareImageActionWithTitle:(NSString *)title serviceType:(NSString *)serviceType image:(UIImage *)image;
- (void)startBusyMode;
- (void)stopBusyMode;
- 在 ViewController.m 中實(shí)現(xiàn)定義的方法。首先仙蚜,我們將從文件加載靜態(tài)圖像并將其轉(zhuǎn)換為適當(dāng)?shù)母袷酱酥蕖H缓螅覀儗⒂梦覀兊膱D像視圖創(chuàng)建視頻攝像機(jī)的實(shí)例作為預(yù)覽的父視圖委粉。我們會(huì)告訴相機(jī)發(fā)送幀到該視圖控制器(委托)呜师,在30幀用一個(gè)高分辨率的模式的預(yù)覽。下面是 viewDidLoad 的實(shí)現(xiàn):
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *originalStillImage = [UIImage imageNamed:@"Fleur.jpg"];
UIImageToMat(originalStillImage, originalStillMat);
self.videoCamera = [[VideoCamera alloc] initWithParentView:self.imageView];
self.videoCamera.delegate = self;
self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetHigh;
self.videoCamera.defaultFPS = 30;
self.videoCamera.letterboxPreview = YES;
}
我們?cè)?viewDidLayoutSubviews 方法中配置攝像機(jī)的方向以匹配設(shè)備的方向(請(qǐng)注意贾节,每當(dāng)方向改變時(shí)汁汗,將再次調(diào)用該方法),如下面的代碼所示:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
switch ([UIDevice currentDevice].orientation) {
case UIDeviceOrientationPortraitUpsideDown:
self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeLeft:
self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIDeviceOrientationLandscapeRight:
self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationLandscapeRight;
break;
default:
self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
break;
}
[self refresh];
}
注意栗涂,在重新配置相機(jī)后知牌,我們調(diào)用 refresh 刷新方法。refresh 方法將檢查攝像機(jī)是否正在運(yùn)行斤程。如果是角寸,我們將確保靜態(tài)圖像被隱藏,我們將停止并重新啟動(dòng)相機(jī)。否則(如果沒有攝像頭運(yùn)行)扁藕,我們將重新處理靜態(tài)圖像并顯示結(jié)果沮峡。該處理包括將圖像轉(zhuǎn)換為適當(dāng)?shù)念伾袷讲⑵鋫鬟f到 processImage: 方法。請(qǐng)記住亿柑,CvVideoCamera 和我們的 VideoCamera 子類同樣將視頻幀傳遞給 CvVideoCameraDelegate 協(xié)議的 processImage: 方法邢疙。在這里,刷新橄杨,對(duì)靜態(tài)圖像使用相同的圖像處理方法秘症。讓我們看看刷新方法的實(shí)現(xiàn)。實(shí)現(xiàn)代碼如下:
- (void)refresh {
if (self.videoCamera.running) {
// Hide the still image.
self.imageView.image = nil;
// Restart the video.
[self.videoCamera stop];
[self.videoCamera start];
}
else {
// Refresh the still image.
UIImage *image;
if (self.videoCamera.grayscaleMode) {
cv::cvtColor(originalStillMat, updatedStillMatGray, cv::COLOR_RGBA2GRAY);
[self processImage:updatedStillMatGray];
image = MatToUIImage(updatedStillMatGray);
} else {
cv::cvtColor(originalStillMat, updatedStillMatRGBA, cv::COLOR_RGBA2BGRA);
[self processImage:updatedStillMatRGBA];
cv::cvtColor(updatedStillMatRGBA, updatedStillMatRGBA, cv::COLOR_BGRA2RGBA);
image = MatToUIImage(updatedStillMatRGBA);
}
self.imageView.image = image;
}
}
當(dāng)用戶輕敲預(yù)覽的父視圖時(shí)式矫,我們將 tap 事件傳遞給 setPointOfInterestInParentViewSpace: 方法乡摹,這是我們以前在視頻攝像機(jī)中實(shí)現(xiàn)的。以下是 tap 事件的相關(guān)回調(diào):
- (IBAction)onTapToSetPointOfInterest:(UITapGestureRecognizer *)tapGesture {
if (tapGesture.state == UIGestureRecognizerStateEnded) {
if (self.videoCamera.running) {
CGPoint tapPoint = [tapGesture locationInView:self.imageView];
[self.videoCamera setPointOfInterestInParentViewSpace:tapPoint];
}
}
}
當(dāng)用戶在 segmented control 控件中選擇顏色時(shí)采转,我們將把VideoCamera的grayscaleMode(灰度模式) 屬性設(shè)置為YES或NO聪廉。這個(gè)屬性是從CvVideoCamera繼承的。在設(shè)置了 grayscaleMode 之后故慈,我們將調(diào)用ViewController的刷新方法來使用適當(dāng)?shù)脑O(shè)置重新啟動(dòng)相機(jī)板熊。這里是回調(diào)來處理segmented control 控件狀態(tài)的更改:
- (IBAction)onColorModeSelected:(UISegmentedControl *)segmentedControl {
switch (segmentedControl.selectedSegmentIndex) {
case 0:
self.videoCamera.grayscaleMode = NO;
break;
default:
self.videoCamera.grayscaleMode = YES;
break;
}
[self refresh];
}
當(dāng)用戶點(diǎn)擊開關(guān)相機(jī)按鈕,我們將激活下一個(gè)相機(jī)或循環(huán)回到靜態(tài)圖像的初始狀態(tài)察绷。在每個(gè)過渡期間干签,我們必須確保前一個(gè)相機(jī)停止或先前的靜態(tài)圖像。隱藏并啟動(dòng)下一個(gè)攝像機(jī)或處理和顯示下一個(gè)靜態(tài)圖像拆撼。同樣容劳,我們也需要調(diào)用 refresh 刷新方法。下面是按鈕回調(diào)的實(shí)現(xiàn):
- (IBAction)onSwitchCameraButtonPressed {
if (self.videoCamera.running) {
switch (self.videoCamera.defaultAVCaptureDevicePosition) {
case AVCaptureDevicePositionFront:
self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack;
[self refresh];
break;
default:
[self.videoCamera stop];
[self refresh];
break;
}
}
else {
// Hide the still image.
self.imageView.image = nil;
self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
[self.videoCamera start];
}
}
在processImage: 方法中闸度,我們確保圖像的旋轉(zhuǎn)正確之后竭贩,我們將把它傳遞給另一個(gè)名為 processImageHelper: 的方法,這將是一個(gè)實(shí)現(xiàn)大多數(shù)圖像處理功能的方便的地方莺禁。最后留量,如果用戶單擊了Save按鈕,我們將將圖像轉(zhuǎn)換為適當(dāng)?shù)母袷讲⑵鋫鬟f給 saveImage: 方法哟冬。 如下:
- (void)processImage:(cv::Mat &)mat {
if (self.videoCamera.running) {
switch (self.videoCamera.defaultAVCaptureVideoOrientation) {
case AVCaptureVideoOrientationLandscapeLeft:
case AVCaptureVideoOrientationLandscapeRight:
// The landscape video is captured upside-down.
// Rotate it by 180 degrees.
cv::flip(mat, mat, -1);
break;
default:
break;
}
}
[self processImageHelper:mat];
if (self.saveNextFrame) {
// The video frame, 'mat', is not safe for long-running
// operations such as saving to file. Thus, we copy its
// data to another cv::Mat first.
UIImage *image;
if (self.videoCamera.grayscaleMode) {
mat.copyTo(updatedVideoMatGray);
image = MatToUIImage(updatedVideoMatGray);
} else {
cv::cvtColor(mat, updatedVideoMatRGBA, cv::COLOR_BGRA2RGBA);
image = MatToUIImage(updatedVideoMatRGBA);
}
[self saveImage:image];
self.saveNextFrame = NO;
}
}
到目前為止楼熄,我們還沒有做太多的圖像處理,只是一些顏色轉(zhuǎn)換和旋轉(zhuǎn)浩峡。讓我們添加下面的方法孝赫,在這里我們將執(zhí)行附加的圖像處理,混合圖像:
- (void)processImageHelper:(cv::Mat &)mat {
// TODO.
}
附:
通常红符,攝像機(jī)的固件,或者至少它的驅(qū)動(dòng)程序,可以有效地將捕獲的視頻轉(zhuǎn)換成平面的YUV格式预侯。然后致开,如果一個(gè)應(yīng)用程序只需要灰度數(shù)據(jù),它可以讀取或復(fù)制Y平面萎馅。這種方法比捕獲RGB幀并將它們轉(zhuǎn)換成灰度級(jí)的效率更高双戳。因此,當(dāng) CvVideoCamera 的 grayscaleMode(灰度模式) 屬性為YES時(shí)糜芳,它獲取平面YUV幀飒货,并將Y平面?zhèn)鬟f給 CvVideoCameraDelegate 協(xié)議的 processImage: 方法。
- 啟動(dòng)和停止Loading模式
在忙于保存或共享照片時(shí)峭竣,顯示活動(dòng)指示符并禁用所有工具欄項(xiàng)塘辅。相反,當(dāng)不再忙于處理照片時(shí)皆撩,我們希望隱藏活動(dòng)指示符并重新啟用工具欄項(xiàng)扣墩。當(dāng)這些操作影響GUI時(shí),我們必須確保它們運(yùn)行在應(yīng)用程序的主線程上扛吞。
在主線程中啟動(dòng)Loading模式呻惕,代碼如下:
- (void)startBusyMode {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicatorView startAnimating];
for (UIBarItem *item in self.toolbar.items) {
item.enabled = NO;
}
});
}
類似地,下面的方法用于停止Loading模式:
- (void)stopBusyMode {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicatorView stopAnimating];
for (UIBarItem *item in self.toolbar.items) {
item.enabled = YES;
}
});
}
- 將圖片保存到照片庫
當(dāng)用戶按下保存按鈕時(shí)滥比,我們啟動(dòng)Loading模式亚脆。然后,如果攝像機(jī)運(yùn)行盲泛,我們準(zhǔn)備保存下一幀濒持。否則,我們立即保存處理后的靜態(tài)圖像版本查乒。下面是事件處理程序:
- (IBAction)onSaveButtonPressed {
[self startBusyMode];
if (self.videoCamera.running) {
self.saveNextFrame = YES;
} else {
[self saveImage:self.imageView.image];
}
}
saveImage: 方法處理系統(tǒng)和照片庫的事務(wù)弥喉。首先,我們嘗試向應(yīng)用程序的臨時(shí)目錄寫入PNG文件玛迄。然后由境,我們嘗試根據(jù)這個(gè)文件在照片庫中創(chuàng)建一個(gè) asset。作為這個(gè)過程的一部分蓖议,文件被自動(dòng)復(fù)制虏杰。我們調(diào)用其他幫手方法顯示一個(gè)警報(bào)對(duì)話框,它將描述事務(wù)的成功或失敗勒虾。下面是方法的實(shí)現(xiàn):
- (void)saveImage:(UIImage *)image {
// Try to save the image to a temporary file.
NSString *outputPath = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.png"];
if (![UIImagePNGRepresentation(image) writeToFile:outputPath atomically:YES]) {
// Show an alert describing the failure.
[self showSaveImageFailureAlertWithMessage:@"The image could not be saved to the temporary directory."];
return;
}
// Try to add the image to the Photos library.
NSURL *outputURL = [NSURL URLWithString:outputPath];
PHPhotoLibrary *photoLibrary = [PHPhotoLibrary sharedPhotoLibrary];
[photoLibrary performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:outputURL];
} completionHandler:^(BOOL success, NSError *error) {
if (success) {
// Show an alert describing the success, with sharing
// options.
[self showSaveImageSuccessAlertWithImage:image];
} else {
// Show an alert describing the failure.
[self showSaveImageFailureAlertWithMessage:error.localizedDescription];
}
}];
}
Alert代碼:
- (void)showSaveImageFailureAlertWithMessage:(NSString *)message {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Failed to save image" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self stopBusyMode];
}];
[alert addAction:okAction];
[self presentViewController:alert animated:YES completion:nil];
}
- 分享圖片
如果光照成功將圖像保存到照片庫纺阔,我們希望向用戶顯示另一個(gè)帶有共享選項(xiàng)的 Alert。下面的方法檢查各種社交媒體平臺(tái)的可用性修然,并對(duì)每個(gè)可用平臺(tái)建立一個(gè) Alert 的操作按鈕笛钝。盡管針對(duì)的是不同的社交媒體平臺(tái)质况,但是動(dòng)作按鈕彼此相似,所以我們使用 shareImageActionWithTitle:serviceType:image: 方法玻靡。我們還提供了一個(gè)不共享動(dòng)作按鈕结榄,除了停止應(yīng)用程序的忙碌模式之外,什么也不做:
- (void)showSaveImageSuccessAlertWithImage:(UIImage *)image {
// Create a "Saved image" alert.
UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Saved image" message:@"The image has been added to your Photos library. Would you like to share it with your friends?" preferredStyle:UIAlertControllerStyleAlert];
// If the user has a Facebook account on this device, add a
// "Post on Facebook" button to the alert.
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
UIAlertAction* facebookAction = [self shareImageActionWithTitle:@"Post on Facebook" serviceType:SLServiceTypeFacebook image:image];
[alert addAction:facebookAction];
}
// If the user has a Twitter account on this device, add a
// "Tweet" button to the alert.
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {
UIAlertAction* twitterAction = [self shareImageActionWithTitle:@"Tweet" serviceType:SLServiceTypeTwitter image:image];
[alert addAction:twitterAction];
}
// If the user has a Sina Weibo account on this device, add a
// "Post on Sina Weibo" button to the alert.
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeSinaWeibo]) {
UIAlertAction* sinaWeiboAction = [self shareImageActionWithTitle:@"Post on Sina Weibo" serviceType:SLServiceTypeSinaWeibo image:image];
[alert addAction:sinaWeiboAction];
}
// If the user has a Tencent Weibo account on this device, add a
// "Post on Tencent Weibo" button to the alert.
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTencentWeibo]) {
UIAlertAction* tencentWeiboAction = [self shareImageActionWithTitle:@"Post on Tencent Weibo" serviceType:SLServiceTypeTencentWeibo image:image];
[alert addAction:tencentWeiboAction];
}
// Add a "Do not share" button to the alert.
UIAlertAction* doNotShareAction = [UIAlertAction actionWithTitle:@"Do not share" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self stopBusyMode];
}];
[alert addAction:doNotShareAction];
// Show the alert.
[self presentViewController:alert animated:YES completion:nil];
}
- (UIAlertAction *)shareImageActionWithTitle:(NSString *)title serviceType:(NSString *)serviceType image:(UIImage *)image {
UIAlertAction* action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:serviceType];
[composeViewController addImage:image];
[self presentViewController:composeViewController animated:YES completion:^{
[self stopBusyMode];
}];
}
return action;
}