2013年5月23日 星期四

宣告property的前綴關鍵字

atomic vs nonatomic primarily ensures that complete values are returned from synthesized getters and that complete values are written by synthesized setters.
readwrite vs readonly determines whether a synthesized property has a synthesized accessor or not (readwrite has a setter and is the default, readonly does not).
assign vs retain vs copy determines how the synthesized accessors interact with the Objective-C memory management scheme:
  • assign is the default and simply performs a variable assignment
  • retain specifies the new value should be sent -retain on assignment and the old value sent -release
  • copy specifies the new value should be sent -copy on assignment and the old value sent -release.

2013年5月8日 星期三

OpenGLES常用function的中文資料

以下是網路上的常用function的中文資料

//------glViewport(橫座標,縱座標,寬度,高度);
//
//想像OpenGL是一個畫家
//視窗就是它的畫框
//glViewport則是決定畫布的長寬大小
//畫布的size跟畫框不符是很糟糕的
//
//glViewport的前兩個參數是畫布左下角的視窗座標位置
//後兩個參數是畫布的size

--------------------------------------------------------------------------------

//------glMatrixMode(GL_PROJECTION);
//
//顧名思義是選擇矩陣模式的意思
//而且選的還是個投影矩陣
//
//之所以要扯到矩陣是因為3D繪圖是向量圖不是點陣圖
//畫面精緻的2D遊戲常常是點陣圖而不是向量圖(畫面不怎麼精緻的flash遊戲是向量圖)
//
//3D電腦圖學最重要的資料就是座標點了
//3D遊戲中的畫面都受到座標點的控制
//改變座標點就能改變整個三維世界
//
//所以矩陣是最適合3D向量繪圖的數學工具了
//大學中教矩陣的這門課是叫做線性代數,是代數學的分支(代數學對資料壓縮有所貢獻)
//
//而資料是立體的,螢幕卻是平面的
//有必要讓3維圖形化為2維平面
//那不就是投影矩陣了嗎?
//
//glMatrixMode(GL_PROJECTION)先選了投影矩陣模式
//這樣待會兒有個指令要放出一個矩陣給OpenGL才不會放錯地方
//(OpenGL還有另外兩種矩陣模式,總共有三個矩陣堆疊供三種矩陣模式使用)

--------------------------------------------------------------------------------

//------glLoadIdentity();
//
//glLoadIdentity是將OpenGL中的矩陣設為單位矩陣
//所謂的單位矩陣是矩陣乘法下的單位元素
//任何矩陣乘上單位矩陣都還是等於自己
//
//我不想提太多數學,這樣我會沒有放暑假的感覺
//總之glLoadIdentity()的作用是不希望之前的矩陣資料殘留到現在的運算
//如同遊戲畫面一樣,要不斷的清理,不能讓之前的遊戲畫面殘留

--------------------------------------------------------------------------------

//------glOrtho(左邊界,右邊界,下邊界,上邊界,近邊界,遠邊界);
//
//這個指令給了OpenGL一個正交投影矩陣
//正交是垂直的代名詞,一個垂直的投影如同陽光底下的影子一樣
//物體跟地面的距離不會影響大小,適合2D遊戲使用
//
//對於glOrtho()的參數
//你可以想像將一個箱子放到3維座標系的原點
//它左右邊的面被x軸貫穿,交了兩個點
//x值就是左右邊界
//箱子的六個面都交了一點
//六個點六個邊界,定義了箱子的範圍
//將箱子前後壓扁就是視窗上的樣子啦

--------------------------------------------------------------------------------

//------glMatrixMode(GL_MODELVIEW);
//
//把矩陣模式改成MODELVIEW(不知道該怎麼翻)
//在Display函式中還會再確認一次
//OpenGL的矩陣模式要盡量處於MODELVIEW模式
//glMatrixMode(GL_MODELVIEW)下一行的glLoadIdentity()這時清理的是MODELVIEW矩陣


