2012年8月3日 星期五

Filter4Cam 實作: 26. 選擇濾鏡之一

since: 2012/08/03
update: 2012/08/08


點選/取消 濾鏡

A. 新增: 記錄使用何種濾鏡的變數
      1. 開啟 GlobalDataClass.h 檔案, 修改如下:
#import <Foundation/Foundation.h>

@interface GlobalDataClass : NSObject
{   
    //@add: array for storage filterCells
    NSMutableArray *filterCellArray;
    BOOL isUsingFilter;        // 有使用濾鏡?
    NSMutableString *filterID; // 濾鏡 ID
}

//@add
@property (nonatomic, strong) NSMutableArray *filterCellArray;
@property (assign) BOOL isUsingFilter;
@property (nonatomic, strong) NSMutableString *filterID;

//@add
+ (GlobalDataClass *)getInstance;

@end

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

      2. 開啟 GlobalDataClass.m 檔案, 修改如下:
#import "GlobalDataClass.h"

@implementation GlobalDataClass

//@add
@synthesize filterCellArray = _filterCellArray;
@synthesize isUsingFilter = _isUsingFilter;
@synthesize filterID = _filterID;

#pragma mark Getter

//@add
- (NSMutableArray *)filterCellArray
{
    if (_filterCellArray == nil) {
        _filterCellArray = [[NSMutableArray alloc] init];
    }
   
    return _filterCellArray;
}

//@add
- (NSMutableString *)filterID
{
    if (_filterID == nil) {
        _filterID = [NSMutableString stringWithString:@"NoID"];
    }
    return _filterID;
}
....

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

B. 在濾鏡表單的 Cell 新增 filterID 變數
      1. 開啟 FilterCell.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import "ConstantDefined.h"
#import "FilterTitleLabel.h"

//@add
@class FilterTitleLabel;

@interface FilterCell : UITableViewCell
{
    //@add
    // hold the sample picture for our filter
    UIImageView *sampleImage; // 濾鏡效果的圖例
   
    // the title of our filter
    //UILabel *titleLabel;
    //@update
    FilterTitleLabel *titleLabel;
   
    //@add
    NSString *filterID;
}

//@add
@property (nonatomic, strong) UIImageView *sampleImage;
//@property (nonatomic, strong) UILabel *titleLabel;
//@update
@property (nonatomic, strong) FilterTitleLabel *titleLabel;
@property (nonatomic, strong) NSString *filterID;

@end

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

      2. 開啟 FilterCell.m 檔案, 修改如下:
....
//@add
@synthesize sampleImage = _sampleImage;
@synthesize titleLabel = _titleLabel;
@synthesize filterID = _filterID;

....
//@add
- (void)dealloc
{
    self.sampleImage = nil;
    self.titleLabel = nil;
    self.filterID = nil;
}
....

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

C. 決定是否要在螢幕上畫出套用濾鏡的影像
     (並調整 GlobalDataClass 類別變數)
     1. 開啟 ViewController.h 檔案, 修改如下:
....
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate, UITableViewDelegate, UITableViewDataSource>
{
....
    BOOL isUsingFrontCamera; // 目前使用前置鏡頭?
    BOOL isLastFrontCamera; // 上次是前置鏡頭?
    //@update: comment it
    //BOOL applyFilter; // 是否套用濾鏡?  // (update: 2012/08/08)

....
    // 設備上次的擺放方向(目前不記錄:FaceUp, FaceDown, 與 Unknown)
    //
    // Portrait,           // 直擺
    // PortraitUpsideDown, // 直擺, 上下顛倒
    // LandscapeRight,     // 橫擺朝右
    // LandscapeLeft,      // 橫擺朝左
    // FaceUp,             // 面朝上
    // FaceDown,           // 面朝下
    // Unknown             // 未知
    enum kLastOrientation lastOrientation;
   
    GlobalDataClass *dataObj;
}
....
@property (assign) BOOL isUsingFrontCamera;
@property (assign) BOOL isLastFrontCamera;
//@update: comment it
//@property (assign) BOOL applyFilter;
// (update: 2012/08/08)
....
//@add for reusable Cells
@property (nonatomic, strong) NSMutableArray *reusableCells;

