2012年7月23日 星期一

Filter4Cam 實作: 19. 前後鏡頭切換

since: 2012/07/23
update: 2012/07/23

reference:
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 有與 OpenGL 的 model 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 的方向與位置.

2012年7月10日 星期二

iOS 5 的 UIKit 粒子系統

since: 2012/07/03
update: 2012/07/10

reference:
UIKit Particle Systems in iOS 5 Tutorial | Ray Wenderlich

A. 說明
     使用粒子系統的例子:
      1. UIKit games:
          你可以完全只使用 UIKit 來製作遊戲(如紙牌類的遊戲, 運作的相當不錯).
          現在, 你可以使用爆炸, 煙霧等效果來使遊戲有更佳的體驗. 

      2. Slick(圓滑的) UI effects:
          當使用者在螢幕上移動一個物體時, 可以留下拖曳的煙霧效果.

      3. Stunning(令人震驚的) screen transitions(轉變):
          在 App 顯示下一個螢幕時, 讓之前的畫面消失在火球之中.

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

B. 粒子 APIs
      1. 有二個在 QuartzCore 框架中的類別, 可供使用來建立粒子系統, 分別是:
          CAEmitterLayer 與 CAEmitterCell.

          備註:
          a. CAEmitterLayer Class Reference:              
               CAEmitterLayer 類別提供一個 Core Animation 的粒子發射(emitter)系統.
               粒子藉由 CAEmitterCell 的實體來定義. 粒子將會被繪製在 layer
               的背景色之上與 layer 的邊緣(border)之上.

          b. CAEmitterCell Class Reference:
               CAEmitterCell 類別用來在 CAEmitterLayer 中將一個粒子來源發射出去.
               Emitter cell 定義了發射的方向與發射粒子的相關屬性. Emitter cells 可以
               有存放 sub-cells 的陣列, 這使得粒子本身可以再發射粒子.  

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

      2. 一般的概念是, 先建立一個 CAEmitterLayer, 將一個或多個 CAEmitterCells
           加入其中. 每個 cell 會依照各自的設定來產生粒子效果.

      3. 並且, 因為 CAEmitterLayer 是繼承自 CALayer, 你可以在 UIKit 的任何階層
           (hierarchy)中輕易地來引入使用.

      4. 我覺得這個新的 UIKit 粒子系統最酷的事情是: 單一的 CAEmitterLayer 可以
          持有很多個 CAEmitterCells. 這使你可以完成一些很複雜的極酷特效. 例如,
          如果要建立一個噴水池, 你可以用一個 cell 發射水滴粒子, 並且用另一個 cell
          在噴水池上方發射水蒸氣粒子.

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

C. 建立專案
      1. Xcode > File > New > Project... > iOS > Application >
          Single View Application > Next
          Product Name: DrawWithFire     
          Company Identifier: com.blogspot
          Device Family: iPhone
          Use Storyboards: checked
          Use Automatic Reference Counting: checked
          > Next > Create

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

      2. 加入以下的 Framework:
        QuartzCore
        - Supports image processing and video image manipulation.

        備註: 目前專案的 Framework 如下:

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

D. 客製化 UIView 類別 (將 CAEmitterLayer 作為 UIView 的 layer)
      1. Xcode > File > New > New File...
         iOS > Cocoa Touch > Objective-C class > Next
         Class: DWFParticleView
         Subclass of: UIView
         > Next > Create

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

      2. 開啟 DWFParticleView.m 檔案, 修改如下:
#import "DWFParticleView.h"
//@add
#import <QuartzCore/QuartzCore.h>

//@add
@interface DWFParticleView ()
{
    CAEmitterLayer *fireEmitter;
}
@property (nonatomic, strong) CAEmitterLayer *fireEmitter;

@end


@implementation DWFParticleView

//@add
@synthesize fireEmitter = _fireEmitter;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

//@add
-(void)awakeFromNib
{
    //set ref to the layer
    self.fireEmitter = (CAEmitterLayer *)self.layer;
}

//@add: overwriting
+ (Class)layerClass
{
    //configure the UIView to have emitter layer
    return [CAEmitterLayer class];
}