//void Keyboard(unsigned char , int, int );
//
//第一個參數得到你輸入的鍵碼
//後兩個參數是你按按鍵時,滑鼠的座標(是視窗座標不是螢幕座標)


以後會寫switch條件判斷式來依據不同的按鍵來執行各自的功能
長的像這樣



代碼:
void Keyboard(unsigned char key, int x, int y)
{
   switch (key)
   {
   case 'a':
      執行按下a之後對應的指令;
      break;
   case 'b':
      執行按下b之後對應的指令;
      break;
   case 'c':
      執行按下c之後對應的指令;
      break;
   }
}



//================================================================================
//Display:
//這裡是OpenGL的舞台
//其中有些指令其實對於這個code並沒有用處
//但是到後面還是鐵定會用到
//所以在第一篇的code裡就先寫入了
//================================================================================


//------glClearColor( 紅 , 綠 , 藍 , 透明色 );
//
//設定用來清理顏色緩衝區的顏色,避免畫面殘留
//只是設定而已,還沒動手清理
//參數值全是介於0到1之間的浮點數

--------------------------------------------------------------------------------

//------glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
//
//真正動手清理緩衝區
//GL_COLOR_BUFFER_BIT指定要清理顏色緩衝區
//GL_DEPTH_BUFFER_BIT指定要清理深度緩衝區(32位元)
//
//深度緩衝區是3D畫面會用到的
//用來做深度測試,目的是讓近的物體能擋到遠方的物體,這樣才符合現實
//不過這個程式還沒啟動深度測試

--------------------------------------------------------------------------------

//------gluLookAt( x1 , y1 , z1 , x2 , y2 , z2 , x3 , y3 , z3 );
//
//這是個glu函式,是用來幫助gl函式的
//glu的功能其實用gl就寫的出來,
//不過glu改良後的用法較方便且直觀
//
//這個函式像個攝影機一樣
//可以調整攝影機的位置、調整攝影機的方向
//
//gluLookAt有9個參數,好像代表3個座標點
//其實是兩個座標點和一個向量
//第一個座標是攝影機的位置座標
//第二個座標是攝影機所要拍攝的物體位置座標,只是要確定拍攝方向
//第三個座標是攝影機正上方的向量,不懂要這幹什麼嗎?
//
//OpenGL只知道攝影機的位置、攝影機的拍攝方向還不夠啊
//誰知道你要躺著拍還是倒立著拍

--------------------------------------------------------------------------------

//------glBegin(GL_TRIANGLES); & glEnd();
//
//告訴OpenGL要動筆畫圖了,使用了glBegin之後開始繪圖指令
//畫完之後要打上glEnd();結束繪圖
//開啟了什麼就要關掉什麼,這點在DirectX還有很多API上面也是一樣的
//
//GL_TRIANGLES選擇你要畫的是三角形
//除了三角形還有GL_POINTS、GL_LINES和GL_QUADS(四邊形)以及更特別的可以選(但不重要)

--------------------------------------------------------------------------------

//------glColor3f( 紅 , 綠 , 藍 ); & glVertex3f( x , y , z );
//
//glColor3f的3個顏色值都介於0到1之間
//glVertex3f設定座標點,它的顏色取決於它前面的glColor3f
//由於前面的glBegin選擇要畫三角形
//所以在glBegin()和glEnd之間給出3個座標點可以定義一個三角形
//給出6個座標點可以定義兩個三角形
//給4個或5個會怎樣我不知道,你可以try看看

--------------------------------------------------------------------------------

