2012年5月20日 星期日

cocos2d 內為了產生遮罩類型的 button, 而必須取得 pixel color RGB 值的解決手段


在 cocos2d 裡頭透過 touch range 所產生的 button 其實都是方方正正的圖檔模式,所以若是遇到想要點擊不規則形狀的 button ,大致上能想到的方式就是產生另外一個遮罩檔,然後透過判斷點擊該遮罩檔的位置,來確認是否為設定的形狀之內。

google 了一大堆 cocos2d 遮罩檔之類的關鍵字,結果花了將近一天的時間,然後才在一些瑣碎的訊息裡頭,才發現這一篇文章:What Color is My Pixel? Image based color picker on iPhone ,看過就曉得,這個範例程式,就是在繪圖軟體或是一些設定上,常用來取得調色盤某 pixel 的 RGB 值,也就是這個範例程式裡頭應該就有我想要找的東西了。

( 主要要取得的程式內容,擺在 ColorPickerImageView 這個 class 裡頭 )
不過呢,這個程式裡頭的內容,也沒辦法馬上拿來套到 cocos2d 的 code 裡頭,而且,範例程式上還有一些規格上的問題需要被修正的,主要是因為此程式是 iOS 規格的,所以需要轉換到本來在使用的 cocos2d 裡頭。( 說穿了就是必須把 iOS 的元件轉換成 cocos2d(OpenGL ES) 來使用 )。


有幾個轉換或注意的部份的地方,也是個人玩了一陣子之後的結論:

一、座標系統之間的轉換
二、UIImage 物件在 cocos2d 內的使用方式
三、UIColor 物件轉換給 cocos2d 的注意事項
其實,如果已經知道這一些差異狀態的話,作業起來是很容易啦,不過因為個人是第一次玩,所以花了不少時間在實驗。

先來簡略描述一下第一個事項:
若是已經使用 cocos2d 開發一陣子了的工程師,應該都已經曉得了 cocos2d 因為是在 OpenGL base 之下的 third party library,所以該座標系就是遵照 OpenGL 的模式;剛好和 iOS 的UI Object 模式有些微上的差異,所以第一個主要工作,就是要將透過 cocos2d  touch 後取得的座標值,轉換成 UI object 可以使用的。
 ↑[圖一] 關於 UI Object 所使用的座標系概念圖示。 
↑[圖二] 關於 OpenGL Object 所使用的座標系概念圖示。 

希望這樣的簡圖不會太難懂。 =..="

可惜的是,個人在試過了很多 cocos2d 內提供的 function,諸如

    CGPoint    localPoint = CGPointMask( x, y )
    [[CCDirector sharedDirector] convertToUI: localPoint];  //  這個 method 會轉換成整個畫面大小的絕對標點.

或是

    CCSprite  * pSprite;
    // ...
    [pSprite convertTouchToNodeSpace: touch];    //  這個 method 至少取得該 sprite 物件獨立的座標點了.

等等的手段,都沒有辦法順利將透過 「- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event」等函式內,所取得座標進行轉換;結果就是還是得自己改寫一下 code 才能達到效果。
結果就是得在自己產生的 sprite button 物件上增加一個 method,用來轉換這個 sprite 物件的座標。
類似:
- ( CGPoint ) _ConvertToUIImagePoint:(CGRect)rect pos:(CGPoint)point
{
    return CGPointMask( point.x, ( rect.size.height - point.y ) );
}

才有辦法順利使用這個範例程式裡頭所取得的函式。


再來,就是修改 ColorPickerImageView 這個 class 裡頭的 method 的工程了;
找到 getPixelColorAtLocation 這一個 method,大概像這樣 ...

- (UIColor*) getPixelColorAtLocation:(CGPoint)point {
    UIColor* color = nil;
    CGImageRef inImage = self.image.CGImage;
    // Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue

    // ... 略 ...
}
把 method 原始宣告和第二行的部份,進行如下的修正:

- (UIColor *) getPixelColorAtLocation:(CGPoint)point image:(UIImage *)pUIImage {
    if ( nil == pUIImage ) {    return nil;     }
    UIColor* color = nil;
    CGImageRef inImage = pUIImage.CGImage;
    // Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue

    // ... 略 ...
}