@property (nonatomic, strong) GlobalDataClass *dataObj;
....

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

     2. 開啟 ViewController.m 檔案, 修改如下:
....
// step 1:
....
@synthesize isUsingFrontCamera = _isUsingFrontCamera;
@synthesize isLastFrontCamera = _isLastFrontCamera;
//@update: comment it
//@synthesize applyFilter = _applyFilter;
// (update: 2012/08/08)
....
//@add for reusable Cells

@synthesize reusableCells = _reusableCells;

@synthesize dataObj = _dataObj;

....
// step 2:
#pragma mark Getter
....
//@add
- (GlobalDataClass *)dataObj
{
    return [GlobalDataClass getInstance];
}

// step 3:
....
//@add:filter Test
- (CIImage *)filterTest:(CIImage *)sourceImage
{
    //@update: comment it
    //self.applyFilter = YES; // (update: 2012/08/08)
   
    self.coreImageFilter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues:
                        kCIInputImageKey, sourceImage,
                        @"inputIntensity", [NSNumber numberWithFloat:0.8], nil];
   
    return [self.coreImageFilter outputImage];
}

....
// step 4:
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    //@update: comment it
    //GlobalDataClass *dataObj = [GlobalDataClass getInstance];
    self.dataObj.isUsingFilter = NO;
    //NSLog(@"filterID = %@", dataObj.filterID); // NoID
....
    //@update: comment it
    //self.applyFilter = NO;
// (update: 2012/08/08)
    self.isUsingFrontCamera = NO; // 預設為後置鏡頭
    self.isLastFrontCamera = NO;  // 預設上次為後置鏡頭
    lastOrientation = Unknown; // 設備上次的擺放方向: 未知
....

}

....
// step 5:
- (void)viewDidUnload
{   
    [super viewDidUnload];
   
    //@add
    if ([EAGLContext currentContext] == self.glContext) {
        [EAGLContext setCurrentContext:nil];
    }
    self.glContext = nil;
    self.filterDictionary = nil;
    self.reusableCells = nil;
    self.coreImageContext = nil;
    self.coreImageFilter = nil;
    self.ciImage = nil;
    self.glView = nil;
    self.session = nil;
    self.filterLensImageView = nil;
    self.filterListTableView = nil;
    self.buttonView = nil;
    self.saveButton = nil;
    self.switchButton = nil;
    self.torchButton = nil;
    self.observerButton = nil;
    self.dataObj = nil;
}

....
// step 6:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    //@add
    //@update: comment it
    //GlobalDataClass *dataObj = [GlobalDataClass getInstance];
    CGFloat filterCellRotationAngle; // filterCell Rotation Angle
....
    /*
    if (dataObj.filterCellArray.count > 0) {
        for (FilterCell* filterCell in dataObj.filterCellArray) {
            filterCell.transform = CGAffineTransformMakeRotation(filterCellRotationAngle);
        }
    }
    */
    //@update
    if (self.dataObj.filterCellArray.count > 0)
   {

        for (FilterCell* filterCell in self.dataObj.filterCellArray)
       {

            filterCell.transform = CGAffineTransformMakeRotation(filterCellRotationAngle);
        }
    }

    return YES;
}

....
// step 7:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
   
    // 將取得的 sampleBuffer 轉成 CVPixelBuffer
    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
       
    //@update: use instance var (ciImage)
    // 接著, 將 CVPixelBuffer 利用 Core Image 的初始化方法再轉成 CIImage.
    self.ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
   
    //@add:轉換原始影像的方向
    self.ciImage = [self orientationTransform:self.ciImage];
   
    // 然後使用 CIContext 物件將其內容畫到 render buffer
    [self.coreImageContext drawImage:self.ciImage atPoint:CGPointZero fromRect:[self.ciImage extent]];
   
    //@update
    if(self.dataObj.isUsingFilter == YES)
    {
        // 濾鏡功能測試
        self.ciImage = [self filterTest:self.ciImage];
   
        // 畫出 "濾鏡範圍" 內的影像(含方向轉變的調整)
        [self drawFilteredImage:self.ciImage];
    }
   
    // 最後, 在螢幕上呈現出來.
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}
....

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