//------glutSwapBuffers();
//
//當你使用雙緩衝區時,可以說是提供了兩張畫布供OpenGL作畫
//已經畫好的那一張先貼上視窗,另外一張正在OpenGL的手上趕工
//
//如果用單緩衝區的話,就要等OpenGL趕稿了
//稿子完成了貼上視窗,趕稿期間畫面被glClear清乾淨了,只剩背景色
//完稿了又貼上畫面,趕著畫下一張,畫面又被glClear清乾淨了
//如此循環下去,即使電腦的手腳很快,畫面還是難免有閃爍感(不舒服)
//
//所以全世界的遊戲都會採用雙緩衝區這個方法
//
//glutSwapBuffers()所做的就是在OpenGL繪畫完成之後趕緊變更顯示的緩衝區
//將新的畫作呈現在螢幕上,然後OpenGL在另一個緩衝區繼續趕稿
//畫好了之後glutSwapBuffers()又變更顯示的緩衝區
//將最新的畫作呈現在螢幕上,舊畫面被glClear清理了,以供OpenGL再次趕稿
//視窗上永遠有一張圖來撐場面,於是就沒有閃爍感了

2013年5月7日 星期二

UIActionSheetDelegate event handlers選單按鈕的事件要用delegate來實現

// UIActionSheetDelegate event handlers


- (void) actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    [self mainOptionClickedButtonAtIndex:buttonIndex];
}


- (void) mainOptionClickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (flashIx == buttonIndex)
    {
        BOOL newFlashMode = ![qUtils cameraTorchOn];
        [qUtils cameraSetTorchMode:newFlashMode];
    }
    else if (selectTargetIx == buttonIndex)
    {
        selectedTarget = (selectedTarget + 1) % [qUtils.targetsList count];       
        DataSetItem *targetEntry = [qUtils.targetsList objectAtIndex:selectedTarget];
        [qUtils activateDataSet:targetEntry.dataSet];
    }
    else if (autofocusContIx == buttonIndex)
    {
        // toggle camera continuous autofocus mode
        BOOL newContAFMode = ![qUtils cameraContinuousAFOn];
        [qUtils cameraSetContinuousAFMode:newContAFMode];
    }
    else if (autofocusSingleIx == buttonIndex)
    {
        [qUtils cameraPerformAF];
    }
       
    self.view.userInteractionEnabled = NO;
 }


2013年4月30日 星期二

前後鏡頭切換

轉自http://itouchs.blogspot.tw/2012/07/filter4cam-19_23.html


1. The iOS 5 Developer's Cookbook, 3/e
2. iphone - CATransform3D vs. CGAffineTransform? - Stack Overflow

A. 開啟 ViewController.m 檔案, 修改如下:

....
// step 1:
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    //@add
    self.applyFilter = NO;
    self.isUsingFrontCamera = NO;
....
}
....

// step 2:
//@add:建立相機 Session
- (void)establishCamera:(uint)whichCamera
{
....
    // Choose camera
    /*
    self.isUsingFrontCamera = NO;
    if ((whichCamera == kCameraFront) && [Filter4CamHelper frontCameraAvailable])
    {
        self.isUsingFrontCamera = YES;
    }
    */
    //@update
    if ((whichCamera == kCameraFront) && [Filter4CamHelper frontCameraAvailable])
    {
        self.isUsingFrontCamera = YES;
    }
    else
    {

        self.isUsingFrontCamera = NO;
    }
....
}
....

// step 3:
//@add: "鏡頭切換"
- (void)switchCameras
{
    if (![Filter4CamHelper numberOfCameras] > 1) return;
   
    self.isUsingFrontCamera = !self.isUsingFrontCamera;

    if ((self.isUsingFrontCamera == YES) && ![Filter4CamHelper frontCameraAvailable]) {
        self.isUsingFrontCamera = NO;
    }
   
    AVCaptureDevice *newDevice = self.isUsingFrontCamera ? [Filter4CamHelper frontCamera] : [Filter4CamHelper backCamera];
   
    [self.session beginConfiguration];
   
    // Remove existing inputs
    for (AVCaptureInput *input in [self.session inputs])
        [self.session removeInput:input];
   
    // Change the input
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:newDevice error:nil];

    [self.session addInput:captureInput];
   
    [self.session commitConfiguration];
}
....