@end

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

      3. 將 view controller 的 root view 設為 DWFParticleView:
          開啟 MainStoryboard.storyboard 檔案, 修改如下:

          選取左方 View Controller 下的 UIView
          > 點選右上方的 "Identity inspector"
          > 將 Class 由 UIView 改選為 DWFParticleView.

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

E. 基本粒子測試
      1. 說明:
          為了要發射出火焰, 煙霧, 瀑布等等, 你需要有一個 PNG 檔案來作為粒子的起始.
          原作者提供一個 PNG 檔案下載, 請將其加入到專案裡. 這個粒子檔案大小為:
          32×32 pixels, 具透明度, 僅使用有點古怪的白色筆刷來隨機繪製. 對粒子而言,
          粒子的發射器最佳的顏色就是白色的, 它可以讓我們將想要的顏色畫在所提供的
          圖像上. 建立半透明的粒子圖像也是個不錯的想法, 如此一來, 粒子系統就可以
          藉由粒子本身來作混合.    

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

      2. 開啟 DWFParticleView.m 檔案, 修改如下:
....
-(void)awakeFromNib
{
    // 1. Set ref to the layer
    self.fireEmitter = (CAEmitterLayer *)self.layer;
   
    // 2. Configure the emitter layer
    //
    // set the position of the emitter
    self.fireEmitter.emitterPosition = CGPointMake(50, 50);
    // set the size of the particles to spawn
    self.fireEmitter.emitterSize = CGSizeMake(10, 10);
   
    // 3. Add a CAEmitterCell to the CAEmitterLayer
    //
    // creating a cell instance
    CAEmitterCell *fire = [CAEmitterCell emitterCell];

    // birthRate(出生率): 每秒發射出的粒子數目. 對於一個好的火焰或瀑布粒子而言,
    // 最少需要幾百個粒子, 所以在此設為 200.
    fire.birthRate = 200;

    // lifetime(壽命): 粒子消失前的存活秒數, 所以在此設為 3.0.
    fire.lifetime = 3.0;
   
    // lifetimeRange(壽命的變化範圍): 系統會在 (lifetime – lifetimeRange,
    // lifetime + lifetimeRange) 這個範圍內,
隨機取得粒子的壽命時間.
    // 因此, 在此例中, 粒子的壽命時間介於 2.5 ~ 3.5 秒之間.

    fire.lifetimeRange = 0.5;
   
    // color(顏色): 粒子內容的著色效果. 在此選擇橘色.
    fire.color = [[UIColor colorWithRed:0.8 green:0.4 blue:0.2 alpha:0.1] CGColor];
   
    // contents(內容): 提供給發射的 cell 使用的內容, 通常是一個 CGImage 物件.
    // 在此設為粒子圖像.  

    fire.contents = (id)[[UIImage imageNamed:@"Particles_fire.png"] CGImage];
   
    // name(名稱): 你可以為要發射的 cell 設定一個名稱, 以便在之後方便查找
    // 和修改屬性.

    [fire setName:@"fire"];
   
    // 4. add the cell to the layer and we're done
    self.fireEmitter.emitterCells = [NSArray arrayWithObject:fire];
}
....

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

      3. 編譯並執行:
          看起來像是一個橘色的汙點.

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

F. 讓粒子效果更加生動
     開啟 DWFParticleView.m 檔案, 修改如下:

    
調整1:
....

-(void)awakeFromNib
{
.... 
    //@add for more dynamic
    //
    // velocity(速度): 粒子在每個像素上每秒移動的速度.
    // 在此, 會讓 cell 朝向螢幕的右邊發射粒子.
    fire.velocity = 10;
   
    // velocityRange(速度的變化範圍)
    fire.velocityRange = 20;
   
    // emissionRange(發射角度, 弧度):
    // 在此設為: PI / 2 = 90 度 
    fire.emissionRange = M_PI_2;
   
    // name(名稱): 你可以為要發射的 cell 設定一個名稱,
    // 以便在之後方便查找和修改屬性.

    [fire setName:@"fire"];
   
    // 4. add the cell to the layer and we're done
    self.fireEmitter.emitterCells = [NSArray arrayWithObject:fire];
}
....

     編譯並執行:

     調整2:
....

-(void)awakeFromNib
{
.... 
    //@add for more dynamic
    //
    // velocity(速度): 粒子在每個像素上每秒移動的速度.
    // 在此, 會讓 cell 朝向螢幕的右邊發射粒子.
    fire.velocity = 10;
   
    // velocityRange(速度的變化範圍)
    fire.velocityRange = 20;
   
    // emissionRange(發射角度, 弧度):
    // 在此設為: PI / 2 = 90 度 
    fire.emissionRange = M_PI_2;
   
    // ScaleSpeed(速度的縮放比率)
    fire.scaleSpeed = 0.3;
   
    // spin(旋轉, 自旋): 每個粒子的旋轉速度.
    fire.spin = 0.5;
   
    // name(名稱): 你可以為要發射的 cell 設定一個名稱,
    // 以便在之後方便查找和修改屬性.
    [fire setName:@"fire"];
   
    // 4. add the cell to the layer and we're done
    self.fireEmitter.emitterCells = [NSArray arrayWithObject:fire];
}
....

     編譯並執行:

     調整3: 設定渲染模式
      a. 先將背景調成灰色:
          開啟 MainStoryboard.storyboard 檔案, 先點選左方 View Controller 下的
          Particle View, 再點選右方的 "Identity Inspector", 將下方的 Background
          改選為: Light Gray Color.


      b. 設定渲染模式:  
....
-(void)awakeFromNib
{
    // 1. Set ref to the layer
    self.fireEmitter = (CAEmitterLayer *)self.layer;
   
    // 2. Configure the emitter layer
    //
    // set the position of the emitter
    self.fireEmitter.emitterPosition = CGPointMake(50, 50);
    // set the size of the particles to spawn
    self.fireEmitter.emitterSize = CGSizeMake(10, 10);
   
    //@add for renderMode
    //
    // additive render mode(遞增渲染模式):
    // 1) 不再以普通的方式: 一個覆蓋住另一個來繪製粒子.
    // 2) 改成: 當粒子有重疊的話, 顏色強度會增加.
    // 3) 所以你會看到: 粒子的發射區域內為沸騰般的白色物質;
    //    而在區域外面則為火球, 因為在那裡粒子因滅亡而減少數量,
    //    色彩蛻變到原始鐵鏽般的顏色.
    self.fireEmitter.renderMode = kCAEmitterLayerAdditive;
....
}
....

     編譯並執行:

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

G. 觸碰火焰
      1. 開啟 DWFParticleView.h 檔案, 修改如下:
#import <UIKit/UIKit.h>

@interface DWFParticleView : UIView

//@add
- (void)setEmitterPositionFromTouch:(UITouch *)t;

@end


      2. 開啟 DWFParticleView.m 檔案, 修改如下:
....
//@add
- (void)setEmitterPositionFromTouch:(UITouch *)t
{
    //change the emitter's position
    self.fireEmitter.emitterPosition = [t locationInView:self];
}

@end

      3. 開啟 ViewController.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import "DWFParticleView.h"

@interface ViewController : UIViewController
{
    //@add
    IBOutlet DWFParticleView *fireView;
}

@end

      4. 開啟 MainStoryboard.storyboard 檔案, 設定如下:
          先點選 UI 左方的 "View Controller", 再點選右方的 "Connections Inspector",
          於下方的 Outlets 點選 "fireView" 右邊的空心圈圈, 拖拉到最左邊
          "Particle View" 上.

          結果如下: "Outlet" fireView 連結到 "Particle View".

      5. 開啟 ViewController.m 檔案, 修改如下:
....
//@add
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setEmitterPositionFromTouch: [touches anyObject]];
}

@end

      6. 編譯並執行:
          對螢幕碰觸並拖曳, 就可以看到粒子的發射效果, 並且在移動的軌跡留下火焰.
---------------------------------------------------------------------------------------

H. 動態地修改 Cells
      1. 開啟 DWFParticleView.m 檔案, 修改如下:
....
-(void)awakeFromNib
{
....
    // birthRate(出生率): 每秒發射出的粒子數目. 對於一個好的火焰或瀑布粒子而言,
    // 最少需要幾百個粒子, 所以在此設為 200.
    //fire.birthRate = 200;
    //@update: 一開始先不要產生粒子
    fire.birthRate = 0;
}
....

      2. 開啟 DWFParticleView.h 檔案, 修改如下:
#import <UIKit/UIKit.h>

@interface DWFParticleView : UIView

//@add
- (void)setEmitterPositionFromTouch:(UITouch *)t;

//@add: to turn on and off emitting
-(void)setIsEmitting:(BOOL)isEmitting;

@end

      3. 開啟 DWFParticleView.m 檔案, 修改如下:
....
//@add
-(void)setIsEmitting:(BOOL)isEmitting
{
    // turn on/off the emitting of particles
    [self.fireEmitter setValue:[NSNumber numberWithInt:isEmitting?200:0]
               forKeyPath:@"emitterCells.fire.birthRate"];
}

// 說明: 之前已為此 CAEmitterCell 設定名稱.
//           [fire setName:@"fire"];

@end

      4. 開啟 ViewController.m 檔案, 修改如下:
....
//@add
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setEmitterPositionFromTouch: [touches anyObject]];
}

//@add
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setEmitterPositionFromTouch: [touches anyObject]];
    [fireView setIsEmitting:YES];
}

//@add
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setIsEmitting:NO];
}

//@add
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [fireView setIsEmitting:NO];
}

@end

      5. 編譯並執行:
          當手碰觸到螢幕時, 才會開始產生粒子.

2012年7月3日 星期二

Using C++11 in Xcode

since: 2012/07/03
update: 2012/07/03

reference:
1. Arne's Blog: What about C++11 in Xcode?
2. Sams Teach Yourself C++ in One Hour a Day, 7/e

A. 新增專案:
      Xcode > File > New > Project... > Mac OS X > Application >
      Command Line Tool > Next

      Product Name: HelloC++11
      Company Identifier: com.blogspot
      Type: C++
      Use Automatic Reference Counting: unchecked
      > Next > Create

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

B. 使用 C++11 的 auto 關鍵字: (利用編譯器的型別推斷能力)
      1. 說明:
          a. 在 C++11 中, 你可以選擇不需要明確地指定變數的型別, 而是使用
              auto 關鍵字來替代. 使用 auto 關鍵字, 變數的型別將藉由給予變數的
              初始值來決定.

          b. 承上, 為變數定義正確型別的任務將由編譯器來處理. 編譯器所作的事,
              是去檢查變數初始化的值, 然後決定符合此變數的最佳可能型別.

          c. 使用 auto 關鍵字時, 需要給予變數初始值, 好讓編譯器能決定其型別, 
              不然的話, 會得到編譯錯誤的訊息.

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

      2. 開啟 main.cpp 檔案, 修改如下:
#include <iostream>

//@add
using namespace std;

int main(int argc, const char * argv[])
{
    // insert code here...
    //std::cout << "Hello, World!\n";
   
    //@use auto
    auto Flag = true;
   
    cout << "Flag = " << Flag;
    cout << " , sizeof(Flag) = " << sizeof(Flag) << endl;
   
    return 0;
}

說明: 在使用 auto 的地方, 會出現警告訊息:

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

      3. 修改編譯器設定:
           預設的設定:

           調整成以下:

           說明: 之前的警告訊息消失了.

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

      4. 編譯並執行:
           Flag = 1 , sizeof(Flag) = 1

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

C. 在複雜型別中使用 auto 來簡化程式碼: (特別是在使用樣板時)
      1. 開啟 main.cpp 檔案, 修改如下:
#include <iostream>
//@add
#include <vector>

//@add
using namespace std;

