2024-05-08|閱讀時間 ‧ 約 7 分鐘

徹底搞清楚pygame的blend mode

呼!折騰了好久,終於徹底搞清楚pygame的各個blend mode所用的計算式,到底是長啥樣子了。

在數位影像編輯和電腦圖學中,當不同的顏色混在一起時,最後會呈現出什麼顏色,完全取決於背後用的是怎樣的計算方式。不同的計算方式,就會得到不同的顏色,而blend mode就是指這些不同的計算方式。如果把blend mode翻譯成「混色模式」,應該可以讓人比較容易理解那到底是個什麼玩意兒。

在寫《The Nature of Code閱讀心得筆記——使用Python實作》《The Nature of Code閱讀心得與Python實作》第4.9節時,因為是有關於blend mode的內容,所以必須知道pygame在這部分有提供哪些功能,這樣才有辦法寫程式。讓人意想不到的是,明明內容不多,看來也都是些很簡單容易理解的概念,真正寫起程式來,卻老是做不出原書的效果,實在是讓人懷疑人生。

「啊你不會網路上查一下喔?!問一下AI也可以啊!」一定會有人帶著懷疑的眼神,以略帶不屑的口吻這樣說。是啦!是啦!這誰不知道啊?但問題也就出在這兒啊,網路上看到的資料中,有已經過時的、有寫錯的,而Google那個萬人迷的Gemini,吃了那些亂七八糟的東西之後,嘴裡吐出來的東西,也是亂七八遭的,根本不知道哪些是正確的,哪些是錯誤的。

既然真相這麼難找,那最後又是怎麼找到的?這個啊,套句電視劇灑狗血用的台詞,就「回歸初心」囉!乖乖的研究pygame的原始檔案中,這部分到底是怎麼設計的。這樣做雖然有點燒腦,但很實在。

在pygame中,混色模式除了BLEND_PREMULTIPLIEDBLEND_ALPHA_SDL2這兩個比較特殊孤僻的模式之外,可以由命名方式區分為兩大類,而其主要的差異在於混色時,包含或不包含透明度。

不包含透明度的混色模式

BLEND_ADD, BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX

BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN, BLEND_RGB_MAX

這裡要注意的是,中間有或沒有RGB三個字母,其實是一樣的模式,只是名稱不同而已。例如,BLEND_ADDBLEND_RGB_ADD,指的其實是同樣的模式。

包含透明度的混色模式

BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX

這些模式會利用不同的計算式,來決定混色之後的顏色數值。假設

0 <= src, dest <= 255

當把來源像素的顏色src混色到目標像素的顏色dest而得到顏色c時,各個模式的RGBA值計算式為:

ADD: c = (dest+src) if (dest+src)<=255 else 255

SUB: c = (dest-src) if (dest-src)>=0 else 0

MULT: c = (dest*src+255)//256

MIN: c = min(dest, src)

MAX: c = max(dest, src)

其中MULT模式有兩個比較特別的性質:當destsrc有一個是0時,則混色之後的值,就會是0;而當destsrc有一個是255時,則混色之後的值,會是另外那個顏色的值。所以,當我們把一張具備透明度的圖片,以MULT混色模式blit到一張顏色為(255, 255, 255, A)的圖片之後,所得到的圖片,顏色會跟原來的圖片一樣,而且,完全透明的部分,也會跟原來的圖片一樣;被改變的,就只是完全不透明以及不完全透明部分的透明度。

在pygame的blit()fill()方法中,有個special_flags參數,可以用來選擇混色模式;如果不特別指定而使用預設的混色模式,則混色後的顏色,計算方式為:

if destA == 0:
c = src
A = srcA
else:
    c = (src*srcA + dest*(255-srcA))//255
  A = srcA + destA*(255-srcA)//255

這裡的srcAdestA分別指的是來源像素和目標像素的alpha值,而A則是混色後所得到的alpha值。不過要注意的是,這兩個式子所算出的值,和用pygame所算出的值,可能會有些小小的差距。造成這個小小差距的原因,在於pygame為了加快計算速度,用了很多技巧來避免浮點數運算,但同時也犧牲了一些計算上的準確度。另外,從計算式可以看出來,當目標像素是完全透明時,混色之後得到的會是來源像素,完全無視目標像素的存在。

以上就是pygame各種blend mode的計算方式。這些計算式看起來挺簡單的,不是嗎?但怎會那麼折騰人呢?以下就是挖掘真相的辛酸血淚史。

這一切的一切,都是因為pygame的說明文件,實在是有夠不清不楚的。

在pygame的說明文件中,只列出了各個blend mode的名稱,並沒有說明相關的計算方式。所以,就只能鼻子摸一摸,在網路上搜尋看看有沒有更詳細的資料。

顯然有不少網友有同樣的疑問,所以在stack overflow和reddit上,可以看到不少相關的討論。只是很不幸的,pygame在發展的過程中,針對混色的計算方式,進行過幾次的調整,而網路上查到的那些網友提供的計算式,有一些是調整前的式子,難怪實際測試時,老是得不到預期中的結果。

要想知道pygame各個blend mode所用的計算式,比較好的辦法,就是直接去讀surface.h這個檔案。不過,千萬千萬要注意,網路上有網友提供這個檔案的連結,要讀之前,先確認一下日期,因為那是個過時的檔案,裡頭關於MULT模式的計算式有bug。所以囉,最萬無一失的方法,就是把安裝在自己電腦上的surface.h找出來讀,這樣就不用擔心版本不符的問題了。

在所有的blend mode中,預設模式的計算式花了最多時間才搞清楚。之所以會這樣,還是因為文件寫得不完整。在<Source Alpha to Source Alpha Surface Blit Guarantees — wiki>這篇文章中,說明了pygame在混色時的計算方式。這個其實也就是blend mode預設模式的計算式。只是啊只是,文章中並沒有提到一個特殊狀況:當目標像素是完全透明時,混色之後得到的RGBA值,會和來源像素的RGBA值一模一樣。就因為這個不完整的說明,又讓人昏天暗地的多花了很多時間,才終於搞清楚問題出在哪。

從這個挖掘pygame的blend mode真相的過程就可以知道,清楚且詳細的說明文件,是多麼的重要而且珍貴啊!

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.