轉載自 http://blog.csdn.net/dionysos_lai/article/details/39030081?utm_source=tuicool
原文章 參考 http://zengrong.net/post/2067.htm
橡皮擦具體功能要求:
1. 實現擦除效果:具體要求是點擊位置,拖動軌跡路上,均可以擦除。在快速拖動過程中,不能出現斷層和鋸齒現象。
2. 擦除的形狀,最好可以自定義。默認可以提供正方形、圓形兩種,最好能提供自定義圖片形狀。
3. 判斷圖片是否擦除完畢。
4. 如果擦除形狀過小,那麼難免在擦除過程中,會遺留一些細小的、可能難以注意的殘留點。在擦除過程中,要求可以自動擦除這些殘留點。
功能分析:
1. 擦除效果實現:
A. 所謂“擦除”,就是將要擦除的圖片RGB和alpha值,全部去掉。可以通過兩張圖片的混合實現。這裏簡單介紹OpenGL中的混合原理。
OpenGL中的混合,就是將原來的原色和將要畫上去的顏色,經過“一些處理”,得到一種新的顏色,然後再次將得到的新顏色畫到畫布上。這裏,我們將要畫上去的顏色,稱爲“源顏色”,把原來的顏色稱爲“目標顏色”。
上文中的“一些處理”,實際是將源顏色和目標顏色各自取出,乘以一個因數(這裏,對應的因數,我們稱之爲“源因子”和“目標因子”),然後二者相加(當然也可以不是相加,可以是相減、或者取二者最大值等等,新版的OpenGl可以設置運算方式),這樣既可以得到一個新的顏色值。我們假設源顏色的四個分量(指紅色,綠色,藍色,alpha值)是(Rs, Gs, Bs, As),目標顏色的四個分量是(Rd, Gd, Bd, Ad),又設源因子爲(Sr, Sg, Sb, Sa),目標因子爲(Dr, Dg, Db, Da)。則混合產生的新顏色可以表示爲:
當然,如果某個分量,超過了最大值,會自動截取的。
源因子和目標因子是可以通過glBlendFunc函數來進行設置的。glBlendFunc有兩個參數,前者表示源因子,後者表示目標因子。這兩個參數可以是多種值,下面介紹比較常用的幾種。
GL_ZERO: 表示使用0.0作爲因子,實際上相當於不使用這種顏色參與混合運算。
GL_ONE: 表示使用1.0作爲因子,實際上相當於完全的使用了這種顏色參與混合運算。
GL_SRC_ALPHA:表示使用源顏色的alpha值來作爲因子。
GL_DST_ALPHA:表示使用目標顏色的alpha值來作爲因子。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0減去源顏色的alpha值來作爲因子。GL_ONE_MINUS_DST_ALPHA:表示用1.0減去目標顏色的alpha值來作爲因子。
利用OpenGl原理,如果我們將源顏色的顏色值設置爲0,並源因子和目標因子分別設置爲GL_OEN,GL_ZERO,則新顏色具體值如下所示:
注:這裏的Rs、Gs、Bs、As均爲0。
因此可以很方便的實現的擦除效果了。其詳細代碼如下所示:
- m_pEraser->setPosition(point);
- ccBlendFunc blendFunc = { GL_ONE, GL_ZERO }; ///< 設置混合模式, 源---1, 目標---0
- m_pEraser->setBlendFunc(blendFunc);
- m_pRTex->begin();
- m_pEraser->visit();
- m_pRTex->end();
如果是自定義的形狀(這裏我們的討論的自定義形狀,是圖片提供的形狀,而不是自己畫出來的-----因爲自己畫出來的,跟前面沒有區別)。這裏對圖片有比較特殊的要求,即要求圖片中間形狀是鏤空的,外部的alpha通道必須爲255。如下圖所示:
(*^__^*) 嘻嘻……,這裏是一張動物圖片(這次是做有關動物繪本遊戲),在其輪廓內部是鏤空的,外部只要alpha最大即可。然後我們將源因子和目標因子分別設置爲GL_ONE_MINUS_SRC_ALPHA、GL_SRC_ALPHA。
則新顏色如下表示:
在外部區域:GL_ONE_MINUS_SRC_ALPHA = 0; GL_SRC_ALPHA =1。則新顏色值如下所示:
還是原來的值。
在內部區域:GL_ONE_MINUS_SRC_ALPHA = 1; GL_SRC_ALPHA =0。則新顏色值如下所示:
可以看出,值全部爲0。
具體代碼如下所示:
- CCSprite* drawSprite = CCSprite::createWithTexture(m_drawTextture);
- drawSprite->setPosition(point);
- ccBlendFunc blendFunc = { GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA }; ///< 設置混合模式, 源---1-alpha, 目標---alpha
- drawSprite->setBlendFunc(blendFunc);
- m_pRTex->begin();
- drawSprite->visit();
- m_pRTex->end();
B. 利用動態紋理,實現紋理的變化。
使用擦除效果,紋理必然是發生動態變化的。這裏採用CCRenderTexture實現動態紋理改變。對於CCRenderTexture的具體使用方法,可見引擎裏描述語言:
To render things into it, simply construct arender target, call begin on it, call visit on any cocos scenes or objects torender them, and call end.
其實,就是下面一段話:
- 創建一個新的CCRenderTexture. 這裏,你可以指定將要創建的紋理的寬度和高度。.
- 調用 CCRenderTexture:begin. 這個方法會啓動OpenGL,並且接下來,任何繪圖的命令都會渲染到CCRenderTexture裏面去,而不是畫到屏幕上。
- 繪製紋理. 你可以使用原始的OpenGL調用來繪圖,或者你也可以使用cocos2d對象裏面已經定義好的visit方法。(這個visit方法就會調用一些opengl命令來繪製cocos2d對象)
- 調用 CCRenderTexture:end. 這個方法會渲染紋理,並且會關閉渲染至CCRenderTexture的通道。
- 從生成的紋理中創建一個sprite. 你現在可以用CCRenderTexture的sprite.texture屬性來輕鬆創建新的精靈了
- CCSprite* sprite = CCSprite::create(pszFileName);
- spriteSize = sprite->getContentSize();
- /// 將精靈加入紋理後,其中心點座標應該設置在(0,0)處, 這是由於紋理的中心點在(0,0),當然,可以通過設置其偏移座標實現;
- sprite->setAnchorPoint(ccp(0.f, 0.f));
- // sprite->setPosition(ccp(spriteSize.width/2.f, spriteSize.height/2.f));
- m_pRTex = CCRenderTexture::create(spriteSize.width, spriteSize.height);
- m_pRTex->setPosition(CCPointZero);
- this->addChild(m_pRTex);
- m_pRTex->begin();
- sprite->visit();
- m_pRTex->end();
C. 避免出現斷層和鋸齒現象
之所以出現斷層和鋸齒的原因,是由於在快速擦除過程中,系統接收到的第一個點位置和第二個點位置,可能有很大的位移偏差。如果只是簡單的處理這兩個點的信息,顯而亦然中間很多點就會缺失,而不畫。因此就出現了斷層和鋸齒的現象。
因此,我們只要簡單的判斷兩點之間的距離是否超過一定程度,就在二者間再次處理。至於二者間,要抽取多少點進行處理,就要看其距離的長度了。這邊,我簡單的判斷距離超過1,就要處理(顯然這樣做,會比較準確,但消耗性能大,可以適當更改)。下面給出具體代碼:
- void EraserSprite::ccTouchMoved( cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent )
- {
- if (m_bEraser)
- {
- CCPoint point = pTouch->getLocation();
- CCPoint normal = ccpNormalize(point-m_touchPoint);
- /// 處理一次移動過多,造成中間有遺漏,或者鋸齒現象;
- while(1)
- {
- if (ccpDistance(point, m_touchPoint) < 1.f)
- {
- /* m_pEraser->setPosition(-this->getPosition() + point + spriteSize/2.f);*/
- eraseByBlend(-this->getPosition() + point + spriteSize/2.f);
- break;
- }
- m_touchPoint = m_touchPoint + normal*1.f;
- /* m_pEraser->setPosition(-this->getPosition() + m_touchPoint + spriteSize/2.f);*/
- eraseByBlend(-this->getPosition() + m_touchPoint + spriteSize/2.f);
- }
- m_touchPoint = point;
- }
- }
2. 擦除形狀
對於擦除形狀,其實上文已經提到了。這裏簡單提一下。如果是採用點或者圓形,可以使用自定義畫節點,即CCDrawNode實現。對於CCDrawNode的擴展使用,也可以用到CCClippingNode中,即實現自定義裁剪模板。下面給出正方形和圓形擦除形狀代碼:
正方形形狀:
- m_pEraser = CCDrawNode::create();
- float width = 10.f;
- m_pEraser->drawDot(CCPointZero, width, ccc4f(0,0,0,0));
圓形形狀:
- m_pEraser = CCDrawNode::create();
- /// 繪製圓形區域
- float fRadius = 30.0f; ///< 圓的半徑
- const int nCount = 100; ///< 用正100邊型來模擬園
- const float coef = 2.0f * (float)M_PI/nCount; ///< 計算每兩個相鄰頂點與中心的夾角
- static CCPoint circle[nCount]; ///< 頂點數組
- for(unsigned int i = 0;i <nCount; i++) {
- float rads = i*coef; ///< 弧度
- circle[i].x = fRadius * cosf(rads); ///< 對應頂點的x
- circle[i].y = fRadius * sinf(rads); ///< 對應頂點的y
- }
- m_pEraser->drawPolygon(circle, nCount, ccc4f(0, 0, 0, 0), 0, ccc4f(0, 0, 0, 0));//繪製這個多邊形!
對於自定義的圖片形狀,其實就是一個精靈對象而已。
3. 判斷圖片是否擦除完畢
判斷是否擦除完畢,基本思路就是對紋理像素值逐點判斷,當所有像素值均爲0時,則代表圖片已經擦除完畢了。
首先,獲取紋理的圖片信息。關鍵函數是newCCImage。具體代碼如下:
- CCImage* image = new CCImage();
- m_pRTex->newCCImage(true);
這裏要注意一點就是,最後要手動刪除image。
其次,獲取各個位置的像素值。代碼如下所示:
- unsigned char *pixel = data_ + (x + y * image->getWidth()) * m;
- // You can see/change pixels' RGBA value(0-255) here !
- unsigned int r = (unsigned int)*pixel;
- unsigned int g = (unsigned int)*(pixel + 1);
- unsigned int b = (unsigned int)*(pixel + 2) ;
- unsigned int a = (unsigned int)*(pixel + 3);
其中,x、y代表位置。
完整代碼如下所示:
- bool EraserSprite::getEraserOk()
- {
- m_bEraserOk = false;
- CCImage* image = new CCImage();
- image = m_pRTex->newCCImage(true);
- int m = 3;
- if (image->hasAlpha())
- {
- m = 4;
- }
- unsigned char *data_= image->getData();
- int x = 0, y = 0;
- /// 這裏要一點,即Opengl下,其中心點座標在左上角
- for (x = 0; x < spriteSize.width; ++x)
- {
- for (y = 0 ; y < spriteSize.height; ++y)
- {
- unsigned char *pixel = data_ + (x + y * image->getWidth()) * m;
- // You can see/change pixels' RGBA value(0-255) here !
- unsigned int r = (unsigned int)*pixel;
- unsigned int g = (unsigned int)*(pixel + 1);
- unsigned int b = (unsigned int)*(pixel + 2) ;
- unsigned int a = (unsigned int)*(pixel + 3);
- if (r != 0 && g != 0 && b != 0 && a != 0)
- {
- m_bEraserOk = false;
- break;
- }
- }
- if (spriteSize.height != y)
- {
- break;
- }
- }
- if (x == spriteSize.width && y == spriteSize.height)
- {
- m_bEraserOk = true;
- }
- delete image;
- return this->m_bEraserOk;
- }
這裏,參考了文章:《Getting andsetting the RGB / RGBA value of a pixel in a CCSprite (cocos2d-x)》,詳細地址:http://stackoverflow.com/questions/9665700/getting-and-setting-the-rgb-rgba-value-of-a-pixel-in-a-ccsprite-cocos2d-x
好囧啊,這個部分,花了我整整一個上午時間,沒想到就這樣的過去,一點都沒有前面高大善的趕腳。
4. 殘留點清除問題
對於這個問題,還沒有很好的思路
最後,附錄上代碼地址:https://github.com/DionysosLai/EraserSprite