D. 選取 / 取消選取 filter cell
     (並調整 GlobalDataClass 類別變數, 幫 cell 記錄 filterID)
      1. 開啟 HorizontalTableCell.h 檔案, 修改如下:
....
@interface HorizontalTableCell : UITableViewCell <UITableViewDelegate, UITableViewDataSource>
{
    //@add
    // we will rotate and add as a subview of our cell
    UITableView *filterTableView;
   
    // hold the filters for the category we are in
    NSArray *filters;
   
    //@add: lastIndexPath in tableView:didSelectRowAtIndexPath:
    NSIndexPath *lastIndexPath;
   
    GlobalDataClass *dataObj;
}

//@add
@property (nonatomic, strong) UITableView *filterTableView;
@property (nonatomic, strong) NSArray *filters;
@property (nonatomic, strong) NSIndexPath *lastIndexPath;
@property (nonatomic, strong) GlobalDataClass *dataObj;

@end

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

      2. 開啟 HorizontalTableCell.m 檔案, 修改如下:
....
// step 1:
@synthesize filterTableView = _filterTableView;
@synthesize filters = _filters;
@synthesize lastIndexPath = _lastIndexPath;
@synthesize dataObj = _dataObj;

....
// step 2:
#pragma mark Getter
//@add:
// 記錄上次選取的資料列:
//
// 問題:
// 一開始尚未選取任何資料列時, lastIndexPath 預設為 nil,
// 第一次點選資料列時, 在 tableView:didSelectRowAtIndexPath: 中呼叫:
// int newRow = [indexPath row];
// int oldRow = [self.lastIndexPath row];
//
// 得到 oldRow 的值為 0, 如果點選的資料是第 1 列, newRow 的值也為 0,
// 會造成: 程式判斷成要 "取消選取濾鏡"
//
// 解決:
// 在初始化 lastIndexPath 時, 將其 indexPathForRow: 的值設為 -1
- (NSIndexPath *)lastIndexPath
{
    if (_lastIndexPath == nil) {
        _lastIndexPath = [NSIndexPath indexPathForRow:-1 inSection:0];
    }
   
    return _lastIndexPath;
}

//@add
- (GlobalDataClass *)dataObj
{
    return [GlobalDataClass getInstance];
}

....
// step 3:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
    //@title text: we don't need to load that, just read it from our array of filters. 
    cell.titleLabel.text = [currentFilter objectForKey:@"Title"];
    cell.filterID = [currentFilter objectForKey:@"FilterID"];
    //NSLog(@"filterID = %@", cell.filterID);
   
    //@add for storage filterCells
    //
    // 當沒有找到相同的 filterID 時, 才將新的 filterCell 存入 Array 裡.
    //
    /*
     備註: 1. 當找到相同的 filterID 時, 不刪除其所屬的 cell.
              因為, 如果刪除舊的 filterCell, 程式在執行時,
              於拖拉"濾鏡表單" 時會 crash 掉.
    
           2. 如果一律將 filterCell 儲存起來, 程式在執行時,
              於每次拖拉"濾鏡表單" 時, 都會將新產生的 filterCell
              (即使 filterID 名稱相同)存入 Array 裡.
    */
   
    //@update: comment it
    //GlobalDataClass *dataObj = [GlobalDataClass getInstance];

    BOOL saveCell = YES;
   
    //for (FilterCell* filterCell in dataObj.filterCellArray) {
    //@update
    for (FilterCell* filterCell in self.dataObj.filterCellArray) {
        //if ([filterCell.titleLabel.text isEqualToString:cell.titleLabel.text])
        //@update
        if ([filterCell.filterID isEqualToString:cell.filterID])
        {
            saveCell = NO;
            break;
        }
    }
    if (saveCell) {
        //[dataObj.filterCellArray addObject:cell];
        //@update
        [self.dataObj.filterCellArray addObject:cell];
    }
    //NSLog(@"self.dataObj.filterCellArray.count = %d", self.dataObj.filterCellArray.count);
   
    return cell;
}