編譯並執行:
預設後置鏡頭:


切換到前置鏡頭之結果:
1. 當設備垂直擺放: 物體左右相反.
    (說明: "麥斯威爾" 應該要從右往左排列)
2. 當設備水平擺放: 物體上下顛倒.
------------------------------------------------------------------------------------------

B. 有關 "前置鏡頭": 左右相反與上下顛倒之問題:
     1. 說明:
         a. 方向轉變的調整, 在 ViewController.m 檔案的
             captureOutput:didOutputSampleBuffer:fromConnection: 方法中,
             (1). 先呼叫了: orientationTransform: 方法, 對整個螢幕作方向調整;
             (2). 後來再呼叫: filterOrientationTransform: 方法, 只對套用濾鏡
                    顯示範圍作方向調整.

         b. 由於目前的問題, 是使用前置鏡頭時, 整個螢幕產生左右相反(垂直擺放)
             與上下顛倒(水平擺放)之問題, 因此要對 orientationTransform: 方法作調整.

         c. 先看看之前在 orientationTransform: 方法中, 對方向轉變的調整:
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    CGAffineTransform affineTransform; // 仿射轉換
    // 設備垂直擺放
    if (orientation == UIDeviceOrientationPortrait)
    {
        affineTransform = CGAffineTransformMakeRotation(-M_PI / 2);
    }
....
    return [sourceImage imageByApplyingTransform:affineTransform];
   
    備註: 在這邊使用了 CGAffineTransform(仿射轉換) 來處理方向的問題,
              但是如果要處理左右相反的問題, 就需要使用 CATransform3D.

         d. CGAffineTransform 與 CATransform3D 的差異:
             (1). CGAffineTransform線性的 2D 轉換, 適用於 NSViews, UIViews
                    與其它 2D 的 Core Graphics 基礎上.

             (2). CATransform3D 是架構在 Core Animation 上的三維投影轉換, 適用於
                    CALayers 上. CATransform3D 有與 OpenGLmodel view 矩陣相同的
                    內部結構, 這是因為 Core Animation 是建立在 OpenGL 的基礎上.
                    (例如: CALayers 是由 OpenGL 的 textures 包裹而成的)   

 **********************************************************

     2. 測試: "設備垂直擺放", 開啟 ViewController.m 檔案, 修改如下:
....
//@add for orientation Transform(方向轉變的調整)
- (CIImage *)orientationTransform:(CIImage *)sourceImage
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    CGAffineTransform affineTransform; // 仿射轉換
    CATransform3D transform3D = CATransform3DIdentity; // 3D 轉換
   
    // 設備垂直擺放
    if (orientation == UIDeviceOrientationPortrait)
    {
        //@add for 3D Transform
        if (self.isUsingFrontCamera) {
            // 將整個 GLKView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
        }
 
        affineTransform = CGAffineTransformMakeRotation(-M_PI / 2);
    }
    ....
       return [sourceImage imageByApplyingTransform:affineTransform];
   }
....

編譯並執行:
設備垂直擺放, 切換到前置鏡頭:
a. "麥斯威爾" 從右往左排列是正確的.
b. 變成左右相反的有: TableView(Cell位置與文字), 4 個 button(排列位置與文字)
     3. 測試: 修正 TableView 與 button 的問題, 修改如下:
....

//@add for orientation Transform(方向轉變的調整)
- (CIImage *)orientationTransform:(CIImage *)sourceImage
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    CGAffineTransform affineTransform; // 仿射轉換
    CATransform3D transform3D = CATransform3DIdentity; // 3D 轉換
   
    // 設備垂直擺放
    if (orientation == UIDeviceOrientationPortrait)
    {
        //@add for 3D Transform
        if (self.isUsingFrontCamera) {
            // 將整個 GLKView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
            self.filterListTableView.layer.transform = transform3D;
            self.saveButton.layer.transform = transform3D;
            self.switchButton.layer.transform = transform3D;
            self.torchButton.layer.transform = transform3D;
            self.observerButton.layer.transform = transform3D;
        }
 
        affineTransform = CGAffineTransformMakeRotation(-M_PI / 2);
    }
    ....
       return [sourceImage imageByApplyingTransform:affineTransform];
   }
