2012年7月24日 星期二

Filter4Cam 實作: 22. 影像方向調整之三

since: 2012/07/24
update: 2012/07/24

reference:
1. The iOS 5 Developer's Cookbook, 3/e

轉換原始影像的方向

A. 說明:
     1. 將原本的 orientationTransform: 方法, 整個內容全部重寫. 在設備不同的
         擺放情形中, 增加前後鏡頭切換的判斷. 

     2. 調整當 "設備水平向右擺放" 時, 原本 "按鈕功能" 會靠近相機鏡頭位置,
         改成 "按鈕功能" 遠離相機鏡頭位置.

     3. 當 "設備面朝上",  "設備面朝下" 或 "設備方向未知" 時, 加入依據 "上次設備
         的擺放方向" 來作調整的處理.

     4. 在這裡, 同時使用了 CGAffineTransform(仿射轉換) 與 CATransform3D(3D 轉換)
         來作影像方向的調整. 原本希望能只用 CATransform3D 來處理就好, 但沒有測試
         成功, 因此整個程式碼看起來瑣碎而複雜.

    5. 調整的要領是: 先利用 "仿射轉換的旋轉角度" 搭配 "GLKView 作 3D 旋轉",
        來使得螢幕上呈現的影像為正向(包含: 上下, 左右), 接著再來調整 "濾鏡表單"
        與 "功能按鈕" 的呈現.

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