這樣子,就能順利透過 getPixelColorAtLocation 這一個 method 來取得圖檔對應 pixel 的 RGB 值了。
※該值會透過 UIColor 物件指標回傳。
※其實,在這邊的測試過程也沒有很順利,因為嘗試著把 Sprite 物件內所得到的圖檔,轉換到 UIImage 物件裡頭,個人在透過一連串的實驗之後,發現能力不足,無法轉換;連網路上找到的類似 UIImage 與 CCSprite 互相轉換的方式都用上了,一樣無解。 
※※ 網址 UIImage与CCSprite互相转换 內的 -(UIImage *) convertSpriteToImage:(CCSprite *)sprite 。

這段的結論就是,額外載入一份圖檔到要用的 UIImage 物件當中,就不用硬將 UIImage 轉成 Sprite 物件了,反正 button 用的遮罩檔,本來就不打算顯示在 UI 上。


最後一項,就是 UIColor 與 cocos2d 的 顏色值之間之料轉換的問題了。
在 getPixelColorAtLocation method裡頭,可以發現一行程式碼:

color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];

值得注意的重點是:UIColor 物件內的 RGBA 值, 該值是 0 ~ 1,而不是一般常使用的 0 ~ 255 之類的。
所以若是取用已經經過轉換成 UIColor 物件的方式,就必須在將這個值轉換回來;因為個人還滿懶惰的,所以決定放棄取得這個 UIColor 物件,直接產生一個  (ccColor4B *)pPointColor 的指標,從中將內容取回就好了。 .... 我真的是很懶的運算轉換的,所以該 method 又被修整了一番 ...。 XD

所以改好後的 method 會長得類似這個樣子‧‧‧
+ ( BOOL ) getPixelColorAtLocation:(CGPoint)point image:(UIImage *)pUIImage color:(ccColor4B *)pPointColor

最後,就可以回到 touch 事件上的判定了。
※關於 UIImage 載入遮罩圖檔的部份,就整個省去不提了,應該不會因此造成卡關了才是。
※其實,個人還是卡關了,畢竟要套回現行開發專案的設定環境內,還是要調整的。 >"<

判定的 method 及流程就會長的很像這樣子‧‧‧
- ( BOOL ) IsTouchMaskRect:(UITouch *)touch {
    //  touch not in button rect, skip it.
    if ( [self IsTouchAtlasRect: touch] == NO )    {    return NO; }

    CCSprite              * pSprite;

    // ... 略 ...

    CGRect                  rectSprite;
    CGPoint                 pointSprite;
    ccColor4B               colorPoint;

    rectSprite              = [self _AtlasRect: pSprite];    //    這個 method, 很多程式都有寫, 只是個人把它變成 private 版本而已.
    pointSprite             = [pSprite convertTouchToNodeSpace: touch];    //    取得 touch 事件, 對應於此 sprite 物件內部的座標值.
    pointSprite             = [self _ConvertToUIImagePoint:rectSprite pos: pointSprite];    //    上頭提到的 轉換為 UIObject 座標系.

    if ( [CPixelColor getPixelColorAtLocation: pointSprite image: m_pMaskImage color: &colorPoint] == NO )     //    這邊的 colorPoint 將取得 touch 到該點之後, 所取得的該點 RGBA color 值.
    {    return NO;     }
    // ... 再來的判斷問題, 就不寫了 ...
    // ... 略 ...
}  

※記得在這個 touch event 之前先自行處理(載入)遮罩檔( UIImage *)的指標到 m_pMaskImage 裡頭就是了,不然肯定掛點。 ^^"


※這樣寫法,也不曉得會不會不恰當,畢竟是在開發公司軟體的過程,遭遇到的問題,及排除過程啊。 ^^" ...

----
後記:
人家有經驗的神人,講了一個方法,我這種完全沒經驗的,找個資料兼測試再兼實作的部份,就花掉了整整的兩個工作天( 還有自己三更半夜不睡覺,神遊在找 google 大神的部份  )。

再把這樣的問題整理成筆記的時間,三個小時。 ^^"

不能寫得過於清楚是因為,礙於這是工作時間實作出來的程式碼以及解決手段,所以沒辦法把整個完整的 code 都擺上來的;而且,寫成這樣應該已經夠清楚了才是。 ^^"

... 收工 ...

再次感謝 What Color is My Pixel? Image based color picker on iPhone  的原著作者。





















沒有留言:

張貼留言