....

編譯並執行:
設備垂直擺放, 切換到前置鏡頭:
a. TableView(Cell位置與文字)已正確顯示了.
b. 4 個 button 的文字顯示正確.
c. 4 個 button 的排列位置, 從左到右應該是: Save, Switch, Torch, Observe.
     4. 測試: 修正 button 的排列位置, 修改如下:
....
//@add for orientation Transform(方向轉變的調整)
- (CIImage *)orientationTransform:(CIImage *)sourceImage
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    CGAffineTransform affineTransform; // 仿射轉換
    CATransform3D transform3D = CATransform3DIdentity; // 3D 轉換
   
    // 設備垂直擺放
    if (orientation == UIDeviceOrientationPortrait)
    {
        //@add for 3D Transform
        if (self.isUsingFrontCamera) {
            // 將整個 GLKView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
            self.filterListTableView.layer.transform = transform3D;
            self.saveButton.layer.transform = transform3D;
            self.switchButton.layer.transform = transform3D;
            self.torchButton.layer.transform = transform3D;
            self.observerButton.layer.transform = transform3D;

            //@update for Button position            
            [self.saveButton setFrame:CGRectMake(241, 420, 66, 40)];
            [self.switchButton setFrame:CGRectMake(164, 420, 66, 40)];
            [self.torchButton setFrame:CGRectMake(87, 420, 66, 40)];
            [self.observerButton setFrame:CGRectMake(10, 420, 66, 40)];
        }
 
        affineTransform = CGAffineTransformMakeRotation(-M_PI / 2);
    }
    ....
       return [sourceImage imageByApplyingTransform:affineTransform];
   }
....

編譯並執行:
設備垂直擺放, 切換到前置鏡頭: 4 個 button 的排列位置已正確了.

備註: 在之後的修正實作時, 會將 4 個 button 加到一個 UIView 之下,
           再調整此 UIView 的方向與位置.

2013年4月24日 星期三

合成二張圖片

+ (UIImage*)mergeImage:(UIImage*)first withImage:(UIImage*)second
{
    // get size of the first image
    CGImageRef firstImageRef = first.CGImage;
    CGFloat firstWidth = CGImageGetWidth(firstImageRef);
    CGFloat firstHeight = CGImageGetHeight(firstImageRef);
   
    // get size of the second image
    CGImageRef secondImageRef = second.CGImage;
    CGFloat secondWidth = CGImageGetWidth(secondImageRef);
    CGFloat secondHeight = CGImageGetHeight(secondImageRef);
   
    // build merged size
    CGSize mergedSize = CGSizeMake(MAX(firstWidth, secondWidth), MAX(firstHeight, secondHeight));
   
    // capture image context ref
    UIGraphicsBeginImageContext(mergedSize);
   
    //Draw images onto the context
    [first drawInRect:CGRectMake(0, 0, firstWidth, firstHeight)];
    [second drawInRect:CGRectMake(0, 0, secondWidth, secondHeight)];
   
    // assign context to new UIImage
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
   
    // end context
    UIGraphicsEndImageContext();
   
    return newImage;
}

直接存取攝影機

轉錄自http://furnacedigital.blogspot.tw/2012/11/avcapturestillimageoutput.html#more(作者超強,內有更多好文!)

 

透過 AVCaptureStillImageOutput 做靜態影像的擷取

 