....
// step 4:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    int newRow = [indexPath row];
    int oldRow = [self.lastIndexPath row];
    //NSLog(@"oldRow = %d", oldRow);
    //NSLog(@"newRow = %d", newRow);
   
    //@update comment it
    //GlobalDataClass *dataObj = [GlobalDataClass getInstance];
   
    // 選取濾鏡
    if (newRow != oldRow)
    {
        self.lastIndexPath = indexPath;
       
        //@TODO: 套用濾鏡功能
        /* (此方式亦可)
        FilterCell *cell = (FilterCell *)[tableView cellForRowAtIndexPath:indexPath];
        NSLog(@"FilterID = %@, Title = %@", cell.filterID, cell.titleLabel.text);
        */
        NSDictionary *currentFilter = [self.filters objectAtIndex:indexPath.row];
        /*
        NSString *FilterID = [currentFilter objectForKey:@"FilterID"];
        NSString *Title = [currentFilter objectForKey:@"Title"];
        NSString *ImageName = [currentFilter objectForKey:@"ImageName"];
        NSLog(@"FilterID = %@, Title = %@, ImageName = %@", FilterID, Title, ImageName);
        */
        self.dataObj.isUsingFilter = YES;

        self.dataObj.filterID = [NSMutableString stringWithString:[currentFilter objectForKey:@"FilterID"]];
    }
    // 取消選取濾鏡
    else
    {
        //self.lastIndexPath = nil; // -> not work at section:0 row:0
        self.lastIndexPath = [NSIndexPath indexPathForRow:-1 inSection:0];
        [tableView deselectRowAtIndexPath:indexPath animated:NO];
       
        //@TODO: 恢復原始影像
        self.dataObj.isUsingFilter = NO;
        self.dataObj.filterID = [NSMutableString stringWithString:@"NoID"];
    }
   
    NSLog(@"filterID = %@", self.dataObj.filterID);
}

....
// step 5:
- (void)dealloc
{
    self.filterTableView = nil;
    self.filters = nil;
    self.lastIndexPath = nil;
    self.dataObj = nil;
}
....

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

E. 編譯並執行
     1. 未套用濾鏡: 相機畫面

     2. 未套用濾鏡: 拍照結果 (只會截取濾鏡方框內的影像)

     3. 套用濾鏡: 相機畫面 (目前每個濾鏡效果都一樣)

     4. 套用濾鏡: 拍照結果

2012年8月2日 星期四

Filter4Cam 實作: 25. 開啟/關閉 閃光燈

since: 2012/08/02
update: 2012/08/02

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

A. 說明:
     1. 從 iPhone 4 之後的相機才有內建閃光燈, 在此先將 "開啟/關閉閃光燈" 功能
         寫上, 等之後有機會再來測試.

     2. 相機內建的閃光燈, 一般都是位在後置鏡頭附近, 因此當要使用閃光燈時,
         要先將相機鏡頭切換到後置.

     3.  當啟用閃光燈時, 閃光燈的按鈕要維持在選取的狀態, 這需要對按鈕作客製化
          處理, 留待製作按鈕圖案時, 再一併處理.       

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

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

....
//@add: "閃光燈切換"
- (void)switchLight
{
    // step 1: Check the Cameras
    if (![Filter4CamHelper numberOfCameras] > 1) return;
   
    AVCaptureDevice *device = [Filter4CamHelper backCamera];
   
    // step 2: Check the Flash
    if (!device.hasFlash)
    {
        NSLog(@"switchLight: The capture device does not has a flash");
       
        return;
    }
   
    // step 3: Recover the default back camera
    if (self.isUsingFrontCamera)
    {
        [self switchCameras];
    }
   
    // step 4: Lock the device
    if ([device lockForConfiguration:nil])
    {
        // step 5: Toggle the button from on to off or vice versa(反之亦然)
        //@TODO ##########################################
        NSLog(@"switchLight TODO: Toggle the button from on to off or vice versa");
       
        // step 6: Toggle the light on or off
        device.torchMode = (self.torchButton.selected) ? AVCaptureTorchModeOn : AVCaptureTorchModeOff;
       
        // step 7: Unlock and proceed
        [device unlockForConfiguration];
    }
}
....