B. 開啟 ViewController.m 檔案, 修改如下:
....
// 轉換原始影像的方向
- (CIImage *)orientationTransform:(CIImage *)sourceImage
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    CGAffineTransform affineTransform = CGAffineTransformIdentity; // 仿射轉換
    CATransform3D transform3D = CATransform3DIdentity; // 3D 轉換
   
    // Debug: 由於在 shouldAutorotateToInterfaceOrientation:
    //               中無法偵測到這三個方向,

    //               & 當上一次設備的方向為此三種情況時, 不記錄
    if (orientation == UIDeviceOrientationFaceUp) {
        NSLog(@" -> FaceUp");
        //lastOrientation = FaceUp; // 記錄為上次: 面朝上
    }
    if (orientation == UIDeviceOrientationFaceDown) {
        NSLog(@" ---> FaceDown");
        //lastOrientation = FaceDown; // 記錄為上次: 面朝下
    }
    if (orientation == UIDeviceOrientationUnknown) {
        NSLog(@" -----> Unknown");
        //lastOrientation = Unknown; // 記錄為上次: 未知
    }
   
    // ############# //
    // A. 設備垂直擺放 //
    // ############## //
    if (orientation == UIDeviceOrientationPortrait)
    {        
        // 上次為前置鏡頭, 本次為前置鏡頭
        if (self.isLastFrontCamera && self.isUsingFrontCamera)
        {            
            // 將 GLKView, TableView 與 buttonView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
            self.filterListTableView.layer.transform = transform3D;
            self.buttonView.layer.transform = transform3D;
        }
        // 上次為後置鏡頭, 本次為後置鏡頭
        else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
        {                    
            // 將 GLKView 對 Z 軸作 180 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f);
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 180 度旋轉為基準, 將 GLKView 對 X 軸作 180 度旋轉
            transform3D = CATransform3DRotate(transform3D, M_PI, 1.0f, 0.0f, 0.0f);
            self.glView.layer.transform = transform3D;

            // 對 Z 軸作 180 度旋轉, 再對 X 軸作 180 度旋轉為基準,
            // 將 GLKView 對 Y 軸作 180 度旋轉
            self.glView.layer.transform = CATransform3DRotate(transform3D, M_PI, 0.0f, 1.0f, 0.0f);
           
            // 將 TableView 與 buttonView 轉成單位矩陣
            self.filterListTableView.layer.transform = CATransform3DIdentity;
            self.buttonView.layer.transform = CATransform3DIdentity;
        }
        // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
        else
        {
            self.isLastFrontCamera = !self.isLastFrontCamera;
        }
       
        // 仿射轉換, 並將輸入角度轉成弧度 (直接輸入 -M_PI /2 亦可)
        affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(-90.0));
    }
    // #################### //
    // B. 設備垂直 180 度擺放 //
    // #################### //
    else if (orientation == UIDeviceOrientationPortraitUpsideDown)
    {
        // 上次為前置鏡頭, 本次為前置鏡頭
        if (self.isLastFrontCamera && self.isUsingFrontCamera)
        {            
            // 將 GLKView, TableView 與 buttonView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
            self.filterListTableView.layer.transform = transform3D;
            self.buttonView.layer.transform = transform3D;
           
            // 將 GLKView 對 X 軸作 180 度旋轉
            self.glView.layer.transform = CATransform3DMakeRotation(M_PI, 1.0f, 0.0f, 0.0f);
        }
        // 上次為後置鏡頭, 本次為後置鏡頭
        else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
        {                  
            // 將 TableView 與 buttonView 轉成單位矩陣
            self.filterListTableView.layer.transform = CATransform3DIdentity;
            self.buttonView.layer.transform = CATransform3DIdentity;
           
            // 將 GLKView 對 Z 軸作 180 度旋轉
            self.glView.layer.transform = CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f);
        }
        // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
        else
        {
            self.isLastFrontCamera = !self.isLastFrontCamera;
        }
       
        affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(90.0));
    }
    // ################# //
    // C. 設備水平向右擺放 //
    // ################# //
    else if (orientation == UIDeviceOrientationLandscapeRight)
    {
        // 上次為前置鏡頭, 本次為前置鏡頭
        if (self.isLastFrontCamera && self.isUsingFrontCamera)
        {                  
            // "按鈕功能" 靠近相機鏡頭(不採用)
            /*
            // 將 GLKView 對 Z 軸作 90 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI/2, 0.0f, 0.0f, 1.0f);
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 90 度旋轉為基準, 將 GLKView 與 TableView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DRotate(transform3D, M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
            self.filterListTableView.layer.transform = transform3D;
           
            // 以單位矩陣為基準, 將 buttonView 對 X 軸作 180 度旋轉
            self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, M_PI, 1.0f, 0.0f, 0.0f);
            */
           
            // "按鈕功能" 遠離相機鏡頭(採用此方式)
            // a. 以單位矩陣為基準, 將 GLKView 對 Z 軸作 90 度旋轉
            transform3D = CATransform3DRotate(CATransform3DIdentity, M_PI/2, 0.0f, 0.0f, 1.0f);
            self.glView.layer.transform = transform3D;

            // 以 a. 為基準, 將 GLKView 對 X 軸作 180 度旋轉
            self.glView.layer.transform = CATransform3DRotate(transform3D, M_PI, 1.0f, 0.0f, 0.0f);
           
            // 以 a. 為基準, 將 TableView 對 X 軸作 180 度旋轉
            self.filterListTableView.layer.transform = CATransform3DRotate(transform3D, M_PI, 1.0f, 0.0f, 0.0f);

            // 以單位矩陣為基準, 將 buttonView 對 Y 軸作 180 度旋轉
            self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, M_PI, 0.0f, 1.0f, 0.0f);
        }
        // 上次為後置鏡頭, 本次為後置鏡頭
        else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
        {      
            // "按鈕功能" 靠近相機鏡頭 (不採用)
            /*
            // 將 TableView 對 Z 軸作 90 度旋轉
            transform3D = CATransform3DMakeRotation(M_PI/2, 0.0f, 0.0f, 1.0f);
            self.filterListTableView.layer.transform = transform3D;
           
            // 對 Z 軸作 90 度旋轉為基準, 將 GLKView 對 X 軸作 180 度旋轉
            transform3D = CATransform3DRotate(transform3D, M_PI, 1.0f, 0.0f, 0.0f);
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 90 度旋轉, 再對 X 軸作 180 度旋轉為基準,
            // 將 GLKView 對 Y 軸作 180 度旋轉
            transform3D = CATransform3DRotate(transform3D, M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
           
            // 以單位矩陣為基準, 將 buttonView 對 Z 軸作 90 度旋轉
            self.buttonView.layer.transform = CATransform3DRotate(transform3D, M_PI/2, 0.0f, 0.0f, 1.0f);
            */

            // "按鈕功能" 遠離相機鏡頭(採用此方式)
            // 以單位矩陣為基準, 將 GLKView 對 Z 軸作 90 度旋轉
            transform3D = CATransform3DRotate(CATransform3DIdentity, M_PI/2, 0.0f, 0.0f, 1.0f);
            self.glView.layer.transform = transform3D;
           
            // 以單位矩陣為基準, 將 TableView 對 Z 軸作 -90 度旋轉
            self.filterListTableView.layer.transform = CATransform3DRotate(CATransform3DIdentity, -M_PI/2, 0.0f, 0.0f, 1.0f);
           
            // 以單位矩陣為基準, 將 buttonView 對 Z 軸作 180 度旋轉
            self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, M_PI, 0.0f, 0.0f, 1.0f);
        }
        // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
        else
        {
            self.isLastFrontCamera = !self.isLastFrontCamera;
        }
       
        // "按鈕功能" 靠近相機鏡頭(不採用)
        //affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(180.0));
        // "按鈕功能" 遠離相機鏡頭(採用此方式)
        affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
    }
    // ################# //
    // D. 設備水平向左擺放 //
    // ################# //
    else if (orientation == UIDeviceOrientationLandscapeLeft)
    {  
        // 上次為前置鏡頭, 本次為前置鏡頭
        if (self.isLastFrontCamera && self.isUsingFrontCamera)
        {                       
            // 將 GLKView 對 Z 軸作 -90 度旋轉
            transform3D = CATransform3DMakeRotation(-M_PI/2, 0.0f, 0.0f, 1.0f); 
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 -90 度旋轉為基準, 將 GLKView 對 Y 軸作 -180 度旋轉
            transform3D = CATransform3DRotate(transform3D, -M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 -90 度旋轉, 再對 Y 軸作 -180 度旋轉為基準,
            // 將 TableView 對 Z 軸作 -180 度旋轉
            transform3D = CATransform3DRotate(transform3D, -M_PI, 0.0f, 0.0f, 1.0f);
            self.filterListTableView.layer.transform = transform3D;

            // 以單位矩陣為基準, 將 buttonView 對 X 軸作 -180 度旋轉
            self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, -M_PI, 1.0f, 0.0f, 0.0f);
        }
        // 上次為後置鏡頭, 本次為後置鏡頭
        else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
        {                         
            // 將 GLKView 對 Z 軸作 -90 度旋轉
            transform3D = CATransform3DMakeRotation(-M_PI/2, 0.0f, 0.0f, 1.0f);
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 -90 度旋轉為基準, 將 GLKView 對 X 軸作 -180 度旋轉
            transform3D = CATransform3DRotate(transform3D, -M_PI, 1.0f, 0.0f, 0.0f);
            self.glView.layer.transform = transform3D;
           
            // 對 Z 軸作 -90 度旋轉, 再對 X 軸作 -180 度旋轉為基準,
            // 將 GLKView 與 TableView 對 Y 軸作 -180 度旋轉
            transform3D = CATransform3DRotate(transform3D, -M_PI, 0.0f, 1.0f, 0.0f);
            self.glView.layer.transform = transform3D;
            self.filterListTableView.layer.transform = transform3D;
           
            // 將 buttonView 轉成單位矩陣
            self.buttonView.layer.transform = CATransform3DIdentity;
        }
        // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
        else
        {
            self.isLastFrontCamera = !self.isLastFrontCamera;
        }
       
        affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
    }
    // ###################### //
    // E. 設備面朝上或設備面朝下 //
    // ###################### //
    else if (orientation == UIDeviceOrientationFaceUp ||
                orientation == UIDeviceOrientationFaceDown)
    {
        ////////////////////////////////////////
        // case 1: 上次為直擺
        //
        if (lastOrientation == Portrait)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {
                // 將 GLKView 對 Z 軸作 -180 度旋轉
                transform3D = CATransform3DMakeRotation(-M_PI, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
               
                // 對 Z 軸作 -180 度旋轉為基準,
                // 將 GLKView , TableView 與 buttonView 對 X 軸作 -180 度旋轉
                transform3D = CATransform3DRotate(transform3D, -M_PI, 1.0f, 0.0f, 0.0f);
                self.glView.layer.transform = transform3D;
                self.filterListTableView.layer.transform = transform3D;
                self.buttonView.layer.transform = transform3D;
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {
                // 將 GLKView 對 Z 軸作 -180 度旋轉
                transform3D = CATransform3DMakeRotation(-M_PI, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
               
                // 對 Z 軸作 -180 度旋轉為基準, 將 GLKView 對 X 軸作 -180 度旋轉
                transform3D = CATransform3DRotate(transform3D, -M_PI, 1.0f, 0.0f, 0.0f);
                self.glView.layer.transform = transform3D;
               
                // 對 Z 軸作 -180 度旋轉, 再對 X 軸作 -180 度旋轉為基準,
                // 將 GLKView, TableView 與 buttonView 對 Y 軸作 -180 度旋轉
                transform3D = CATransform3DRotate(transform3D, -M_PI, 0.0f, 1.0f, 0.0f);
                self.glView.layer.transform = transform3D;
                self.filterListTableView.layer.transform = transform3D;
                self.buttonView.layer.transform = transform3D;
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(-90.0));
        }
       
        ////////////////////////////////////////
        // case 2: 上次為直擺且上下顛倒
        //
        else if (lastOrientation == PortraitUpsideDown)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {               
                // 將 GLKView 對 Z 軸作 -180 度旋轉
                transform3D = CATransform3DMakeRotation(-M_PI, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
               
                // 對 Z 軸作 -180 度旋轉為基準, 將 GLKView 對 Y 軸作 -180 度旋轉
                self.glView.layer.transform = CATransform3DRotate(transform3D, -M_PI, 0.0f, 1.0f, 0.0f);
               
                // 以單位矩陣為基準, 將 TableView 與 buttonView 對 Y 軸作 -180 度旋轉
                transform3D = CATransform3DRotate(CATransform3DIdentity, -M_PI, 0.0f, 1.0f, 0.0f);
                self.filterListTableView.layer.transform = transform3D;
                self.buttonView.layer.transform = transform3D;
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {               
                // 將 GLKView, TableView 與 buttonView 轉成單位矩陣
                self.glView.layer.transform = CATransform3DIdentity;
                self.filterListTableView.layer.transform = CATransform3DIdentity;
                self.buttonView.layer.transform = CATransform3DIdentity;
               
                // 將 GLKView 對 X 軸作 -180 度旋轉
                transform3D = CATransform3DMakeRotation(-M_PI, 1.0f, 0.0f, 0.0f);
                self.glView.layer.transform = transform3D;
               
                // 對 X 軸作 -180 度旋轉為基準,
                // 將 GLKView 對 Y 軸作 -180 度旋轉
                self.glView.layer.transform = CATransform3DRotate(transform3D, -M_PI, 0.0f, 1.0f, 0.0f);
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(90.0));
        }

        ////////////////////////////////////////
        // case 3: 上次為橫擺朝右
        //         (與 "設備水平向右擺放" 設定相同)
        else if (lastOrientation == LandscapeRight)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {      
                // a. 以單位矩陣為基準, 將 GLKView 對 Z 軸作 90 度旋轉
                transform3D = CATransform3DRotate(CATransform3DIdentity, M_PI/2, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
               
                // 以 a. 為基準, 將 GLKView 對 X 軸作 180 度旋轉
                self.glView.layer.transform = CATransform3DRotate(transform3D, M_PI, 1.0f, 0.0f, 0.0f);
               
                // 以 a. 為基準, 將 TableView 對 X 軸作 180 度旋轉
                self.filterListTableView.layer.transform = CATransform3DRotate(transform3D, M_PI, 1.0f, 0.0f, 0.0f);
               
                // 以單位矩陣為基準, 將 buttonView 對 Y 軸作 180 度旋轉
                self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, M_PI, 0.0f, 1.0f, 0.0f);
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {                        
                // 以單位矩陣為基準, 將 GLKView 對 Z 軸作 90 度旋轉
                transform3D = CATransform3DRotate(CATransform3DIdentity, M_PI/2, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
               
                // 以單位矩陣為基準, 將 TableView 對 Z 軸作 -90 度旋轉
                self.filterListTableView.layer.transform = CATransform3DRotate(CATransform3DIdentity, -M_PI/2, 0.0f, 0.0f, 1.0f);
               
                // 以單位矩陣為基準, 將 buttonView 對 Z 軸作 180 度旋轉
                self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, M_PI, 0.0f, 0.0f, 1.0f);
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
        }       
       
        ////////////////////////////////////////
        // case 4: 上次為橫擺朝左
        //
        else if (lastOrientation == LandscapeLeft)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {  
                // a. 以單位矩陣為基準, 將 GLKView 對 Z 軸作 90 度旋轉
                transform3D = CATransform3DRotate(CATransform3DIdentity, M_PI/2, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
               
                // 以 a. 為基準, 將 TableView 對 Y 軸作 180 度旋轉  
                self.filterListTableView.layer.transform = CATransform3DRotate(transform3D, M_PI, 0.0f, 1.0f, 0.0f);
               
                // 以 a. 為基準, 將 GLKView 對 X 軸作 -180 度旋轉
                self.glView.layer.transform = CATransform3DRotate(transform3D, -M_PI, 1.0f, 0.0f, 0.0f);
               
                // 以單位矩陣為基準, 將 buttonView 對 X 軸作 -180 度旋轉
                self.buttonView.layer.transform = CATransform3DRotate(CATransform3DIdentity, -M_PI, 1.0f, 0.0f, 0.0f);
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {        
                // 以單位矩陣為基準, 將 GLKView 與 TableView 對 Z 軸作 90 度旋轉
                transform3D = CATransform3DRotate(CATransform3DIdentity, M_PI/2, 0.0f, 0.0f, 1.0f);
                self.glView.layer.transform = transform3D;
                self.filterListTableView.layer.transform = transform3D;
               
                // 將 buttonView 轉成單位矩陣
                self.buttonView.layer.transform = CATransform3DIdentity;
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
        }
        else
        {
            NSLog(@"orientationTransform: orientation = FaceUp/FaceDown, lastOrientation not defined!");
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
        }
    }
    // ############## //
    // F. 設備方向未知 //
    // ############## //
    else if (orientation == UIDeviceOrientationUnknown)
    {
        ////////////////////////////////////////
        // case 1: 上次為直擺
        //
        if (lastOrientation == Portrait)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {
                // Do Nothing
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {
                // Do Nothing
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(-90.0));
        }
       
        ////////////////////////////////////////
        // case 2: 上次為直擺且上下顛倒
        //
        else if (lastOrientation == PortraitUpsideDown)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {               
                // Do Nothing
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            { 
                // Do Nothing
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(90.0));
        }
       
        ////////////////////////////////////////
        // case 3: 上次為橫擺朝右
        //         (與 "設備水平向右擺放" 設定相同)
        else if (lastOrientation == LandscapeRight)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {   
                // Do Nothing
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {     
                // Do Nothing
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
        }       
       
        ////////////////////////////////////////
        // case 4: 上次為橫擺朝左
        //
        else if (lastOrientation == LandscapeLeft)
        {
            // 上次為前置鏡頭, 本次為前置鏡頭
            if (self.isLastFrontCamera && self.isUsingFrontCamera)
            {  
                // Do Nothing
            }
            // 上次為後置鏡頭, 本次為後置鏡頭
            else if (!self.isLastFrontCamera && !self.isUsingFrontCamera)
            {      
                // Do Nothing
            }
            // "前置切換成後置鏡頭" 或 "後置切換成前置鏡頭"
            else
            {
                self.isLastFrontCamera = !self.isLastFrontCamera;
            }
           
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
        }
        else
        {
            NSLog(@"orientationTransform: orientation = Unknown, lastOrientation not defined!");
            affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
        }
    }
    else
    {
        NSLog(@"orientationTransform: No orientation match!");
        affineTransform = CGAffineTransformMakeRotation(DegreesToRadians(0.0));
    }
   
    return [sourceImage imageByApplyingTransform:affineTransform];
}
....

Filter4Cam 實作: 21. 影像方向調整之二

since: 2012/07/24
update: 2012/07/24

reference:
1. The iOS 5 Developer's Cookbook, 3/e

"記錄設備上次的擺放方向" 與 "調整按鈕位置"

A. 說明:
     1. 將移除不再使用的 "設備上一次方向是否為水平擺放" 之變數:
          isPreviousOrientationLandscape, 改成由 lastOrientation 來判斷.

     2. "記錄設備上次的擺放方向", 由於只需要記錄: 直擺, 直擺且上下顛倒, 橫擺朝右
          與橫擺朝左共 4 種情況, 因此會在 shouldAutorotateToInterfaceOrientation:
          方法中處理.   

     3. 在  shouldAutorotateToInterfaceOrientation: 方法中, 無法即時偵測到:
         設備面朝上, 面朝下與未知的情況. 因此會將這三種情況(FaceUp, FaceDown
         與 Unknown) 的偵測, 置於 orientationTransform: 方法內, 以協助在開發的
         過程中除錯. (只有當有套用濾鏡功能時, 才會去呼叫 drawFilteredImage: ,
         因此不放在此方法處理)  

     4. orientationTransform: 與 drawFilteredImage: 方法, 皆會被
         captureOutput:didOutputSampleBuffer:fromConnection: 頻繁地呼叫,
         因此除了螢幕繪製的相關內容之處理, 儘量不要在其中處理其它的事情.

    5. 調整按鈕的基本擺放位置, 將在 shouldAutorotateToInterfaceOrientation:
        方法中作適當處理.

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

B. 開啟 ViewController.h 檔案, 修改如下:
....
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate, UITableViewDelegate, UITableViewDataSource>
{
....
    BOOL isUsingFrontCamera; // 目前使用前置鏡頭?
    BOOL isLastFrontCamera; // 上次是前置鏡頭?
    //@add 設備上一次方向是否為水平擺放(向左或向右皆算)
    //BOOL isPreviousOrientationLandscape;
    BOOL applyFilter; // 是否套用濾鏡?
....
}
....
@property (assign) BOOL isUsingFrontCamera;
@property (assign) BOOL isLastFrontCamera;
//@property (assign) BOOL isPreviousOrientationLandscape;
@property (assign) BOOL applyFilter; 
 
....

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

C. 開啟 ViewController.m 檔案, 修改如下:
....
// step 01:
@synthesize isUsingFrontCamera = _isUsingFrontCamera;
@synthesize isLastFrontCamera = _isLastFrontCamera;
//@synthesize isPreviousOrientationLandscape = _isPreviousOrientationLandscape;
@synthesize applyFilter = _applyFilter;
....

// step02:
- (void)viewDidLoad
{
....
    /*
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    if (orientation == UIDeviceOrientationLandscapeRight ||
        orientation == UIDeviceOrientationLandscapeLeft) {
        self.isPreviousOrientationLandscape = YES;
    }
    else {
        self.isPreviousOrientationLandscape = NO;
    }
    */
....
}
....

// step 03:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    //@add
    GlobalDataClass *dataObj = [GlobalDataClass getInstance];
    CGFloat filterCellRotationAngle; // filterCell Rotation Angle
   
    /*
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
    */
   
    // ############# //
    // A. 設備垂直擺放 //
    // ############# //
    if (interfaceOrientation == UIDeviceOrientationPortrait)
    {
        lastOrientation = Portrait; // 記錄為上次: 直擺
        NSLog(@"lastOrientation = Portrait");
       
        //@update: Tune UI
        [self.filterLensImageView setFrame:CGRectMake(20, 120, 280, 280)];
       
        //@add for Button
        /*
        [self.saveButton setFrame:CGRectMake(10, 420, 66, 40)];
        [self.switchButton setFrame:CGRectMake(87, 420, 66, 40)];
        [self.torchButton setFrame:CGRectMake(164, 420, 66, 40)];
        [self.observerButton setFrame:CGRectMake(241, 420, 66, 40)];
        */
        //@update: 調整按鈕相對於 buttonView 的位置
        [self.buttonView setFrame:CGRectMake(0, 398, 320, 82)];
        [self.saveButton setFrame:CGRectMake(10, 22, 66, 40)];
        [self.switchButton setFrame:CGRectMake(87, 22, 66, 40)];
        [self.torchButton setFrame:CGRectMake(164, 22, 66, 40)];
        [self.observerButton setFrame:CGRectMake(241, 22, 66, 40)];
               
        //@update: Tune for Orientation
        self.filterListTableView.transform = CGAffineTransformMakeRotation(-M_PI * 2.0);
        [self.filterListTableView setFrame:CGRectMake(0, 0, self.view.frame.size.width, kOverlayTableViewRowHeight)];
       
        filterCellRotationAngle = M_PI * 0.5;
    }
    // #################### //
    // B. 設備垂直 180 度擺放 //
    // #################### //
    else if (interfaceOrientation == UIDeviceOrientationPortraitUpsideDown)
    {       
        lastOrientation = PortraitUpsideDown; // 記錄為上次: 直擺, 上下顛倒
        NSLog(@"lastOrientation = PortraitUpsideDown");
       
        //@update: Tune UI
        [self.filterLensImageView setFrame:CGRectMake(20, 120, 280, 280)];
       
        //@add for Button
        /*
        [self.saveButton setFrame:CGRectMake(10, 420, 66, 40)];
        [self.switchButton setFrame:CGRectMake(87, 420, 66, 40)];
        [self.torchButton setFrame:CGRectMake(164, 420, 66, 40)];
        [self.observerButton setFrame:CGRectMake(241, 420, 66, 40)];
        */
        //@update: 調整按鈕相對於 buttonView 的位置
        [self.buttonView setFrame:CGRectMake(0, 398, 320, 82)];
        [self.saveButton setFrame:CGRectMake(10, 22, 66, 40)];
        [self.switchButton setFrame:CGRectMake(87, 22, 66, 40)];
        [self.torchButton setFrame:CGRectMake(164, 22, 66, 40)];
        [self.observerButton setFrame:CGRectMake(241, 22, 66, 40)];
       
        //@update: Tune for Orientation
        self.filterListTableView.transform = CGAffineTransformMakeRotation(-M_PI * 2.0);
        [self.filterListTableView setFrame:CGRectMake(0, 0, self.view.frame.size.width, kOverlayTableViewRowHeight)];
       
        filterCellRotationAngle = M_PI * 0.5;
    }
    // ################# //
    // C. 設備水平向右擺放 //
    // ################# //
    else if (interfaceOrientation == UIDeviceOrientationLandscapeRight)
    {     
        lastOrientation = LandscapeRight; // 記錄為上次: 橫擺朝右
        NSLog(@"lastOrientation = LandscapeRight");
       
        //@update: Tune UI
        [self.filterLensImageView setFrame:CGRectMake(120, 20, 280, 280)];
       
        //@add for Button
        /*
        [self.saveButton setFrame:CGRectMake(406, 32, 66, 40)];
        [self.switchButton setFrame:CGRectMake(406, 104, 66, 40)];
        [self.torchButton setFrame:CGRectMake(406, 176, 66, 40)];
        [self.observerButton setFrame:CGRectMake(406, 248, 66, 40)];
        */
        //@update: 調整按鈕相對於 buttonView 的位置
        [self.buttonView setFrame:CGRectMake(398, 22, 82, 278)];
        [self.saveButton setFrame:CGRectMake(8, 10, 66, 40)];
        [self.switchButton setFrame:CGRectMake(8, 82, 66, 40)];
        [self.torchButton setFrame:CGRectMake(8, 154, 66, 40)];
        [self.observerButton setFrame:CGRectMake(8, 226, 66, 40)];
       
        //@update: Tune for Orientation
        self.filterListTableView.transform = CGAffineTransformMakeRotation(M_PI * 0.5);
        [self.filterListTableView setFrame:CGRectMake(0, 0, kOverlayTableViewRowHeight, self.view.frame.size.width)];
       
        filterCellRotationAngle = 0.0;
    }
    // ################# //
    // D. 設備水平向左擺放 //
    // ################# //
    else if (interfaceOrientation == UIDeviceOrientationLandscapeLeft)
    {  
        lastOrientation = LandscapeLeft; // 記錄為上次: 橫擺朝左
        NSLog(@"lastOrientation = LandscapeLeft");
       
        //@update: Tune UI
        [self.filterLensImageView setFrame:CGRectMake(120, 20, 280, 280)];
       
        //@add for Button
        /*
        [self.saveButton setFrame:CGRectMake(406, 32, 66, 40)];
        [self.switchButton setFrame:CGRectMake(406, 104, 66, 40)];
        [self.torchButton setFrame:CGRectMake(406, 176, 66, 40)];
        [self.observerButton setFrame:CGRectMake(406, 248, 66, 40)];
        */
        //@update: 調整按鈕相對於 buttonView 的位置
        [self.buttonView setFrame:CGRectMake(398, 22, 82, 278)];
        [self.saveButton setFrame:CGRectMake(8, 10, 66, 40)];
        [self.switchButton setFrame:CGRectMake(8, 82, 66, 40)];
        [self.torchButton setFrame:CGRectMake(8, 154, 66, 40)];
        [self.observerButton setFrame:CGRectMake(8, 226, 66, 40)];       
       
        //@update: Tune for Orientation
        self.filterListTableView.transform = CGAffineTransformMakeRotation(M_PI * 0.5);
        [self.filterListTableView setFrame:CGRectMake(0, 0, kOverlayTableViewRowHeight, self.view.frame.size.width)];
       
        filterCellRotationAngle = 0.0;
    }
    // ################################# //
    // E. 其它: FaceUp, FaceDown, Unknown //
    // ################################## //
    else
    {
        // 1. 在此, 不記錄上一次設備的方向
        // 2. 在此無法即時偵測到其它情況:
        //    UIDeviceOrientationFaceUp, UIDeviceOrientationFaceDown,
        //    UIDeviceOrientationUnknown
       
        //@update: Tune UI
        [self.filterLensImageView setFrame:CGRectMake(120, 20, 280, 280)];
       
        //@add for Button
        /*
        [self.saveButton setFrame:CGRectMake(406, 32, 66, 40)];
        [self.switchButton setFrame:CGRectMake(406, 104, 66, 40)];
        [self.torchButton setFrame:CGRectMake(406, 176, 66, 40)];
        [self.observerButton setFrame:CGRectMake(406, 248, 66, 40)];
        */
        //@update: 調整按鈕相對於 buttonView 的位置
        [self.buttonView setFrame:CGRectMake(398, 22, 82, 278)];
        [self.saveButton setFrame:CGRectMake(8, 10, 66, 40)];
        [self.switchButton setFrame:CGRectMake(8, 82, 66, 40)];
        [self.torchButton setFrame:CGRectMake(8, 154, 66, 40)];
        [self.observerButton setFrame:CGRectMake(8, 226, 66, 40)];
       
        //@update: Tune for Orientation
        self.filterListTableView.transform = CGAffineTransformMakeRotation(M_PI * 0.5);
        [self.filterListTableView setFrame:CGRectMake(0, 0, self.view.frame.size.width, kOverlayTableViewRowHeight)];
               
        filterCellRotationAngle = M_PI * 0.5;
    }
   
    //@add
    if (dataObj.filterCellArray.count > 0) {
        for (FilterCell* filterCell in dataObj.filterCellArray) {
            filterCell.transform = CGAffineTransformMakeRotation(filterCellRotationAngle);
        }
    }
   
    return YES;

}
....

// step04:
- (void)drawFilteredImage:(CIImage *)sourceImage
{
....
    // 設備垂直擺放
    if (orientation == UIDeviceOrientationPortrait)
    {
        //self.isPreviousOrientationLandscape = NO;
        ....
    }
    // 設備垂直 180 度擺放
    else if (orientation == UIDeviceOrientationPortraitUpsideDown)
   {
        //self.isPreviousOrientationLandscape = NO;
....
    }
    // 設備水平向右擺放
    else if (orientation == UIDeviceOrientationLandscapeRight)
    {
        //self.isPreviousOrientationLandscape = YES;
        ....
    }
    // 設備水平向左擺放(最穩定)
    else if (orientation == UIDeviceOrientationLandscapeLeft)
    {
        //self.isPreviousOrientationLandscape = YES;
....
    }
    // 其它 (UIDeviceOrientationFaceUp, UIDeviceOrientationFaceDown,
    //           UIDeviceOrientationUnknown)
    else
    {   
        // 上一次設備為水平擺放
        // if (self.isPreviousOrientationLandscape == YES)
        if (lastOrientation == LandscapeRight || lastOrientation == LandscapeLeft)
       {
           ....
        }
        //@add:上一次設備為垂直擺放
        else
       {
            ....
        }
    }
}

Filter4Cam 實作: 20. 影像方向調整之一

since: 2012/07/24
update: 2012/07/24

reference:
1. The iOS 5 Developer's Cookbook, 3/e

前置準備

A. 說明
     1. 如果使用圖像挑選器(image picker), 系統會自動處理跟方向有關的問題.
         在這個專案裡是使用 AVFoundation 來存取相機, 因此必須自行根據不同的
         情況(使用前置或後置相機, 裝置擺放方向) 來處理影像緩衝區裡的原始資料,
         以使影像的呈現保持正向.

     2. 這個專案沒有使用到  AVFoundation 的 AVCaptureVideoPreviewLayer 類別,
         它提供了即時圖像預覽的圖層, 可以將其嵌入到視圖裡.

     3. 由於整個調整的內容範圍較廣, 會等到這一系列的調整都完成後, 再進行編譯.

     4. 關於 "影像方向調整" 這部分, 使用了土法煉鋼的方式(費時費力),
         修改得有點複雜了, 僅供參考. (希望以後能有較好的解決方式)

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

B. 新增: radians(弧度) 與 degrees(角度) 轉換函式
     1. 開啟 Filter4CamHelper.h 檔案, 修改如下:
....
@end

// Convert between radians(弧度) and degrees(角度)
CGFloat DegreesToRadians(CGFloat degrees);
CGFloat RadiansToDegrees(CGFloat radians);

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

     2. 開啟 Filter4CamHelper.m 檔案, 修改如下:
....
@end

//@add: Convert between radians(弧度) and degrees(角度)
CGFloat DegreesToRadians(CGFloat degrees)
{
    return degrees * M_PI / 180;
};

CGFloat RadiansToDegrees(CGFloat radians)
{
    return radians * 180 / M_PI;
};

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

C. 定義: 儲存 "設備上次的擺放方向"
     開啟 ConstantDefined.h 檔案, 修改如下:
....
////////////////////////////////////////////////////////////////////
// 設備上次的擺放方向(目前不記錄:FaceUp, FaceDown, 與 Unknown)
//
//
enum kLastOrientation
{
    Portrait,           // 直擺
    PortraitUpsideDown, // 直擺, 上下顛倒
    LandscapeRight,     // 橫擺朝右
    LandscapeLeft,      // 橫擺朝左
    FaceUp,             // 面朝上
    FaceDown,           // 面朝下
    Unknown             // 未知
};

#endif

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

D. 修改函式名稱
     1. 說明:
         在 ViewController.m 中的
         captureOutput: didOutputSampleBuffer:fromConnection: 方法裡,
         呼叫了 filterOrientationTransform: 來將 "濾鏡範圍" 內的影像內容
         畫到 render buffer, 將其函式名稱改成: drawFilteredImage: 以符實情.

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

     2. 開啟 ViewController.h 檔案, 修改如下:
....
//@add for orientation Transform
- (CIImage *)orientationTransform:(CIImage *)sourceImage; // 轉換原始影像的方向
//- (void)filterOrientationTransform:(CIImage *)sourceImage;  // 方向轉變的調整
- (void)drawFilteredImage:(CIImage *)filteredImage; // 畫出 "濾鏡範圍" 內的影像(含方向轉變的調整)
....

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

     3. 開啟 ViewController.m 檔案, 修改如下:
....
// step 1:
//@add for filter Orientation Transform (方向轉變的調整)
//- (void)filterOrientationTransform:(CIImage *)sourceImage
// 畫出 "濾鏡範圍" 內的影像(含方向轉變的調整)
- (void)drawFilteredImage:(CIImage *)filteredImage
....

// step 2:
....
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
....
//[self filterOrientationTransform:ciImage];
// 畫出 "濾鏡範圍" 內的影像(含方向轉變的調整)
[self drawFilteredImage:self.ciImage];
....
}
....

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

E. 新增: 記錄上次是否為前置鏡頭之變數
    1. 開啟 ViewController.h 檔案, 修改如下:
....
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate, UITableViewDelegate, UITableViewDataSource>
{
....
    BOOL isUsingFrontCamera; // 目前使用前置鏡頭?
    BOOL isLastFrontCamera; // 上次是前置鏡頭?
....
    // 設備上次的擺放方向(目前不記錄:FaceUp, FaceDown, 與 Unknown)
    //
    // Portrait,           // 直擺
    // PortraitUpsideDown, // 直擺, 上下顛倒
    // LandscapeRight,     // 橫擺朝右
    // LandscapeLeft,      // 橫擺朝左
    // FaceUp,             // 面朝上
    // FaceDown,           // 面朝下
    // Unknown             // 未知
    enum kLastOrientation lastOrientation;
}
....
@property (assign) BOOL isUsingFrontCamera;
@property (assign) BOOL isLastFrontCamera;
....

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

    2. 開啟 ViewController.m 檔案, 修改如下:
....// step 1:
@synthesize isUsingFrontCamera = _isUsingFrontCamera;
@synthesize isLastFrontCamera = _isLastFrontCamera;
....

// step 2:
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    //@add
    self.applyFilter = NO;
    self.isUsingFrontCamera = NO; // 預設為後置鏡頭
    self.isLastFrontCamera = NO;  // 預設上次為後置鏡頭
    lastOrientation = Unknown; // 設備上次的擺放方向: 未知
....
}
....

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


F. 新增: 用來放所有按鈕的 View

    1.
開啟 ViewController.h 檔案, 修改如下:
....
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate, UITableViewDelegate, UITableViewDataSource>
{
....
    //@add for Camera Overlay UI
    UIImageView *filterLensImageView;   
    UITableView *filterListTableView;
    UIView *buttonView;       // 用來放所有按鈕的 View
    UIButton *saveButton;     // "拍照" 按鈕
    UIButton *switchButton;   // "鏡頭切換" 按鈕
    UIButton *torchButton;    // "閃光燈切換" 按鈕
    UIButton *observerButton; // "觀察者模式" 按鈕
....
}
....
//@add for Camera Overlay UI
@property (nonatomic, strong) UIImageView *filterLensImageView;
@property (nonatomic, strong) UITableView *filterListTableView;
@property (nonatomic, strong) UIView *buttonView;
@property (nonatomic, strong) UIButton *saveButton;
@property (nonatomic, strong) UIButton *switchButton;
@property (nonatomic, strong) UIButton *torchButton;
@property (nonatomic, strong) UIButton *observerButton;
....


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


    2.
開啟 ViewController.m 檔案, 修改如下:
....
// step01:
//@add for Camera Overlay UI
@synthesize filterLensImageView = _filterLensImageView;
@synthesize filterListTableView = _filterListTableView;
@synthesize buttonView = _buttonView;
@synthesize saveButton = _saveButton;
@synthesize switchButton = _switchButton;
@synthesize torchButton = _torchButton;
@synthesize observerButton = _observerButton;
....

// step02:
#pragma mark Getter
....
// 用來放所有按鈕的 View
- (UIView *)buttonView
{
    if (_buttonView == nil) {
        _buttonView = [[UIView alloc] init];
        _buttonView.backgroundColor = [UIColor redColor]; // 測試使用
    }
   
    return _buttonView;
}
....

// step03:
//@add for setting camera Overlay
- (void)cameraOverlay
{  
....
    /*
    [self.view addSubview:self.saveButton];
    [self.view addSubview:self.switchButton];
    [self.view addSubview:self.torchButton];
    [self.view addSubview:self.observerButton];
    */
    //@update: add buttonView  
    [self.view addSubview:self.buttonView];
    [self.buttonView addSubview:self.saveButton];
    [self.buttonView addSubview:self.switchButton];
    [self.buttonView addSubview:self.torchButton];
    [self.buttonView addSubview:self.observerButton];
....
}
....