在 iOS 6 SDK(iOS 5 SDK 以上)中捕捉攝影機的靜態拍攝畫面,製作類似「錄影同時拍照的效果」可以透果很多方式,像是透過 UIImagePickerController 的方式,呼叫 iOS SDK 所提供的 API 來捕捉畫面,或是透過 AVFoundation 的 方式,建立影像的 AVCaptureSession,並且設定對應的 Input 與 Output。而本篇文章所採用的方法屬於後者,我們使用 AVCaptureStillImageOutput 來當做 AVCaptureSession 的 Output 端,輸出靜態影像。

在開始之前請先替您的專案加上 AVFoundation.framework,並且在對應的類別中引用此標頭檔。替專案加入 Framework 的方法請參考Xcode 4 新增 Framework 的方法一文。


建立 AVCaptureSession
在這部份中我們要在類別的 @interface 區段中宣告一個 AVCaptureSession 型態的全域變數,方便之後的作業。
1
AVCaptureSession *myCaptureSession;

在建立 AVCaptureSession 的部份,其實並沒有太多的設定,唯一要注意的是 setSessionPreset: 這個方法函式,它決定了攝影機捕獲影像的解析度大小,你可以使用 AVCaptureSessionPreset 的關鍵字來查閱各種解析度的定義。
1
2
3
//建立 AVCaptureSession
myCaptureSession = [[AVCaptureSession alloc] init];
[myCaptureSession setSessionPreset:AVCaptureSessionPresetPhoto];


建立 AVCaptureDeviceInput
在建立好 AVCaptureSession 之後,接下來我們要對他的輸入端做設定,你可以透過下列程式碼來取得裝置上具有輸入特性的硬體裝置和他們實際的裝置名稱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//建立 AVCaptureDeviceInput
NSArray *myDevices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in myDevices) {
    if ([device position] == AVCaptureDevicePositionBack) {
        NSLog(@"後攝影機硬體名稱: %@", [device localizedName]);
    }
    if ([device position] == AVCaptureDevicePositionFront) {
        NSLog(@"前攝影機硬體名稱: %@", [device localizedName]);
    }
    if ([device hasMediaType:AVMediaTypeAudio]) {
        NSLog(@"麥克風硬體名稱: %@", [device localizedName]);
    }
}

裝置上具有輸入特性的硬體名稱

下列我們就以後置鏡頭為例,製作 AVCaptureSession 的輸入端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用後置鏡頭當做輸入
NSError *error = nil;
for (AVCaptureDevice *device in myDevices) {
    if ([device position] == AVCaptureDevicePositionBack) {
        AVCaptureDeviceInput *myDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
        if (error) {
            //裝置取得失敗時的處理常式
        } else {
            [myCaptureSession addInput:myDeviceInput];
        }
    }
}


建立 AVCaptureVideoPreviewLayer
在建立 AVCaptureSession 與設定對應的 Input 之後,我們就可以透過 AVCaptureVideoPreviewLayer 來取得攝影機所捕捉的連續畫面,在 此,AVCaptureVideoPreviewLayer 所呈現的畫面是連續的,並非單張的靜態影像,當然你也可以略過設定 AVCaptureVideoPreviewLayer 的步驟,不顯示攝影機所拍攝到的畫面,這並不會有任何影響。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//建立 AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer *myPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:myCaptureSession];
[myPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
CGRect rect = CGRectMake(160, 180, 320, 240);
[myPreviewLayer setBounds:rect];
UIView *myView = [[UIView alloc]initWithFrame:rect];
[myView.layer addSublayer:myPreviewLayer];
[self.view addSubview:myView];
//啟用攝影機
[myCaptureSession startRunning];

在上述程式碼中,我們透過 AVCaptureSession 來建立 AVCaptureVideoPreviewLayer,並且設定它的 VideoGravity 屬性為 AVLayerVideoGravityResizeAspectFill,表示所取得的影像必須以等比例的方式縮放來填滿我們所指定的大小 (CGRect rect)。

到目前為止,我們還並未對 AVCaptureSession 做 Output 的設定,但是已經可以透過 AVCaptureVideoPreviewLayer 在畫面上看到攝影機所拍攝的影像。下面,我們將針對靜態影像的擷取來製作對應的 Output。


建立 AVCaptureStillImageOutput
要使用單張靜態影像的擷取,就可以考慮使用 AVCaptureStillImageOutput 來製作你的 Output 端,否則最好是使用 AVCaptureVideoDataOutput 來當做輸出端,這兩個不同類型的 Output 可以做的事情也不盡相同,關於 AVCaptureVideoDataOutput 可以參考透過 AVCaptureVideoDataOutput 做連續影像片段的擷取一文,來獲得更多資訊,下面我們只針對 AVCaptureStillImageOutput 做說明。

在這部份中我們要在類別的 @interface 區段中宣告一個 AVCaptureStillImageOutput 型態的全域變數,方便之後的作業。
1
AVCaptureStillImageOutput *myStillImageOutput;

接著,在根據以下程式碼對 AVCaptureStillImageOutput 做設定,並將此 Output 與 AVCaptureSession 做連接。
1
2
3
4
5
6
//建立 AVCaptureStillImageOutput
myStillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *myOutputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];
[myStillImageOutput setOutputSettings:myOutputSettings];
[myCaptureSession addOutput:myStillImageOutput];