2012年7月26日 星期四

Clean Install OS X Mountain Lion

since: 2012/07/26
update: 2012/07/29

reference:
1. How to Clean Install OS X Mountain Lion
2. Create an OS X Mountain Lion Installation Boot DVD or USB Drive with LionDiskMaker

A. 取得 OS X Mountain Lion
     從 Mac App Store - OS X Mountain Lion 購買(NT$590)並下載,
     但是先不要安裝.

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

B. 製作 Mountain Lion 系統磁碟
     1. 說明: 先嘗試製作成 DVD系統安裝片, 無論使用之前的方式, 或
                   Lion Diskmaker 軟體, 都會說 4.7GB 空間不足, 但是放入 8.5GB 的
                   空白DVD 片, 到最後都燒錄失敗, 也可能是空白片本身的問題, 因此,
                   決定改成製作 USB 系統磁碟.

     2. 先將「應用程式」內的「安裝 OS X Mountain Lion.app」拷貝至桌面.

     3. 將至少 8GB 的 USB 放入 Mac 的 USB 插槽, 下載最新版本的
          Lion DiskMaker. (在此為: 2.0rc2)

     4. 承上, 解壓縮至桌面後, 執行: Lion Diskmaker 2rc2.app 檔案.

     5. 選擇製作成開機磁碟的 Mac OS X 版本:
          Mountain Lion (10.8)

     6. 選擇桌面上的 "安裝 OS X Mountain Lion.app" 檔案.

     7. 選擇 "創建安裝磁盤"

     8. 選擇磁盤種類:
          8GB 的 USB 隨身碟

     9. 選定該USB 隨身碟.

     10. 警示: 磁碟的整個內容將被擦除.
           擦除並創建安裝磁盤.

     11. 進行拷貝過程.

     12. 完成了, 按下: 退出

     13. 完成後的 USB 隨身碟內容.

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

C.安裝 Mountain Lion
    1. 將 USB 隨身碟與 Mac 連接好, 重新開機並按下 "Option" 鍵,
        從開機選單選澤有 "Mountain Lion" 圖示的磁碟.

    2. 以繁體中文作為主要的語言.

    3. 選取磁碟工具程式 > 選擇要格式化的硬碟 > 切換到 "清除" 頁籤
         > 在 "格式" 項目, 選擇: "Mac OS 擴充格式 (日誌式) "
         > 輸入磁碟名稱


    4. 按下 "清除" (這是無法回復的點)

    5. 完成後, 離開磁碟工具程式.

    6. 選擇 "重新安裝 OS X" > 繼續 > 同意 > 同意

    7. 選取剛才格式化好的磁碟機, 並開始安裝.

    8. 安裝完成.


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

D. 備註
    
1. 裝了以下的軟體:
          uTorrent, aMule, ToyViewer, Paintbrush, TextWrangler,
          Adium, Line, Core Data Editor, Icon Maker, FireFox, Xcode,
          Google Driver, Sophos Anti-Virus, Opengl Extension Viewer,
          MPlayerX, iExplorer, QREncoder, XMarks (for FireFox & Safari),
          CleanMyMac, AVerTV, OmniGraffle Professional, TheUnarchiver,
          PlistEdit Pro, Toast, Photoshop CS5, Cog, Paragon NTFS for Mac

     2. 在終端機模式下, 用 Mac 的文字編輯器開啟某個檔案的方式:
          $ sudo /Applications/TextEdit.app/Contents/MacOS/TextEdit /etc/hosts
            
           編輯後存檔:
           command + s

           刷新快取內容:
           $ sudo dscacheutil -flushcache

2012年7月25日 星期三

Filter4Cam 實作: 24. 影像方向調整之五

since: 2012/07/25
update: 2012/07/25

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

拍照

A. 說明:
     1. 要產生的圖片, 不需要再次從 filter 取得 CIImage 的輸出:
         self.ciImage = [self.coreImageFilter outputImage], 直接使用 self.ciImage 即可, 
         因為 self.ciImage 會在之前的處理過程中, 採用原始影像或套用濾鏡.

     2. ALAssetOrientation 的值, 可有以下 8 種:
         ALAssetOrientationUp;
         ALAssetOrientationUpMirrored; 
         ALAssetOrientationDown;
         ALAssetOrientationDownMirrored;
         ALAssetOrientationRight;
         ALAssetOrientationRightMirrored;
         ALAssetOrientationLeft;
         ALAssetOrientationLeftMirrored;

     3. "擷取的濾鏡影像範圍"(fromRect:) 與 drawFilteredImage: 方法中,
          drawImage:inRect:fromRect: 裡的 fromRect: 設定值相同

     4. 使用 ALAssetsLibrary 將 CGImageRef 儲存到相簿裡的函式有二個:
          a. writeImageToSavedPhotosAlbum:metadata:completionBlock:
          b. writeImageToSavedPhotosAlbum:orientation:completionBlock:

     5. 依照 "設備擺放方向", "前後置鏡頭" 與 "設備上次擺放方向" 來調整
         產生圖片的方向.

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