int main(int argc, const char * argv[])
{
    // insert code here...
    //std::cout << "Hello, World!\n";
   
    //@use auto
    /*
    auto Flag = true;
   
    cout << "Flag = " << Flag;
    cout << " , sizeof(Flag) = " << sizeof(Flag) << endl;
    */
   
    //@add: dynamic array of integers
    vector<int> MyNumbers;
   
    // 將一些元素放入 vector 中 (每次向容器後面添加一個值)
    MyNumbers.push_back(100);
    MyNumbers.push_back(200);
    MyNumbers.push_back(300);
    MyNumbers.push_back(400);
    MyNumbers.push_back(500);
   
    // 顯示所存放的元素(原始方式)
    //
    // Iterator 變數的型別為: vector<int>::const_iterator
    for (vector<int>::const_iterator Iterator = MyNumbers.begin();
         Iterator < MyNumbers.end();
         ++Iterator) {
       
        cout << *Iterator << " ";
    }
   
    return 0;
}

編譯並執行:
100 200 300 400 500

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

      2. 改成使用 auto:
....
int main(int argc, const char * argv[])
{
    //@add: dynamic array of integers
    vector<int> MyNumbers;
   
    // 將一些元素放入 vector 中 (每次向容器後面添加一個值)
    MyNumbers.push_back(100);
    MyNumbers.push_back(200);
    MyNumbers.push_back(300);
    MyNumbers.push_back(400);
    MyNumbers.push_back(500);
   
    // 顯示所存放的元素(原始方式)
    //
    // Iterator 變數的型別為: vector<int>::const_iterator
    /*
    for (vector<int>::const_iterator Iterator = MyNumbers.begin();
         Iterator < MyNumbers.end();
         ++Iterator) {
       
        cout << *Iterator << " ";
    }
    */
    //@update: use auto
    for (auto Iterator = MyNumbers.begin();
         Iterator < MyNumbers.end();
         ++Iterator) {
       
        cout << *Iterator << " ";
    }
   
    return 0;
}

編譯並執行:
100 200 300 400 500

說明:
在 auto 那一行, 編譯器檢查 Iterator 的初始值(為 MyNumbers.begin() 的回傳值),
並將 Iterator 變數的型別設為與 MyNumbers.begin() 的回傳值之型別相同.
(i.e. vector<int>::const_iterator)  

2012年7月2日 星期一

Filter4Cam 實作: 18. 拍照

since: 2012/07/02
update: 2012/07/02

reference:
I touchs: Filter4Cam 學習之 Core Image Tutorial

A. 說明:
      先檢查專案是否有加入: AssetsLibrary framework, 如果沒有的話就加入
      (之前應該有加入了). AssetsLibrary framework 是用來存取被影像應用程式
      所管理的照片或影片.

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

B. 開啟 ViewController.h 檔案, 修改如下:
....
//@add
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
#import <CoreVideo/CoreVideo.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreImage/CoreImage.h>
#import <ImageIO/ImageIO.h>
#import <AssetsLibrary/AssetsLibrary.h>
....
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate, UITableViewDelegate, UITableViewDataSource>
{
    //@add
    CIContext *coreImageContext;
    CIFilter *coreImageFilter;
    CIImage *ciImage;
....
    BOOL isUsingFrontCamera;

    //@add 設備上一次方向是否為水平擺放(向左或向右皆算)
    BOOL isPreviousOrientationLandscape;
    BOOL applyFilter; // 是否套用濾鏡
....
}

//@add
@property (strong, nonatomic) EAGLContext *glContext;
@property (strong, nonatomic) CIContext *coreImageContext;
@property (strong, nonatomic) CIFilter *coreImageFilter;
@property (strong, nonatomic) CIImage *ciImage;
....
@property (assign) BOOL isUsingFrontCamera;

@property (assign) BOOL isPreviousOrientationLandscape;
@property (assign) BOOL applyFilter;
....

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

C. 開啟 ViewController.m 檔案, 修改如下:
....
// step 1:
//@add
@synthesize glContext = _glContext;
@synthesize coreImageContext = _coreImageContext;
@synthesize coreImageFilter = _coreImageFilter;
@synthesize ciImage = _ciImage;
....

@synthesize applyFilter = _applyFilter;


// step 2:
....
- (void)viewDidLoad

{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    //@add
    self.applyFilter = NO;
....
}