在設定 AVCaptureStillImageOutput 上,我們將所擷取到的影像做 JPEG 的編碼以節省空間,雖然說使用 AVVideoCodec 這個關鍵字時,你可以看到 AVVideoCodecH264 的選項,但是他並不是靜態影像的編碼方式,而是使用在 Video 上的一種壓縮編碼,所以無法使用。


擷取單張靜態影像
在成功建立好 AVCaptureSession 的 Output 之後,接下來就是製作靜態影像擷取,你可以透過下列的方法函式,來與拍照的按鈕做互動,取得靜態影像。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
AVCaptureConnection *myVideoConnection = nil;
//從 AVCaptureStillImageOutput 中取得正確類型的 AVCaptureConnection
for (AVCaptureConnection *connection in myStillImageOutput.connections) {
    for (AVCaptureInputPort *port in [connection inputPorts]) {
        if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
            myVideoConnection = connection;
            break;
        }
    }
}
//擷取影像(包含拍照音效)
[myStillImageOutput captureStillImageAsynchronouslyFromConnection:myVideoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
    //完成擷取時的處理常式(Block)
    if (imageDataSampleBuffer) {
        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
         //取得的靜態影像
         UIImage *myImage = [[UIImage alloc] initWithData:imageData];
         [self setImage:myImage];
         //取得影像資料(需要ImageIO.framework 與 CoreMedia.framework)
         CFDictionaryRef myAttachments = CMGetAttachment(imageDataSampleBuffer, kCGImagePropertyExifDictionary, NULL);
         NSLog(@"影像屬性: %@", myAttachments);
    }
}];

在上述程式碼中,我們必須先從 AVCaptureStillImageOutput 中取得正確類型的 AVCaptureConnection,接著,才利用此 AVCaptureConnection 做影像的擷取,另外,如果你希望改變擷取影像時的方向,也可以對 AVCaptureConnection 做 setVideoOrientation: 旋轉影像,或 setVideoMirrored: 鏡射影像。

另外,在擷影像時會包含拍照的音效,無法消除,這是基於 iOS SDK 安全條款的使用原則,如果你是想製作每秒擷取 N 張靜態影像的話,最好還是使用 AVCaptureVideoDataOutput 來當做輸出,避免產生連續的拍照音效。

最後,我們也可以使用 CMGetAttachment 來取得該影像的其他屬性,但是這必須使用 ImageIO 與 CoreMedia 兩個 Framework 才行。

使用 CMGetAttachment 所取的的影像屬性

調整self.view 的subview前後位置

將一個UIView顯示在最前面只需要調用其父視圖的 bringSubviewToFront()方法。
將一個UIView推送到背後只需要調用其父視圖的 sendSubviewToBack()方法。