B. 開啟 ViewController.m 檔案, 修改如下:
....
//@update: "拍照": 只截取濾鏡範圍的影像
- (void)savePhoto
{   
    // 產生 CGImageRef
    CGImageRef cgImage = nil;
   
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
   
    ALAssetOrientation ALAOrientation = ALAssetOrientationUp;
       
    // ################################################# //
    // A. 設備擺放方向為: "垂直" 或 "垂直 180 度" 或 "水平向右" 或 "水平向左" //
    // ################################################# //
    if (orientation == UIDeviceOrientationPortrait ||
        orientation == UIDeviceOrientationPortraitUpsideDown ||
        orientation == UIDeviceOrientationLandscapeRight ||
        orientation == UIDeviceOrientationLandscapeLeft)
    { 
        ///////////////////////////////////////
        // 1. 擷取濾鏡影像範圍
        //
       
        // 設備垂直擺放
        if (orientation == UIDeviceOrientationPortrait)
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(23, -557.5, 274, 274)];
        }
        // 設備垂直 180 度擺放
        else if (orientation == UIDeviceOrientationPortraitUpsideDown)
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(-458.5, 83, 274, 274)];
        }
        // "設備水平向右擺放" 或 "設備水平向左擺放"
        else if (orientation == UIDeviceOrientationLandscapeRight ||
                 orientation == UIDeviceOrientationLandscapeLeft)
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(123.5, 23.5, 274, 274)];
        }
       
        ///////////////////////////////////////
        // 2. "前置鏡頭" 影像方向
        //
       
        // 前置鏡頭
        if (self.isUsingFrontCamera)
        {           
            // 設備水平向左擺放
            if (orientation == UIDeviceOrientationLandscapeLeft)
            {
                ALAOrientation = ALAssetOrientationDownMirrored;
            }
            // 其它: 垂直, 垂直 180 度, 水平向右
            else
            {
                ALAOrientation = ALAssetOrientationUpMirrored;
            }
           
            // 將 CGImageRef 儲存到相簿裡
            ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
           
            [library writeImageToSavedPhotosAlbum:cgImage orientation:ALAOrientation
                                  completionBlock:^(NSURL *assetURL, NSError *error) {
                                     
                              // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                                      CGImageRelease(cgImage);
                                  }];
        }
       
        ///////////////////////////////////////
        // 3. "後置鏡頭" 影像方向
        //
       
        // 後置鏡頭
        else
        {
            // 設備水平向右擺放
            if (orientation == UIDeviceOrientationLandscapeRight)
            {              
                ALAOrientation = ALAssetOrientationDown;
               
                // 將 CGImageRef 儲存到相簿裡
                ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
               
                [library writeImageToSavedPhotosAlbum:cgImage orientation:ALAOrientation
                                      completionBlock:^(NSURL *assetURL, NSError *error) {
                                         
                                  // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                                          CGImageRelease(cgImage);
                                      }];
            }
            // 其它: 垂直, 垂直 180 度, 水平向左  
            else {
                // 將 CGImageRef 儲存到相簿裡
                ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
               
                [library writeImageToSavedPhotosAlbum:cgImage
                                             metadata:[self.ciImage properties]
                                      completionBlock:^(NSURL *assetURL, NSError *error) {
                                         
                                    // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                                          CGImageRelease(cgImage);
                                      }];
            }
        }
    }
    // ############################### //
    // B. 設備擺放方向為: "朝上" 或 "朝下" 或 "未知" //
    // ############################### //
    else if(orientation == UIDeviceOrientationFaceUp ||
            orientation == UIDeviceOrientationFaceDown ||
            orientation == UIDeviceOrientationUnknown)
    {
        ///////////////////////////////////////
        // 1. 擷取濾鏡影像範圍
        //
       
        // 上次為直擺
        if (lastOrientation == Portrait)
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(23, -557.5, 274, 274)];
        }
        // 上次為直擺且上下顛倒
        else if (lastOrientation == PortraitUpsideDown)
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(-458.5, 83.5, 274, 274)];
        }
        // 上次為 "橫擺朝右" 或上次為 "橫擺朝左"
        else if (lastOrientation == LandscapeRight ||
                   lastOrientation == LandscapeLeft)   

        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(123.5, 23.5, 274, 274)];
        }
        else
        {
            NSLog(@"savePhoto: orientation = FaceUp/FaceDown/Unknown, lastOrientation not defined!");
        }


        ///////////////////////////////////////
        // 2. "前置鏡頭" 影像方向
        //
       
        if (self.isUsingFrontCamera)
        {           
            // 上次為 "橫擺朝左"
            if (lastOrientation == LandscapeLeft)
            {
                ALAOrientation = ALAssetOrientationDownMirrored;
            }
            // 其它: 上次為 "直擺", "直擺且上下顛倒" 或 "橫擺朝右"
            else
            {
                ALAOrientation = ALAssetOrientationUpMirrored;
            }
           
            // 將 CGImageRef 儲存到相簿裡
            ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
           
            [library writeImageToSavedPhotosAlbum:cgImage orientation:ALAOrientation
                                  completionBlock:^(NSURL *assetURL, NSError *error) {
                                     
                              // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                                      CGImageRelease(cgImage);
                                  }];
        }

        ///////////////////////////////////////
        // 3. "後置鏡頭" 影像方向
        //
       
        // 後置鏡頭
        else
        {
            // 上次為 "橫擺朝右"
            if (lastOrientation == LandscapeRight)    
            {              
                ALAOrientation = ALAssetOrientationDown;
               
                // 將 CGImageRef 儲存到相簿裡
                ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
               
                [library writeImageToSavedPhotosAlbum:cgImage orientation:ALAOrientation
                                      completionBlock:^(NSURL *assetURL, NSError *error) {
                                         
                                    // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                                          CGImageRelease(cgImage);
                                      }];
            }
            else {
                // 將 CGImageRef 儲存到相簿裡
                ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
               
                [library writeImageToSavedPhotosAlbum:cgImage
                                             metadata:[self.ciImage properties]
                                      completionBlock:^(NSURL *assetURL, NSError *error) {
                                         
                                  // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                                          CGImageRelease(cgImage);
                                      }];
            }
        }
    }
    else
    {
        NSLog(@"savePhoto: No orientation match!");
    }
}
....

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

C. 編譯並執行:

     1. 先將 buttonView 的 backgroundColor 註解後, 再編譯並執行.
     // ViewController.m
....
// 用來放所有按鈕的 View
- (UIView *)buttonView
{
    if (_buttonView == nil) {
        _buttonView = [[UIView alloc] init];
        //@測試使用
        //_buttonView.backgroundColor = [UIColor redColor];    
    }
   
    return _buttonView;
}
....

     2. 設備水平向右擺放:
         (相機鏡頭在右後方, 功能按鈕遠離相機鏡頭位置)

     3. 拍照結果:

Filter4Cam 實作: 23. 影像方向調整之四

since: 2012/07/25
update: 2012/07/25

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

畫出 "濾鏡範圍" 內的影像

A. 說明:
     修改原本的 drawFilteredImage: 方法, 主要調整:
     1. "設備水平向右擺放" 時的, 繪製來源區塊(fromRect)與目的區塊(inRect)位置.

     2. 對 "設備朝上擺放", "設備朝下擺放" 或 "設備擺放方向未知" 的情況, 加入依據
         "上次設備的擺放方向" 來作調整的處理.

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

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

....
// 畫出 "濾鏡範圍" 內的影像(含方向轉變的調整)
- (void)drawFilteredImage:(CIImage *)filteredImage
{
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
   
    // ############# //
    // A. 設備垂直擺放 //
    // ############# //
    if (orientation == UIDeviceOrientationPortrait)
    {       
        [self.coreImageContext drawImage:filteredImage inRect:CGRectMake(23.5, 83.5, 274, 274)
                                // x 越小, 濾鏡越向右; y 負越多, 濾鏡越向上
                                fromRect:CGRectMake(23.5, -557.5, 274, 274)];
    }
    // #################### //
    // B. 設備垂直 180 度擺放 //
    // #################### //
    else if (orientation == UIDeviceOrientationPortraitUpsideDown)
    {       
        [self.coreImageContext drawImage:filteredImage inRect:CGRectMake(23.5, 83.5, 274, 274)
                                // x 負越多, 濾鏡越向左; y 越多, 濾鏡越向上
                                fromRect:CGRectMake(-458.5, 83.5, 274, 274)];
    }
    // ####################################### //
    // C. "設備水平向右擺放" 或 "設備水平向左擺放" //
    // ####################################### //
    else if (orientation == UIDeviceOrientationLandscapeRight ||
             orientation == UIDeviceOrientationLandscapeLeft)
    {       
        [self.coreImageContext drawImage:filteredImage inRect:CGRectMake(123.5, 23.5, 274, 274)
                                // x 越大, 濾鏡越向左; y 越多, 濾鏡越向下
                                fromRect:CGRectMake(123.5, 23.5, 274, 274)];
    }
    // ################################################### //
    // D. "設備朝上擺放" 或 "設備朝下擺放" 或 "設備擺放方向未知" //
    // #################################################### //
    else if (orientation == UIDeviceOrientationFaceUp ||
             orientation == UIDeviceOrientationFaceDown ||
             orientation == UIDeviceOrientationUnknown)
    {       
        // case 1: 上次為直擺
        if (lastOrientation == Portrait)
        {
            [self.coreImageContext drawImage:filteredImage inRect:CGRectMake(23.5, 83.5, 274, 274)
                                    // x 越大, 濾鏡越向左; y 越多, 濾鏡越向上
                                    fromRect:CGRectMake(23.5, -557.5, 274, 274)];
        }
        // case 2: 上次為直擺且上下顛倒
        else if (lastOrientation == PortraitUpsideDown)
        {
             [self.coreImageContext drawImage:filteredImage inRect:CGRectMake(23.5, 83.5, 274, 274)
                                    // x 負越多, 濾鏡越向左; y 越多, 濾鏡越向上
                                    fromRect:CGRectMake(-458.5, 83.5, 274, 274)];
        }
        // case 3: 上次為 "橫擺朝右" 或 case 4: 上次為 "橫擺朝左"
        else if (lastOrientation == LandscapeRight || lastOrientation == LandscapeLeft)   
        {
            [self.coreImageContext drawImage:filteredImage inRect:CGRectMake(123.5, 23.5, 274, 274)
                                    // x 越大, 濾鏡越向左; y 越多, 濾鏡越向上
                                    fromRect:CGRectMake(123.5, 23.5, 274, 274)];
        }
        else
        {
            NSLog(@"drawFilteredImage: orientation = FaceUp/FaceDown/Unknown, lastOrientation not defined!");
        }
    }
    else
    {
        NSLog(@"drawFilteredImage: No orientation match!");
    }
}
....