// step 3:
....
- (CIImage *)filterTest:(CIImage *)sourceImage
{
    /*
    CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues:
                        kCIInputImageKey, sourceImage,
                        @"inputIntensity", [NSNumber numberWithFloat:0.8], nil];
   
    return [filter outputImage];
    */

    //@add
    self.applyFilter = YES;

    self.coreImageFilter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues:
                        kCIInputImageKey, sourceImage,
                        @"inputIntensity", [NSNumber numberWithFloat:0.8], nil];
   
    return [self.coreImageFilter outputImage];
}

....
// step 4:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
   
    // 將取得的 sampleBuffer 轉成 CVPixelBuffer
    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
   
    //@update: comment it
    /*
    CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    ciImage = [self orientationTransform:ciImage];
    [self.coreImageContext drawImage:ciImage atPoint:CGPointZero fromRect:[ciImage extent]];
    ciImage = [self filterTest:ciImage];
    [self filterOrientationTransform:ciImage];
    */
   
    //@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]];
   
    // 濾鏡功能測試
    self.ciImage = [self filterTest:self.ciImage];
   
    // e.g. [self.coreImageContext drawImage:self.ciImage
    // inRect:CGRectMake(23.5, 123.5, 274, 274) fromRect:[self.ciImage extent]];
    [self filterOrientationTransform:self.ciImage];
   
    // 最後, 在螢幕上呈現出來.
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}

....
// step 5:
- (void)savePhoto
{
    //@TODO
    //NSLog(@"savePhoto");

    // 從 filter 取得 CIImage 的輸出:
    // 不需要再作, 因為 self.ciImage 可能有使用過 filter 或是未套用濾鏡的原始影像
   
// self.ciImage = [self.coreImageFilter outputImage];
   
    // 產生 CGImageRef
    CGImageRef cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:[self.ciImage extent]];

    // 將 CGImageRef 儲存到相簿裡
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
   
    [library writeImageToSavedPhotosAlbum:cgImage
                                 metadata:[self.ciImage properties]
                          completionBlock:^(NSURL *assetURL, NSError *error) {
                             
                              // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                              CGImageRelease(cgImage);
                          }];
}
....

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

D. 編譯並執行:
      拍照畫面:

       拍照結果:
       紅框的範圍內才是套用濾鏡的結果, 而不是整個螢幕. 

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

E. 修正拍照結果
     開啟 ViewController.m 檔案, 修改如下:
....
- (void)savePhoto
{   
    // 從 filter 取得 CIImage 的輸出:
    // 不需要再作, 因為 self.ciImage 可能有使用過 filter 或是未套用濾鏡的原始影像
    // self.ciImage = [self.coreImageFilter outputImage];
   
    // 產生 CGImageRef
    //CGImageRef cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:[self.ciImage extent]];
    CGImageRef cgImage = nil;
   
    //@update for only capture filtered scope
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
       
    //@擷取的濾鏡範圍與 filterOrientationTransform: 中
    // drawImage:inRect:fromRect: 裡的 fromRect: 相同
    //
    // 設備垂直擺放
    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)
    {
        cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(-517, -457.5, 274, 274)];
    }
    // 設備水平向左擺放(最穩定)
    else if (orientation == UIDeviceOrientationLandscapeLeft) 
   {
        cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(123.5, 23.5, 274, 274)];
    }
    // 其它 (UIDeviceOrientationFaceUp, UIDeviceOrientationFaceDown,
    // UIDeviceOrientationUnknown)

    else {          
        // 上一次設備為水平擺放
        if (self.isPreviousOrientationLandscape == YES) 
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(123.5, 23.5, 274, 274)];
        }
        // 上一次設備為垂直擺放
        else 
        {
            cgImage = [self.coreImageContext createCGImage:self.ciImage fromRect:CGRectMake(23.5, 83.5, 274, 274)];
        }
    }

    // 將 CGImageRef 儲存到相簿裡
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
   
    [library writeImageToSavedPhotosAlbum:cgImage
                                 metadata:[self.ciImage properties]
                          completionBlock:^(NSURL *assetURL, NSError *error) {
                             
                              // 釋放 CGImage, 這是一個 callback block, 當處理完畢時才會觸發.
                              CGImageRelease(cgImage);
                          }];
}
....

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

F. 編譯並執行
     拍照結果: