2012年1月31日 星期二

Filter4Cam 學習之 Core Image Programming Guide 1-2

since: 2012/01/31
update: 2012/01/31

reference: Core Image Programming Guide: Core Image Concepts

Core Image 概念之二

D. 處理過程路徑
    1. 圖 1-5 顯示在二個原始影像上操作濾鏡的像素處理過程路徑. 原始影像通常
       都被指定為 CIImage 物件. Core Image 提供了多樣的方式來取得影像資料.
       你可以利用以下的方式來轉成 CIImage 物件: 提供一個連結到影像的 URL, 讀取
       未加工(raw)的影像資料(使用 NSData 類別), 轉換一個 Quartz 2D 影像
       (CGContextRef), 一個 OpenGL 的紋理(texture), 或是一個 Core Video 的影像
       緩衝器(CVImageBufferRef).

       注意, 實際的影像輸入數量, 與是否濾鏡需要一個輸入的影像依不同的濾鏡而異,
       濾鏡是非常有彈性的, 可以做到以下的事情:

        a. 不需要影像而運作. 有些濾鏡基於輸入非影像的參數來產生影像. (例如, 在
             Core Image Filter Reference 中, 查看 CICheckerboardGenerator
         
CIConstantColorGenerator 濾鏡.)

        b. 需要一個影像. (例如, 在 Core Image Filter Reference 中, 查看 CIColorPosterize
         與
CICMYKHalftone 濾鏡.)

        c. 需要二個或多個影像. 用來合成影像的濾鏡或使用一個影像中的值來控制並
            處理另一個影像中的像素, 這都是典型地需要二個或多個影像. 其中一個輸入
            的影像能夠作為遮光影像(shading image), 影像遮罩, 背景影像或提供控制
            處理其它影像外觀時的查詢值之來源. (例如, 在 Core Image Filter Reference
            中, 查看 CIShadedMaterial 濾鏡.)

         當要處理一個影像時, 你有責任去建立一個包含適當輸入資料的 CIImage 物件.

         注意: 雖然 CIImage 物件是與影像資料相關聯的, 它本身並不是影像. 你可以將
                    CIImage 物件看成是一個影像的配方(recipe). 一個 CIImage 物件擁有產生
              一個影像
所需要的所有資訊, 但是 Core Image 實際上直到被告知去作時,
                   才會描繪出影像. 這個延遲求值(lazy evaluation)的方法(查看
                   "Filter Clients and Filter Creators" ) 允許 Core Image 盡可能地高效率運作.

    2. 每個原始影像的像素會被 CISampler 物件所取走(fetched), 或只是一個取樣器
         (sampler). 就如其名稱所建議的, 取樣器會取回影像的樣本並且將它們提供給
         kernel. 濾鏡創造者要為每個原始影像提供取樣器. 濾鏡委託人不需要知道有關
         取樣器的任何事情. 

         一個取樣器定義了以下的事情:
         a. 協調的轉換(coordinate transform): 如果沒有需要轉換的話, 可以是本身的轉換.

         b. 竄改模式(interpolation mode): 可以是最鄰近的樣本或是雙線性的(bilinear)
             竄改(此為預設).

         c. 包裹模式(wrapping mode): 當樣本區域位在原始影像之外時, 明確說明如何產生
             像素 ---- 使用透明黑(transparent black)或限制到其範圍(clamp to the extent).

         濾鏡創造者在 kernel 中定義每個像素的影像處理計算, 但是 Core Image 處理那些
         計算的實際實作. Core Image 決定使用 GPU 或 CPU 來執行計算. Core Image 經由
         OpenGL 來實作硬體光柵化處理(rasterization: 由向量圖形轉換為光柵圖形的過程).
         它經由特別調整用來為片斷(fragment)求值程序的模擬環境中實作了軟體的光柵化
         處理, 其隨著非投射性(nonprojective)紋理(texture)來查詢大的四邊形
         (quadrilaterals or quads).

    3. 雖然像素處理的路徑是從原始影像到目的地, 但是 Core Image 所使用的計算路徑
         從目的地開始, 並且它的工作方式是反向回到原始像素的, 如圖 1-6 所示.

        這個反向的計算也許似乎不方便使用, 但是它實際上在任何的計算中最小化了
        所使用的像素數量. 提供選擇的東西(二擇一), Core Image 沒有使用的是處理
        所有原始像素的暴力(brute force)方法, 接著之後決定目的地需要什麼東西.
        近一點來看圖 1-6.

    4. 假定在圖 1-6 中的濾鏡執行某些種類的合成操作, 例如 source-over 合成
        (上面的圖形覆蓋了下面的圖形). 濾鏡委託人想要將二個影像作部分重疊, 所以
        只有每個影像的一小部份會合成以達到結果, 如圖的左邊所示. 更進一步來看,
        目的地應該會是什麼樣子? Core Image 能夠決定從哪個原始影像來的資料會對
        最後的影像(目的地)發生作用, 並且只限制在那些原始影像中作計算. 結果, 取樣器
        只會在原始影像遮蔽的地區取回樣本, 如圖 1-6 所示.

    5. 注意, 在圖中標記為 domain of definition 的箱子(box). 這個定義的範圍是將來
        要限制住計算的方式. 它是一個在所有像素都透明之外的地區(那就是 alpha 成分
        等於零). 在這個例子中, 定義的範圍跟目的影像正確地相符. Core Image 提供了一個
        CIFilterShape 物件用來定義這個區域. CIFilterShape 類別提供了一些方法可以
        用來: 定義矩形的形狀, 轉換形狀, 以及在多邊形上執行嵌入(inset), 聯集(union),
        與交集 (intersection)的操作. 例如, 假如你使用一個小於遮蔽地區的矩形定義一個
        濾鏡的外形, 如圖 1-6 所示, Core Image 就會使用該資訊作為未來在計算中限制
        所使用的原始像素.

    6. Core Image 用其它的方式來提升高效率的處理. 它執行聰明的快取與編譯器最佳化,
        以致能適合如即時錄影控制的任務. 它對任何評估為多次出現的資料集, 快取了中間
        的結果(intermediate results). 每當增加一個新的影像會造成快取成長太大時,
         Core Image 以最近最少使用(least-recently-used)的次序來收回資料. 這意謂著:
        當那些被使用一次經過一段時間之後, 有需要時可能會被移入或移出快取, 而繼續
        存在快取中的物件會頻繁地重復被使用. 你的應用程式得益於 Core Image 的快取,
        而無須知道快取是如何實作的細節. 然而無論什麼時候, 你卻可以藉由重復使用物件
        (影像, contexts, 等等)來獲得最佳的效能.         

    7. Core Image 藉由在 kernel 及 pass levels 中使用傳統的編輯(compilation)技術,
        也可以獲得重大的效能. Core Image 採用的方法是: 配置最小化的註冊, 包括
        暫時註冊的數目(每個 kernel), 與暫時的像素緩衝器(每個濾鏡的圖). 編譯器執行
        CSE(Common Subexpression: 共同子運算式)並且作監視點最佳化
        (
peephole optimization), 在讀取資料相依的紋理時自動識別, 這些是基於之前
        的計算, 並且它們不是資料相依的. 又一次, 你自己無需擔心編輯(compilation)
        技術的細節. 重點為 Core Image 是懂硬體的; 每當可行的話, 它會使用 GPU
        力量, 並且採用敏捷的方式.

© 2004, 2011 Apple Inc. All Rights Reserved. (Last updated: 2011-10-12)

2012年1月29日 星期日

Filter4Cam 學習之 Core Image Filter With CATransition

since: 2012/01/29
update: 2012/01/29


A. 說明  
      這篇文章主要是參考之前的二篇文章: Filter4Cam 學習之 PocketCoreImage
      與 Filter4Cam 學習之 ViewTransitions 的內容. 延續 PocketCoreImage 的專案,
      利用 Core Animation transitions 來達成濾鏡套用時的特效切換. 目前只適用在
      靜態的圖片上, 動態的即時影片尚未測試.

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

B. 開啓 PocketCoreImage 專案中的 ViewController.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add: 確認有 import QuartzCore/QuartzCore.h 進來
#import <QuartzCore/QuartzCore.h>
#import "FilteredImageView.h"

//@update
@interface ViewController : UIViewController <UINavigationControllerDelegate>
{
    //@add
    UITableView *tableView;
   
    // Array of CIFilters currently applied to the image.
    NSMutableArray *_filtersToApply;
   
    // Array created at startup containg the names of filters that can be applied to the image.
    NSArray *_availableFilters;
   
    //@add for CATransition
    BOOL transitioning;
}
....

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

C. 開啓 ViewController.m 檔案, 修改如下:
    說明: 以下三種情況會作特效切換.
              1. 按下 "Clear All", 清除所有的濾鏡效果.
              2. 選取一個濾鏡來套用. 
              3. 取消已選取的濾鏡.
....
//@add for CATransition
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    transitioning = NO;
}
....
// 按下 "Clear All", 清除所有的濾鏡效果.
- (IBAction)clearFilters:(id)sender
{
    //@add for CATransition
    if(!transitioning)
    {
        CATransition *transition = [CATransition animation];
        transition.duration = 1.20;

        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

        transition.type = @"suckEffect";
        transitioning = YES;
        transition.delegate = self;
        [self.imageView.layer addAnimation:transition forKey:nil];
    }

    [_filtersToApply removeAllObjects];
   
    // Instruct the filtered image view to refresh
    [_imageView reloadData];
   
    // Instruct the table to refresh.  This will remove
    // any checkmarks next to selected filters.
    [_tableView reloadData];
}
....
// 選取一個濾鏡來套用. 
- (void)addFilter:(NSString*)name
{
    //@add for CATransition
    if(!transitioning)
    {
        CATransition *transition = [CATransition animation];
        transition.duration = 1.20;

        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

        transition.type = @"suckEffect";
        transitioning = YES;
        transition.delegate = self;
        [self.imageView.layer addAnimation:transition forKey:nil];
    }   
   
    // Create a new filter with the given name.
    CIFilter *newFilter = [CIFilter filterWithName:name];
    // A nil value implies the filter is not available.
    if (!newFilter) return;
   
    // -setDefaults instructs the filter to configure its parameters
    // with their specified default values.
    [newFilter setDefaults];
    // Our filter configuration method will attempt to configure the
    // filter with random values.
    [ViewController configureFilter:newFilter];
   
    [_filtersToApply addObject:newFilter];
   
    // Instruct the filtered image view to refresh
    [_imageView reloadData];
}
....
// 取消已選取的濾鏡.
- (void)removeFilter:(NSString*)name
{
    //@add for CATransition
    if(!transitioning)
    {
        CATransition *transition = [CATransition animation];
        transition.duration = 1.20;

        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

        transition.type = @"suckEffect";
        transitioning = YES;
        transition.delegate = self;
        [self.imageView.layer addAnimation:transition forKey:nil];
    }   
      
    NSUInteger filterIndex = NSNotFound;
   
    // Find the index named filter in the array.
    for (CIFilter *filter in _filtersToApply)
        if ([filter.name isEqualToString:name])
            filterIndex = [_filtersToApply indexOfObject:filter];
   
    // If it was found (which it always should be) remove it.
    if (filterIndex != NSNotFound)
        [_filtersToApply removeObjectAtIndex:filterIndex];
   
    // Instruct the filtered image view to refresh
    [_imageView reloadData];
}

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

D. 編譯並執行:


Filter4Cam 學習之 ViewTransitions

since: 2012/01/29
update: 2012/01/29

reference: iOS Developer Library - ViewTransitions
sample code: ViewTransitions.zip

A. 有關 ViewTransitions
     1. 這個 ViewTransitions 範例應用程式, 展示如何在二個 view 之間使用內建的
         Core Animation transitions(轉變) 來執行切換特效. 藉著觀看這些程式碼,
         你將會瞭解如何使用 CATransition 物件去設定並控制 transitions.

     2. 這篇文章, 參考 apple 的文件, 並且調整成採用 iOS5 的 storyboardARC
         來建立整個專案.

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

B. 說明
    1. 要試驗這個範例, 用 Xcode 編譯並在模擬器或實機上執行. 按下 "Transition"
       按鈕來從一張圖片執行切換特效到另一張圖片. 這個應用程式採用隨機的方式
       來選擇轉換的效果.

    2. 套件清單:
        a. AppDelegate.h / .m
            隸屬 UIApplication delegate 類別, 為應用程式主要的 controller.

        b. main.m
            ViewTransitions 應用程式的主要進入點.

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

C. 新增專案
   1. Xcode > File > New > New Project...
       iOS > Application > Single View Application > Next
       Product Name: ViewTransitions
       Device Family: iPhone
       Use Storyboard: checked
       Use Automatic Reference Couting: checked
       > Next > Create

   2. 將 QuartzCore.framework 加入到專案裡:

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

D. 基本 UI 配置
     點選 MainStoryboard.storyboard 檔案.
     1. 拖拉一個 Toolbar 到 UI 的最下方, 它會自動夾帶一個 Bar Button Item.
     2. 在 UI 上點二下 Bar Button Item 將其顯示名稱改成: Transition
     3. 拖拉二個 Flexible Space Bar Button Item 分別到 Bar Button Item 的左右方.

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

E. 開啓 ViewController.h 檔案, 調整如下:

#import <UIKit/UIKit.h>
//@add
#import <QuartzCore/QuartzCore.h>

@interface ViewController : UIViewController
{
    //@add
    UIView *containerView;
    UIImageView *view1;
    UIImageView *view2;
    BOOL transitioning;
}

//@add
@property (nonatomic, strong) IBOutlet UIView *containerView;

//@add
-(IBAction)nextTransition:(id)sender;
-(void)performTransition;

@end

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

F. 調整 UI 連結設定
   點選 MainStoryboard.storyboard 檔案.
   1. 將 View Controller 的 Outlet: containerView 連結到 UI 上的 view.

   2. 將 View Controller 的 Action: nextTransition: 連結到 UI 上的 Bar Button Item.

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

G. 開啓 ViewController.m 檔案, 調整如下:

#import "ViewController.h"

@implementation ViewController

//@add
@synthesize containerView = _containerView;
....

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //@add: Create the two views to transition between, and add them to our
    //             containerView. We'll hide the second one
until we are ready to
    //             transition to it.

    UIImage *image1 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image1.jpg" ofType:nil]];
    view1 = [[UIImageView alloc] initWithImage:image1];
   
    UIImage *image2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image2.jpg" ofType:nil]];
    view2 = [[UIImageView alloc] initWithImage:image2];
    view2.hidden = YES;
   
    [self.containerView addSubview:view1];
    [self.containerView addSubview:view2];
   
    transitioning = NO;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    //@add
    self.containerView = nil;
}

//@add
-(void)performTransition
{
    // First create a CATransition object to describe the transition
    CATransition *transition = [CATransition animation];
    // Animate over 3/4 of a second
    transition.duration = 0.75;
   
    // using the ease in/out timing function
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
   
    // Now to set the type of transition. Since we need to choose at random,
    // we'll setup a couple of arrays to help us.

    NSString *types[4] = {kCATransitionMoveIn, kCATransitionPush, kCATransitionReveal, kCATransitionFade};

    NSString *subtypes[4] = {kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom};
    
    int rnd = random() % 4;
    transition.type = types[rnd];

    if(rnd < 3) // if we didn't pick the fade transition, then we need to set a subtype too
    {
        transition.subtype = subtypes[random() % 4];
    }
   
    // Finally, to avoid overlapping transitions we assign ourselves as the delegate
    //  for the animation and wait for the
-animationDidStop:finished: message.
    //  When it comes in, we will flag that we are no longer transitioning.

    transitioning = YES;
    transition.delegate = self;
   
    // Next add it to the containerView's layer. This will perform the transition
    // based on how we change its contents.

    [self.containerView.layer addAnimation:transition forKey:nil];
   
    // Here we hide view1, and show view2, which will cause Core Animation
    // to animate view1 away and view2 in.

    view1.hidden = YES;
    view2.hidden = NO;
   
    // And so that we will continue to swap between our two images,
    // we swap the instance variables referencing them.

    UIImageView *tmp = view2;
    view2 = view1;
    view1 = tmp;
}

//@add: Called when the animation completes its active duration or is removed
//             from the object it is attached to.

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    transitioning = NO;
}

//@add
-(IBAction)nextTransition:(id)sender
{
    if(!transitioning)
    {
        [self performTransition];
    }
}
....

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

H. 編譯並執行:
    說明: 1. 將二個 UIImageView 加到入目前的 view 中,  Apple 官方的作法
                  是在 AppDelegate.m 中處理(application:didFinishLaunchingWithOptions:)
                  , 而在這裡我是將它移到 ViewController.m 中處理(viewDidLoad), 因此
                   造成執行的結果中, UIImageView 會覆蓋到部分的 Toolbar.

              2. 解決辦法, 可在 MainStoryboard.storyboard 檔案中, 新增二個 UIImageView
                  , 並與 ViewController 作 IBOutlet 的連結關係.

    第一個 view:

    切換 view 中:

    第二個 view:

© 2010 Apple Inc. All Rights Reserved. (Last updated: 2010-06-28)

Filter4Cam 學習之 Parallel Universe 2

since: 2012/01/25
update: 2012/01/29

reference: The Universes of Max Tegmark
                    -> 主要參考: The Multiverse Hierarchy (a 2009 update)

等級三: 量子物理的多重世界

A. 前言
    圖5 說明: 等級一和等級三的差別. 鑑於等級一的平行宇宙是在遙遠的太空, 而等級三
                     的平行宇宙甚至就在這裡, 隨著量子事件造成傳統的現實分裂並分歧成
                     平行的故事情節. 然而等級三並沒有在等級一與等級二之外, 增加新的故事
                     情節.
  
    可能有第三種類型的平行世界, 不在很遠的地方, 就某種意義來說, 就在這裡. 如果
    基礎的物理方程式在數學上是所謂的一元化的, 到目前為止看起來好像都是如此,
    那麼在上圖(圖5)中, 宇宙將繼續保持分支成多個平行宇宙: 無論何時一個量子事件
    出現並且有了隨機的結局, 實事上所有的結局都發生了, 每一個都存在於平行宇宙
    的個別分支中. 這就是等級三的多重宇宙. 雖然比等級一和等級二有更多的爭論
    和可疑之處(之後將會看到), 出乎意料地, 這個等級沒有增加新類型的宇宙.

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

B. 量子難題(The quantum conundrum)

   1. 在二十世紀初期, 量子力學理論藉由說明原子的領域徹底改革了物理學, 它無法
       被牛頓力學的古典法則所容忍. 儘管理論明顯的成功, 仍然對它真實的意義有著
       狂熱激昂的爭論. 這個學說以非古典的條款明確地說明宇宙的狀態, 例如: 粒子
       的位置速度在數學上的物件而言, 稱為波函數. 根據薛丁格方程式
       (
Schrodinger equation), 這個狀態隨著時間的推移逐步形成數學上統一的方式,
       意謂著波函數在一個
抽象的無限次元空間稱為希爾伯特空間(Hilbert space)中
       轉動. 雖然量子力學經常被描述為隨機的天性與不確定的, 但是波函數在一個
       決定論的方式中發展, 卻是沒有隨機與不確定性.

   2. 麻煩的部分是如何連結這個波函數到我們觀察的東西上. 許多合理的波函數符合
       反直觀的情況, 例如: 在所謂的態疊加(superposition)中, 一隻貓同時間是生又是死.
       在 1920 年代, 物理學家們藉由假設: 當有任何人開始作觀測時, 波函數便會崩塌
       (
collapse)成某個明確的傳統結果, 來解釋離奇的現象. 這個附加的東西在解釋觀測
        行為上的確有好處, 但是它把一個優美, 公正的理論變得笨拙而不公正, 從那時到
        現在就沒有方程式明確說明崩塌是如何發生的. 一般在量子力學裡的內在隨機性,
        其實正是這個外加假定的結果, 也因此引發愛因斯坦(Albert Einstein)的異議說:
        "上帝不會擲骰子".  

   3. 多年來, 許多物理學家已經放棄支持在 1957 年由普林斯頓的畢業生: 艾弗雷特
       (Hugh Everett III)所發展的觀點. 艾弗雷特表明波函數崩塌的假設是多餘的.
       事實上, 純粹的量子理論對此並沒有提出任何的反駁. 雖然它預言一個古典的現實
       漸漸地分裂到許多現實的態疊加(superpositions)裡, 觀測者只是主觀地體驗隨機性
       的微小分裂(圖5), 從舊的崩塌假設隨著一致同意的或然率(德威特 de Witt 2003).
       這個古典世界的態疊加就是等級三的多重宇宙.

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

C. 等級三的平行宇宙像什麼呢?
   1. 艾弗雷特的多重世界解釋已經在物理學界內與外讓人困惑超過四十年了. 但是當
       在二個觀看物理理論方法之間作辨別時, 這個理論變得容易理解了. 一是從物理
       學家研究數學方程的外部觀點, 像一隻鳥從高處環視一片土地. 另一是從住在
       方程式所描述世界中的觀測者的內部觀點, 就像一隻住在被鳥所環視的土地上
       的青蛙.

   2. 從鳥的觀點, 等級三的多重宇宙是簡單的. 只有一個波函數. 它流暢地逐步形成
       並且確定地隨著時間的推移沒有任何種類的裂開或平行. 抽象的量子世界藉由
       發展中的波函數來描述, 其中包含一個廣大數量的平行古典故事線, 連續不斷地
       分裂與合併, 和一些量子現象一樣, 缺乏古典的描述. 從青蛙的觀點, 在完整的
       現實中, 觀測者只察覺到極小的部分. 藉由量子去相干(quantum decoherence)
       (
Dieter Zeh 1970 去相干化理論, ; Giulini et al. 1996)的過程, 他們能夠觀看到
       他們自己的等級一之宇宙 ---- 當維持一元化時, 與波函數崩塌時極相似, 可以
       防止讓他們看到自身上的等級三之平行複製.
       備註: 量子去相干: 由任意疊加態演變為某個對應於測量結果的本徵態.

   3. 無論什麼時候, 當觀測者被問問題, 即時作一個決定並給予一個回答, 腦中的
       量子效應引導成一個態疊加的結果, 例如: "繼續閱讀這篇文章" 與 "放下這篇文章".
       從鳥的觀點, 作決定的行動造成一個人分割成多重個複製: 其中一個繼續閱讀, 其中
       另一個不再閱讀. 然而從青蛙觀點, 相對於其它的分割, 無法察覺到每個自我意識
       的改變並且僅僅察覺分歧只是微小的隨意: 一個確鑿的可能性去閱讀或不閱讀.

   4. 這聽起來可能奇怪, 相同確切的情況甚至發生在等級一的多重宇宙. 你已經明顯地
       決定繼續閱讀文章, 但是在遙遠銀河中其中的另一個我, 在文章的第一段後, 就放下
       雜誌了. 在等級一與等級三之間的唯一差異是你居住在哪裡. 在等級一, 他們住在
       別處令人滿意且熟悉的三維空間. 在等級三, 他們住在無限維度希爾伯特空間的量子
       分支中.(圖5)

2012年1月24日 星期二

Filter4Cam 學習之 Core Image Programming Guide 1-1

since: 2012/01/23
update: 2012/01/24

reference: Core Image Programming Guide: Core Image Concepts

Core Image 概念之一

A. 導言
   1. 從 Mac OS X v10.4 開始, Core Image 是一個可用的能擴張的架構, 用來接近即時的
       , 像素-準確的(pixel-accurate) 對圖片及影片作影像處理. 你可以經由 Core Image
       內建的濾鏡或開發者自行建立的濾鏡來執行以下類型的操作:
       a. 裁切圖片(Crop images).
       b. 校正顏色, 例如: 執行白點調整(white point adjustment).
       c. 套用濾鏡, 像是褐色調(sepia tone).
       d. 模糊(Blur)或銳利(sharpen)圖片.
       e. 圖片合成(Composite).
       f. 對圖片變形(Warp)或幾何轉換(transform the geometry).
       g. 產生顏色, 象棋棋盤圖案, 高斯傾斜(Gaussian gradients), 及其它圖案的圖片.
       h. 對圖片或影片增加過渡特效(transition effects).
       i. 在影片上提供及時的顏色調整(color adjustment).

   2. Core Image 與其它圖像技術的關係:
      在 Mac OS X 中, Core Image 是與其它的圖像技術整合在一起, 允許一起使用來完成
      廣泛的結果. 例如: 你可以將由 Quartz 2D (Core Graphics) 建立的圖片以及由
      OpenGL 產生的紋理, 再經由 Core Image 來作影像處理; 你也可以將由 Core Video
      播放的影片, 套用 Core Image 的濾鏡.

   3. 這個章節提供 Core Image 技術的概觀, 並描述如何於應用程式中使用這個程式介面.
      也討論到 Core Image 如何在背景運作以完成快速, 令人震驚的, 近乎即時的影像處理.

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

B. Core Image 與 GPU
   1. OpenGL 直至目前為高效能 2D 與 3D 圖像的工業標準, 已經成為前往 GPU
      (graphics processing unit) 的主要通道. 在以往, 如果你想要使用 GPU 來作
      影像處理, 你需要知道 OpenGL Shading 語言. Core Image 改變了這一切, 因為
      有了 Core Image, 你不需要知道 OpenGL 的細解, 就可以駕馭 GPU 的力量來作
      影像處理. Core Image 自動地為你處理 OpenGL buffers狀態管理. 假如 GPU
      因為某些因素而無法使用, Core Image 會退而使用 CPU 以確保應用程式能執行.
      你的軟體僅僅是在運行, Core Image 的運作對你而言是不透明的(opaque).

   2. Core Image 提供了由 Objective-C 語言實作的簡易使用之 API, 因此隱藏了低階
       影像處理的細節. Core Image API 是 Quartz Core 框架 (QuartzCore.framework)
       的一部分, 你可以經由在 CocoaCarbon frameworks 中連結這個框架來使用
       Core Image.

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

C. 濾鏡委託人(Clients)與濾鏡創造者(Creators)
   1. Core Image 是設計給二種類型的開發者: 濾鏡委託人(filter clients)與濾鏡開發者
      (filter creators). 如果你打算只使用 Core Image 濾鏡, 你就是一個濾鏡委託人. 而
      如果你打算去寫自己的濾鏡, 那你就是濾鏡開發者. 這一章節將從每個類型的開發
      者觀點來敘述 Core Image 濾鏡, 並且提供使用 Core Image 需要知道的事情之概觀.

   2. Core Image 有超過 100 個內建的濾鏡, 可供濾鏡委託人在其應用程式上使用影像
       處理. 在 Core Image Filter Reference 中描述著那些濾鏡. 內建的濾鏡清單可以被
       更改, 因此之故, Core Image 提供了一個方法用來查詢系統中可用的濾鏡有哪些.
       你也可以載入第三方開發者的濾鏡套件當成影像單位(image units), 你會在這章節
       的後續讀到更多有關影像單位的內容.

   3. 你可以取得濾鏡清單或縮小查詢範圍來取得符合特定分類的濾鏡, 例如變形濾鏡
        (distortion filters) 或可與影片一同運作的濾鏡. 一個濾鏡的分類明確地說明了
       效果的種類: 模糊(blur), 變形(distortion), 產生器(generator), 等等, 或者預期要
       使用靜態圖片, 影片, 非方形像素(nonsquare pixels) 等等. 一個濾鏡可以是超過一
       種分類的成員. 濾鏡也有顯示名稱(display name), 用來在使用者界面上顯示;
       而濾鏡名稱(filter name)是用來以程式化的方式存取濾鏡名稱的. 你會在
        Using Core Image Filters 中, 知道如何去執行查詢.

   4. 大部分的濾鏡都有一個或多個輸入的參數, 用來控制如何完成影像處理. 每一個輸入
       的參數都有一個屬性類別(attribute class), 用來指明資料型別, 例如: NSNumber.
       輸入的參數可以選擇性地有其它的屬性, 例如: 預設值, 允許的最小和最大值, 參數的
       顯示名稱, 以及其它在 CIFilter 中描述的屬性.

   5. 舉例來說, 單色(color monochrome)濾鏡有三個輸入的參數: 待處理的影像, 單色
       顏色, 色彩飽和度(color intensity). 你除了提供影像, 還可選擇性地設定顏色與
       飽和度. 大部份的濾鏡, 包括單色濾鏡, 對於非影像的輸入參數都有預設值. 如果你
       不提供設定值給輸入的參數, Core Image 會使用預設值來處理影像.

   6. 濾鏡的屬性以鍵值成對(key-value pairs)的方式來儲存. 鍵是固定不變的, 用來識別
      不同的屬性; 而值的設定是搭配與鍵的組合. Core Image 屬性的值, 典型地是以下的
      資料型別之一:

      a. Strings (NSString objects): 例如: 顯示名稱(display names).

      b. Floating-point numbers (NSNumber data type): 用來指定數量(scalar), 如: 飽和度
          等級(intensity levels), 半徑(radii).

      c. Vectors (CIVector objects): 可以是 2 ~ 4 個元素(elements), 每個元素都是浮點數.
          用來指定位置, 地區與顏色值.

      d. Colors (CIColor objects): 指定顏色值與色彩空間(color space)來設值.

      e. Images (CIImage objects): 輕量級的物件, 用來指定影像的製作方法.

      f. Transforms (NSAffineTransform objects): 指定一個仿射的轉換來套用到影像上.

   7. Core Image 使用: 鍵-值編碼(key-value coding), 這意謂著: 藉由 NSKeyValueCoding
     協定
(protocol)所提供的方法, 你可以取得設定濾鏡的屬性值.
       註: Key-value coding 是 Objective-C 中用來存取物件屬性的機制. 為了有效地使用
             Core Image, 你必須熟悉 NSKeyValueCoding protocol, 更多資訊請看:
             Key-Value Coding Programming Guide.

   8. 典型的濾鏡組成成分:
      a. 上圖中, 所指出的色彩較暗地區是「藏在表面之下的機制或結構」(under the hood)
          ---- 這個部分, 濾鏡委託人可以不需要知道任何事, 但是濾鏡開發者必須瞭解的.
         沒有被遮蔽的部分, 有二個方法: attributesoutputImage, 是提供給濾鏡
         委託人呼叫的.

         濾鏡的 attributes 方法, 提供呼叫以獲得濾鏡屬性清單(之前討論過的), 包含濾鏡
         的輸入參數以及要在使用者介面上顯示的濾鏡名稱.

         outputImage 方法, 組合並儲存產生影像所必要的計算, 但實際上未促使
         Core Image 處理影像. 這是因為 Core Image 採用延遲求值的方式
         (
lazy evaluation). 換句話說, Core Image 直到實際要在目標上處理繪製像素的
         時刻到來時, 才會開始處理影像. outputImage 方法所做的所有事情就是: 組合
         繪製的時刻來臨時, Core Image 所需要的計算, 並且在 CIImage 物件中, 儲存這些
         計算(或影像製作方法). 當明確地去呼叫繪製影像的方法, 如:
         drawImage:atPoint:fromRect:drawImage:inRect:fromRect: , 實際的影像便
        會被描繪出來(因此, 計算就被執行了).

      b. Core Image 儲存了計算, 直到應用程式發佈要繪製影像的指令. 在那時, Core Image
          開始計算結果. 延遲求值是讓 Core Image 更快更有效率的實踐方式之一. 在描繪的
          時刻, Core Image 能夠知道是否有超過一個的濾鏡需要應用到影像上. 如果是的話,
          它可以連接複合的製作方法成為單一的操作, 這意謂著每個像素只會被處理一次,
          而不是許多次.

   9. 延遲求值的工作流程
          上圖, 說明了 lazy evaluation 如何在複合的操作中, 讓影像處理更有效率. 最後的
          影像是原始版本按比例縮減(scaled-down)的結果. 以一個大的影像為例, 在縮減
          影像尺寸之前先套用色彩調整濾鏡, 會比先縮減影像尺寸然後再套用色彩調整濾鏡
          需要更多的
處理能力. Core Image 會一直等待到最後一個可能套用濾鏡的時刻,
          然後能夠以相反的順序來執行操作, 這樣更有效率.

   10. 對濾鏡開發者而言, 在濾鏡中最令人興奮的組成成份是核心(kernel), 那是每個濾鏡
         的心臟. kernel 具體指定了會在每個原始影像像素執行的計算. Kernel 的計算可以
         是非常簡單或複雜. 一個不做任何事的濾鏡擁有非常簡單的 kernel, 這個 kernel 能夠
         簡單地傳回原始的像素: destination pixel = source pixel 

   11. 濾鏡開發者使用不同的 OpenGL Shading 語言 (glslang) 來詳細指明每個像素的
         計算. (請看: Core Image Kernel Language Reference.) 對濾鏡委託人而言, kernel
         是不透明的. 一個濾鏡實際上可以使用數個核心慣例(kernel routines), 將輸出的一
         方傳入輸入的另一方. 教導如何寫出客制的濾鏡, 請看: Creating a Custom Filter
         註: 濾鏡用來處理像素的 kernel 是實際上的例行程序, 由 Core Image 不同的
               glslang 所寫成. CIKernel 物件是一個包含有 kernel routine Core Image
               物件. 當你建立一個濾鏡時, 就會看到 kernel routine 存在於它自己的檔案裡, 其
               副檔名為 .cikernel. 藉著有計劃性地傳入包含著 kernel routine 的字串, 就可以
              建立一個 CIKernel 物件.

   12. 影像單位
          濾鏡開發者能夠使用 NSBundle 類別所指定結構 藉由將濾鏡套裝成外掛
          影像單位(
image unit), 來讓客制的濾鏡供其它的應用程式使用. 如上圖所示, 一個
          影像單位可以包含超過一個的濾鏡. 舉例來說, 你可以寫一套濾鏡用來執行不同種類
          的邊緣偵測(edge detection), 並且將它們套裝成一個影像單位. 濾鏡委託人可以
          使用 Core Image API 來將影像單位載入, 並且獲得在其影像單位中的濾鏡清單.
          參考 Loading Image Units 來得到基本的資訊. 參考 Image Unit Tutorial , 有更
          深入的範例與詳細的資訊用來寫出濾鏡並將其套裝成獨立的
影像單位.  

© 2004, 2011 Apple Inc. All Rights Reserved. (Last updated: 2011-10-12)

2012年1月22日 星期日

Filter4Cam 學習之 Parallel Universe 1

since: 2012/01/22
update: 2012/01/22

reference: The Universes of Max Tegmark
                    -> 主要參考: The Multiverse Hierarchy (a 2009 update)

多重宇宙階系(The Multiverse Hierarchy)

A. 前言
     1. 平行宇宙(Parallel universes) 目前已在書籍, 電影中大量盛行, 甚至是笑話: 你在許多
         平行宇宙中通過了考試, 但不是目前這一個. 然而, 它們的爭議性也隨同流行度而
         上升. 並且重要的問題是: 它們是隸屬於科學的範圍內, 或只是無聊的推測. 自從許多
         被提出來議論的不同類型平行宇宙, 忘了區別它們也造成了困惑的來源.

   2. 自從大爆炸擴張開始, 距離上最遠可以觀察到 140 億光年. 最遠可以看到的物體目前
        約為 4 * 10 的 26 次方公尺遠(說明: 因為宇宙的膨脹, 在發射的光線到達我們之後,
        我們所能看到的最遠距離已經縮減, 目前約為 400 億光年遠), 並且(半徑)周圍的球體
        定義了我們所看見的宇宙, 也稱為我們的哈伯容積(Hubble volume), 視野容積
        (horizon volume)或簡單地稱為: 宇宙. 在這篇文章中, 會審視物理學上涉及到平行
        宇宙的理論, 形式上表現出合乎常情的 4 個級別的多重宇宙階系, 其允許逐漸增加
        較大的多樣性.

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

B. 多重宇宙級別簡介
     1. 等級ㄧ: 在我們宇宙的水平分佈更遠處的範圍
        一般對宇宙論膨脹的預言是歷經多態的無窮太空, 其包含了哈伯容積所了解到的所有
        初始狀態 - 包含: 在 10 的 10 次方的 29 次方公尺遠處, 找到與你相同的複製品.
        特徵: 相同的物理法則, 不同的初始狀態.
        假設: 無限的太空, 多態的物質分佈.
        跡象: a. 宇宙微波背景輻射測量結果指出宇宙早期為平坦分佈的.
                   b. 無限的太空, 大規模的平滑.
         ---- 最簡單的模型

     2. 等級二: 其它在後方膨漲的氣泡
         給定基本的物理法則, 比如物理學家有一天希望能在 T-shirt 上用方程式來捕獲;
         不同範圍的空間能夠顯出有效的物理法則(物理常數, 維度, 粒子內容, 等等)對應
         到不同地區的最低限度之可能性.
        特徵: 相同的基本物理方程式, 但可能擁有不同的物理常數, 粒子, 維度.
        假設: 混亂的膨脹發生.
        跡象: 膨脹理論解釋了平坦的空間, 無變化級別的波動, 解決水平問題以及磁單極問題
                  並且能夠自然解釋諸如氣泡.
         ---- 解釋了優化的參數

     3. 等級三: 量子物理的多重世界
        在統一的量子力學中, 其它波函式的分支從本質上來說無法新增事物, 以歷史觀點
        來看出乎意料的最具有爭議性.
        特徵: 與等級二的特徵相同.
        假設: 一元化的物理.
        跡象: a. 單一物理的實驗性支持.
                   b. AdS/CFT對偶, 假設甚至連量子重力都是統一的.
                   c. 去相干(Decoherence)實驗驗證.
         ---- 最簡單的數學模型

     4. 等級四: 其它數學結構
        其它的數學結構, 對先前的 T-shirt 而言, 給予不同的基礎物理方程式.
        特徵: 不同的基本物理方程式.
        假設: 數學上的存在等於物理上的存在.
        跡象: a. 在物理上不合理的有效數學式.
                   b. 回答惠勒( J. Wheeler) / 霍金( s. w. hawking )的問題:
                       為什麼是那些方程式, 不是別的?

     5. 四個級別的多重宇宙階系圖

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

C. 討論
   1. 關鍵的問題不是: 因此是否有多重宇宙?(因為等級一是最不具爭論性的宇宙論
       一致模型) 而是有多少個多重宇宙的級別?

   2. 底下, 我們將在尺度上討論爭論的跡象, 並且討論這是科學或是哲學. 目前為止, 關鍵
      之處是要記得: 平行宇宙不是一個理論, 而是一個預知的確信理論. 當我們無法觀察並
      且測試它的所有預測(僅僅至少其中之一), 一個理論就可被曲解. 考慮以下的類推:
      因為愛因斯坦的廣義相對論(General Relativity)理論, 成功地預測了許多我們可以
      觀察到的事情. 我們也對於無法觀測的預測嚴肅地看待, 例如: 在黑洞的事界(event
      horizons) 內部, 空間仍持續著; 以及(相反地, 在早期的誤解)在事界裡沒有事情發生. 
      同樣地, 成功地預測宇宙論膨脹的理論以及統一的量子力學(數學上最簡單的量子力學
      版本就是一元化的, 缺乏爭論的過程: 波動方程式崩潰), 已使一些科學家嚴肅地面對
      其它的預測, 包含多種類型的平行宇宙.

   3. 在探究細節前, 讓我們用二個告誡的評論作為達成協議. 傲慢與缺乏想像力會多次地
       讓人低估巨大的物理世界, 並且僅僅因為無法站在有利位置觀測, 而不加考慮事情, 這
       讓人聯想到鴕鳥將頭埋在沙中. 此外, 最近的理論指出大自然可能正在欺騙我們.

       愛因斯坦教我們空間並非只是一個無聊的靜態空白, 而是一個動態的實體, 能夠延伸
       (擴展的宇宙), 顫動(重力波)並且彎曲(引力). 尋找大統一理論也提議空間能夠"凍結",
       有可能在地上於不同的相(phases)之間轉換, 就像水可以是固態, 液態或氣態. 在不同
       的相中, 有效的物理法則(粒子, 對稱性)就可能不同了.

       一隻未曾離開過大海的魚, 可能錯誤地斷定水的性質是一種普遍現象, 沒有瞭解到
       還有冰及蒸氣的存在. 我們可能比魚還聰明, 但是可能會有類似愚蠢: 宇宙論的膨脹
       擁有騙人的特性: 在特定的階段延伸一小片的空間, 所以它填裝我們整個可觀察的
       宇宙, 這是潛在地欺騙我們. 去誤解了我們當地的情況對宇宙律而言, 應該會在那件
       T-shirt 上發生. 備註: 膨脹是在星系與星系(或星團與星團)之間的空間膨脹.

Filter4Cam 學習之 Core Image Programming Guide 0

since: 2012/01/22
update: 2012/01/22

reference: Core Image Programming Guide: Introduction to Core Image Programming Guide

介紹

A. 導言
    1. Core Image 是一種內建在 Mac OS X v10.4iOS 5.0 的影像處理技術, 它可以操控
       具有編程控制的圖像硬體, 無論何時都可以提供接近即時的處理. Core Image 的應用
       程式介面提供對影片靜態圖片來存取固定影像濾鏡, 並提供對建立客制化濾鏡的
       支援.

    2. 重要說明:
        雖然這篇文件是被含括在參考文庫中, 但是它尚未對 iOS 5.0 的細部作更新. 即將到
        來的版本會詳細說明在 iOS 中 Core Image 的差別之處. 特別是, 關鍵的差異為 Core
        Image 在 iOS 上並沒有包含可以建立客制化影像濾鏡的能力.

    3. 開發者設計一個支援影片或靜態圖片處理的應用程式, 或著想要寫一個影像處理的
        濾鏡能夠讓其它的應用程式使用, 這篇文件將會對你有所幫助.

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

B. 文件組織
      這篇文件是由以下的章節所組織起來的.
      1. Core Image Concepts
         描述 Core Image模型, API 的組織架構, 以及定義使用 Core Image API 所需
         瞭解的關鍵概念.

      2. Using Core Image Filters
          展示如何去設定並使用 Core Image 來獲得可用的濾鏡清單以及其屬性; 對影像作
          處理, 套用轉換的效果, 影像動態系統, 以及對影片套用濾鏡.

      3. Creating Custom Filters
          敘述如何去寫出自己的濾鏡, 並且使用在應用程式中. 也討論有關可執行與不可執行
          濾鏡的爭議.

      4. Packaging Filters as Image Units
          說明如何套裝一個濾鏡並視為一個影像單位, 以讓其它的應用程式可以載入並且
          使用你寫的濾鏡.

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

C. 相關資源
      Apple 提供以下繪圖圖像的額外資源:
     1. Core Image Reference Collection
         提供在 Core Image API 中可用的物件方法的詳細描述.

     2. Core Image Filter Reference
         描述 Apple 在 Mac OS X 中提供的影像處理濾鏡, 並且展示影像在濾鏡處理之前
         與之後如何表現.

     3. Core Image Kernel Language Reference
         描述在客制化濾鏡時, 建立 kernel routines 所使用的語言.

     4. Image Kit Programming Guide
         包含如何在 Image Kit framework(在 Mac OS X v10.5 中被引進) 中, 以附加的方式
         使用 Core Image 的相關資訊, 以提供一個使用者介面來瀏覽 Core Image 濾鏡並且
         設定輸入的參數.

     5. NSCIImageRep
         有附加的應用程式工具, 可以讓 Core Image 跟 NSImage 模型一同運作.

     6. Quartz 2D Reference Collection
         為 Quartz 2D 資料型別的一個完整參照, 也使用在 Core Image framework 中.

     7. Quartz 2D Programming Guide
         包含如何去建立 Quartz 2D 影像與色彩空間, 以及如何用 Quartz 執行 2D 描繪
         的資訊.

     8. The OpenGL website (www.opengl.org)
         提供 OpenGL Shading 語言(glslang)的資訊. 你可以到那裡獲得 glslang 語法的資訊,
         其為使用客制化濾鏡時, 用來詳細指明 kernel routines 的子集合.

     9. Quartz Composer User Guide
         描述如何使用 Quartz Composer 開發工具, 它是由 Mac OS X v10.4 (與之後) 所提供
         的, 用來處理與描繪圖形資料. 你可以不需要寫任何程式碼, 使用 Quartz Composer
         來試驗內建的 Core Image 濾鏡. 你也可以使用 Quartz Composer 來測試 kernel
          routines
( 見 Use Quartz Composer to Test the Kernel Routine ).

     10. Core Video Reference
           包含對 Core Video API 的詳細描述.

     11. Core Video Programming Guide
           描述 Mac OS X 數位影片模型, 並展示如何使用 Core Video API.


© 2004, 2011 Apple Inc. All Rights Reserved. (Last updated: 2011-10-12)

2012年1月17日 星期二

Filter4Cam 學習之 PocketCoreImage

since: 2012/01/15
update: 2012/01/17

reference: iOS Developer Library - PocketCoreImage
sample code: PocketCoreImage.zip    

A. 有關 PocketCoreImage
   1. 這個例子示範應用 Core Image 濾鏡到靜態的圖片上. 濾鏡的參數自動設定
       (使用隨機數字), 並且可以在同時間套用多重濾鏡. 這個例子採用預先調整好的
      濾鏡列表讓使用者選擇, 在程式碼中會示範向系統請求濾鏡列表的方式.

   2. 這篇文章, 參考 apple 的文件, 來建立整個專案.

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

B. 說明
   1. 取得濾鏡清單
       濾鏡清單是在 -awakeFromNib 方法裡被建立的. 簡而言之, 雖然這裡使用的
       四個濾鏡是被手動指明的; 但是可以向 iOS 系統查詢到已安裝的濾鏡清單.
       以下的程式碼區段顯示了如何來達成.
{
    return [CIFilter filterNamesInCategories:[NSArray arrayWithObject:kCICategoryBuiltIn]];
}

      iOS 系統上已安裝的濾鏡依分類而群組化, 並且可以只查詢某分類的濾鏡.
      在對 -filterNamesInCategories 方法的查詢中, 明確指定多個分類將會回傳在那些
      分類中的交集. 沒有方式可以在二個分類中取得濾鏡的聯集. 除了從二個個別查詢的
      分類結果中將它們合併. 以下的程式碼區段顯示如何同時在顏色效果(Color Effect)
      與顏色調整(Color Adjustment)分類中, 取得濾鏡集合
{
    NSArray *installedFilters = [CIFilter filterNamesInCategories:[NSArray arrayWithObjects:kCICategoryBuiltIn, kCICategoryColorEffect, nil]];

    installedFilters = [installedFilters arrayByAddingObjectsFromArray:
                       [CIFilter filterNamesInCategories:[NSArray arrayWithObjects:kCICategoryBuiltIn, kCICategoryColorAdjustment, nil]]];

    return installedFilters;
}
      備註: a. 以下的網址可以取得查詢濾鏡分類的 keys 清單: CIFilter Class Reference
                 b. 以下的網址列出了 Apple 提供的完整 Core Images 濾鏡清單:
                     Core Image Filter Reference (目前, 並非所有列出的濾鏡清單都可以在
                     iOS 中使用)

   2. 濾鏡參數的設定
       大部份的濾鏡都包含數個參數可用來設定以改變輸出的影像. 一個濾鏡的實體能夠
       藉由呼叫 -attributes 查詢到相關資訊與其提供的參數. 你也可以命令 CIFilter 的實體
       藉由呼叫 -setDefaults 來自動設定原始的預設值.

       在這個範例中, 我們定義了一個方法: +(void)configureFilter:(CIFilter*)filter, 它帶著
       CIFilter 實體作為方法參數, 檢查 filter 可用的參數並將其設成隨機值. 在這個方法中
       所展示的技巧能夠輕易地被使用於: 實作一個動態的使用者介面, 允許使用者去設定
       濾鏡.

   3. 套用濾鏡到輸出的影像
       我們的 controller 管理著一個包含著許多 CIFilter 實體的 NSArray. 當
       FilteredImageView 需要自行繪出時, 它就會要求獲得此濾鏡清單, 並且為輸入的影像
       連續地套用每個濾鏡.

   4. 套件清單:
        a. FilteredImageView.m/h
            為 UIView 的子類別. 會從 data source 要求取得濾鏡清單, 並且將它們套用至輸入
            的影像, 再將結果影像畫到螢幕上.

        b. ViewController.m/h
            包含著 UI 的邏輯部分. 管理著套用濾鏡的 image view 與濾鏡清單.

        c. xxxFilterDetailCategory.m
           為 ViewController 類別的類目(category). 實作了一個方法: 利用隨機產生
           的值來設定濾鏡參數.
 
---------------------------------------------------------------------------------------------

C. 新增專案
   1. Xcode > File > New > New Project...
       iOS > Application > Single View Application > Next
       Product Name: PocketCoreImage
       Device Family: iPhone
       Use Storyboard: checked
       Use Automatic Reference Couting: checked
       > Next

   2. 將 CoreImage.framework 加入到專案裡:

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

D. 基本 UI 配置
      點選 MainStoryboard.storyboard 檔案. UI 的配置主要分為三個部分, 由上而下:
      Navigation Bar, UIViewTable View.

       1. 依序將: Navigation Bar, Navigation ItemBar Button Item 拖拉到 UI 最上方,
           並修改 Navigation Item Title 為: Filtered Image , Bar Button Item 名稱為:
           Clear All, 如下所示:

       2. 接著將 UIView 拖拉進來, 並調整大小與位置如下所示:

       3. 再將 Table View 拖拉到 UI 最下方,
           a. 選取 Table View, 並將屬性中的 Style 改成 Grouped,
               取消勾選
: Scrolling Enabled.

           b. 選取 Table View Cell, 並將屬性中的 Identifier 改成: filterCell
       4. 再選取整個 View, 將屬性中的 Background 設為:
            Group Table View Background Color


       5. 整個 UI 完成如下:

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

E. 在 ViewController 中, 調整 UI 相關的程式碼
   1. 開啓 ViewController.h 檔案, 調整如下:

#import <UIKit/UIKit.h>

//@interface ViewController : UIViewController
//@update: 加入 UINavigationControllerDelegate protocol
@interface ViewController : UIViewController <UINavigationControllerDelegate>
{
    //@add
    UITableView *tableView;
   
    // Array of CIFilters currently applied to the image.
    NSMutableArray *_filtersToApply;
   
    // Array created at startup containg the names of filters that can be applied to the image.
    NSArray *_availableFilters;
}

//@add
@property (nonatomic, strong) NSMutableArray *filtersToApply;
@property (nonatomic, strong) IBOutlet UITableView *tableView;

//@add
- (IBAction)clearFilters:(id)sender;

@end

//@add
@interface ViewController (FilterDetailCategory)

+ (void)configureFilter:(CIFilter*)filter;

@end

   2. 開啓 ViewController.m 檔案, 調整如下:

#import "ViewController.h"

@implementation ViewController

//@add
@synthesize filtersToApply = _filtersToApply;
@synthesize tableView = _tableView;

//@add
- (IBAction)clearFilters:(id)sender
{
}

//@add
#pragma mark - TableView

// Standard table view datasource/delegate code.
//
// Create a table view displaying all the filters named in the _availableFilters array.
// Only the names of the filters a stored in the _availableFilters array, the actual filter
// is created on demand when the user chooses to add it to the list of applied filters.
//

//@add
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

//@add
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [_availableFilters count];
}

//@add
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    static NSString *filterCellID = @"filterCell";
   
    UITableViewCell *cell;
   
    cell = [tableView dequeueReusableCellWithIdentifier:filterCellID];
    if(!cell)
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:filterCellID];
   
    cell.textLabel.text = [_availableFilters objectAtIndex:indexPath.row];
   
    // Check if the filter named in this row is currently applied to the image.  If it is,
    // give this row a checkmark.
    cell.accessoryType = UITableViewCellAccessoryNone;
    for (CIFilter *filter in _filtersToApply)
        if ([[filter name] isEqualToString:[_availableFilters objectAtIndex:indexPath.row]])
            cell.accessoryType = UITableViewCellAccessoryCheckmark;
   
    return cell;

}

//@add
- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return @"Select a Filter";
}

//@add
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}

//@add
- (void)awakeFromNib
{
    _availableFilters = [NSArray arrayWithObjects:@"CIColorInvert", @"CIColorControls", @"CIGammaAdjust", @"CIHueAdjust", nil];
}

   3. 接著, 將 IBOutlet UITableView *tableView 與 UI 上的 table view 作連結;
        - (IBAction)clearFilters: 與 UI 上的 Bar Button Item 作連結.

   4. UI 上的 UIView 稍後會做客制處理與相關連結.

   5. 編譯並執行.

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

F. 新增 FilteredImageView 類別
   1. 新增類別檔案:
       Xcode > File > New > New File >
       iOS > Cocoa Touch > Objective-C class > Next 
       Class: FilteredImageView
       Subclass of: UIView
       > Next

   2. 調整 FilteredImageView.h 檔案, 如下:
#import <UIKit/UIKit.h>

//@add a protocol for datasource used
@protocol FilteredViewDatasource <NSObject>

- (NSMutableArray*)filtersToApply;

@end


@interface FilteredImageView : UIView
{
    //@add
    CIImage *_filteredImage;
}

//@add
@property (nonatomic, weak) IBOutlet id<FilteredViewDatasource> datasource;
@property (nonatomic, strong) UIImage *inputImage;

//@add
- (void)reloadData;

@end

   3. 調整 FilteredImageView.m 檔案, 如下:
#import "FilteredImageView.h"

@implementation FilteredImageView

//@add
@synthesize datasource;
@synthesize inputImage = _inputImage;

//@add
// Requests the list of filters from the data source and applies each filter
// in order to the _inputImage.
- (void)reloadData
{
    if (!_inputImage)
        return;
   
    // Create a CIImage from the _inputImage.  While UIImage has a property returning
    // a CIImage representation of it, there are cases where it will not work.  This is the
    // most compatible route.
    _filteredImage = [[CIImage alloc] initWithCGImage:_inputImage.CGImage options:nil];
   
    // Retrieve the list of CIFilters to apply from our datasource.
    NSArray *filters = [self.datasource filtersToApply];
    if (filters)
        // Iterate through each filter setting our CIImage as the input and re-assigning
        // the filter's output back to our CIImage.  This creates a chaining effect.
        for(CIFilter *filter in filters) {
            [filter setValue:_filteredImage forKey:@"inputImage"];
            // Certain filters place restrictions on their input values that we may not have
            // accounted for
in the configuration method.  For example, CIColorCube
            // requires its parameter to be a power
of 2.  In such as case, the filter will throw
            // an exception when we ask it generate and image.
Catch the exception and
            // pretend nothing happened thereby bypassing the filter.

            @try {
                _filteredImage = filter.outputImage;
            }
            @catch (NSException* e) { }
        }
   
    // Inform UIKit that we need to be redrawn.
    [self setNeedsDisplay];
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
    //@add
    [super drawRect:rect];
   
    if (!_filteredImage)
        return;
   
    // This is the rect we'll draw our final image into.  By making it a bit smaller than our
    // bounds
we'll get a nice border.
    CGRect innerBounds = CGRectMake(5, 5, self.bounds.size.width - 10, self.bounds.size.height - 10);
   
    // To display the image, convert it back to a UIImage and draw it in our rect. 
    // UIImage takes
into account the orientation of an image when drawing which
    // we would have needed to worry about
when drawing it directly with Core Image
    // and Core Graphics calls.

    [[UIImage imageWithCIImage:_filteredImage] drawInRect:innerBounds];
}

//@add
- (void)setInputImage:(UIImage *)inputImage
{
    // Since Core Image filters must be operate on every pixel in an image, you may
    // want to
consider resizing an input image to the view size before applying any
    // filters.

    _inputImage = inputImage;
   
    [self reloadData];
}

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

G. 調整 ViewController 
    1. 修改 ViewController.h 檔案如下:
#import <UIKit/UIKit.h>
//@add
#import <QuartzCore/QuartzCore.h>
#import "FilteredImageView.h"
....
//@add
@property (nonatomic, strong) NSMutableArray *filtersToApply;
@property (nonatomic, strong) IBOutlet UITableView *tableView;
@property (nonatomic, strong) IBOutlet FilteredImageView *imageView;
....

    2. 修改 ViewController.m 檔案如下:
....
//@add
@synthesize filtersToApply = _filtersToApply;
@synthesize tableView = _tableView;
@synthesize imageView = _imageView;

//@add
// Action sent by the right navigation bar item.
// Removes all applied filters and updates the display.
- (IBAction)clearFilters:(id)sender
{
    [_filtersToApply removeAllObjects];
   
    // Instruct the filtered image view to refresh
    [_imageView reloadData];
   
    // Instruct the table to refresh.  This will remove
    // any checkmarks next to selected filters.
    [_tableView reloadData];
}

//@add
// Private method to add a filter given it's name.
// Creates a new instance of the named filter and adds
// it to the list of filters to be applied, then
// updates the display.
- (void)addFilter:(NSString*)name
{
    // Create a new filter with the given name.
    CIFilter *newFilter = [CIFilter filterWithName:name];
    // A nil value implies the filter is not available.
    if (!newFilter) return;
   
    // -setDefaults instructs the filter to configure its parameters
    // with their specified default values.
    [newFilter setDefaults];
    // Our filter configuration method will attempt to configure the
    // filter with random values.
    [ViewController configureFilter:newFilter];
   
    [_filtersToApply addObject:newFilter];
   
    // Instruct the filtered image view to refresh
    [_imageView reloadData];
}

//@add
// Private method to remove a filter given it's name.
// Updates the display when finished.
- (void)removeFilter:(NSString*)name
{
    NSUInteger filterIndex = NSNotFound;
   
    // Find the index named filter in the array.
    for (CIFilter *filter in _filtersToApply)
        if ([filter.name isEqualToString:name])
            filterIndex = [_filtersToApply indexOfObject:filter];
   
    // If it was found (which it always should be) remove it.
    if (filterIndex != NSNotFound)
        [_filtersToApply removeObjectAtIndex:filterIndex];
   
    // Instruct the filtered image view to refresh
    [_imageView reloadData];
}

//@add
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
   
    // Determine if the filter is or is not currently applied.
    BOOL filterIsCurrentlyApplied = NO;
    for (CIFilter *filter in _filtersToApply)
        if ([[filter name] isEqualToString:selectedCell.textLabel.text])
            filterIsCurrentlyApplied = YES;
   
    // If the filter is currently being applied, remove it.
    if (filterIsCurrentlyApplied) {
        [self removeFilter:[_availableFilters objectAtIndex:indexPath.row]];
        [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
    }

    // Otherwise, add it.
    else {
        [self addFilter:[_availableFilters objectAtIndex:indexPath.row]];
        [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
    }
   
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //@add
    _filtersToApply = [[NSMutableArray alloc] init];
   
    // p.s. 先自行將圖檔加入專案中
    _imageView.inputImage = [UIImage imageNamed:@"LakeDonPedro2.jpg"];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    //@add
    self.tableView = nil;
    self.imageView = nil;
}

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

H. 調整 UI 設定
      開啓 MainStoryboard.storyboard 檔案, 調整如下:
       1. 選取 UIView, 在 Identity 將其 Class 選為 FilteredImageView.

       2. 選取整個 View Controller,  在 Connections Tab 處將 Outlets: imageView
           連結到 UI 的 Filtered Image View

        3. 選取 Table View, 在 Connections Tab 處將 Outlets: dataSourcedelegate
            分別皆與 View Controller 作連結.

       4. 選取 Filtered Image View , 在 Connections Tab 處將 Outlets: dataSource
            與 View Controller 作連結.

   5. 編譯並執行.

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

I. 建立給 ViewController 使用的 Category
  1. 新增檔案:
      Xcode > File > New > New File >
      iOS > Cocoa Touch > Objective-C category > Next 
      Category: FilterDetailCategory
      Category on: ViewController
      > Next
    說明: 產生 ViewController+FilterDetailCategory.h.m 檔.

  2. 修改 ViewController+FilterDetailCategory.h 檔案如下:
#import "ViewController.h"

// 之前已經在 ViewController.h 宣告了
/*
@interface ViewController (FilterDetailCategory)

//@add
+ (NSDictionary*)deriveEditableAttributesForFilter:(CIFilter*)filter;

@end
*/

  3. 修改 ViewController+FilterDetailCategory.m 檔案如下:

#import "ViewController+FilterDetailCategory.h"

@implementation ViewController (FilterDetailCategory)

//@add
//
// Helper method.
// Returns an NSDictionary containg only the parameters we want our
// configure method
to operate on.
+ (NSDictionary*)deriveEditableAttributesForFilter:(CIFilter*)filter
{
    NSMutableDictionary *editableAttributes = [NSMutableDictionary dictionary];
    NSDictionary *filterAttributes = [filter attributes];
   
    for (NSString *key in filterAttributes) {
        if ([key isEqualToString:@"CIAttributeFilterCategories"]) continue;
        else if ([key isEqualToString:@"CIAttributeFilterDisplayName"]) continue;
        else if ([key isEqualToString:@"inputImage"]) continue;
        else if ([key isEqualToString:@"outputImage"]) continue;
        else if (![[[filter attributes] objectForKey:key] isKindOfClass:[NSDictionary class]]) continue;
       
        [editableAttributes setObject:[[filter attributes] objectForKey:key] forKey:key];
    }
   
    return editableAttributes;
}

//@add
//
// Helper function that returns a random float value within the specified range.
float randFloat(float a, float b);
float randFloat(float a, float b)
{
    srand(time(NULL));
    return ((b-a)*((float)arc4random()/RAND_MAX))+a;
}

//@add
//
// Given a filter, examine all its parameters and configure them with
// randomly generated values.
+ (void)configureFilter:(CIFilter*)filter
{
    // Get the filter's parameters we're interested in configuring here.
    NSDictionary *editableAttributes = [ViewController deriveEditableAttributesForFilter:filter];
   
    for (NSString *key in editableAttributes) {
       
        NSDictionary *attributeDictionary = [editableAttributes objectForKey:key];
       
        // Our method here only supports generating random values for parameters
        // that expect numbers. Some paramters take an image, color, or vector. 
        if ([[attributeDictionary objectForKey:kCIAttributeClass] isEqualToString:@"NSNumber"]) {
           
            // The number types are further broken down into sub types.  For our purposes,
            // we can group them into types that require either a boolean, float, or integer.
            if ([attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeBoolean)
            {
                NSInteger randomValue = (rand() % 2);
               
                NSLog(@"Setting %i for key %@ of type BOOL", randomValue, key);
                [filter setValue:[NSNumber numberWithInteger:randomValue] forKey:key];
            }
            else if([attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeScalar ||
                    [attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeDistance ||
                    [attributeDictionary objectForKey:kCIAttributeType] == kCIAttributeTypeAngle)
            {
                // Get the min and max values
                float maximumValue = [[attributeDictionary valueForKey:kCIAttributeSliderMax] floatValue];

                float minimumValue = [[attributeDictionary valueForKey:kCIAttributeSliderMin] floatValue];
               
                float randomValue = randFloat(minimumValue, maximumValue);
               
                NSLog(@"Setting %f for key %@ of type Decimal", randomValue, key);
                [filter setValue:[NSNumber numberWithFloat:randomValue] forKey:key];
            }
            else
            {
                // Get the min and max values
                NSInteger maximumValue = [[attributeDictionary valueForKey:kCIAttributeMax] integerValue];
                NSInteger minimumValue = [[attributeDictionary valueForKey:kCIAttributeMin] integerValue];
               
                NSInteger randomValue = (rand() % (maximumValue - minimumValue)) + minimumValue;
               
                NSLog(@"Setting %i for key %@ of type Integer", randomValue, key);
                [filter setValue:[NSNumber numberWithInteger:randomValue] forKey:key];
            }
           
        }
       
    }
}

@end

  4. 編譯並執行.