【秘辛】白色改變顏色

將白色用『圖像效果顏色改變 200』不會變回白色

這是一位老師發現的,將貓咪用『圖像效果顏色改變 200』 之後,貓咪的身體變回原來的顏色,但貓咪嘴巴卻不是白色,而是淡淡的紅色。自己畫一個白色的方形,也把它的顏色改變 200,也是一樣的結果。

這是怎麼回事?將白色改變顏色 200 為什麼不會變回白色,是 Scratch 的 bug 嗎?這引起了傑夫老師的好奇心,決定來找出答案。

將白色用『圖像效果顏色改變 200』不會變回白色

實驗步驟

實驗步驟如上面所顯示,顏色是用偵測積木《碰到顏色》來檢查。貓咪身體的顏色,在執行改變顏色的前後都是一樣(顏色 10、彩度 90、亮度 100),但貓咪的嘴巴改變前是純白色(顏色 0、彩度 0、亮度 100),之後卻變成了淡紅色(顏色 0、彩度 9、亮度 100)。原本以為,貓咪嘴巴可能不是純白色,所以另外畫了一個白色底的正方形,重複一樣的步驟,卻也是得到一樣的淡紅色。

把這問題在臉書社團 Teaching with Scratch 中提問,希望大神 RokCoder 會出面解釋,可惜沒回... 只好自己找答案了

臉書社團上的回覆

臉書社團上的回覆

果然,谷哥大神什麼都知道,經過了一番搜索,最後在 Scratch Discussion 的這一篇中找到答案
» Bugs and Glitches » Grey changes with the color effect
https://scratch.mit.edu/discuss/topic/6959/
樓主的問題如下,這是個 2013 年就提出的問題,而且看似與我的問題無關。

樓主的問題

樓主的問題

但事隔多年之後,有個回應雖是在討論 Scratch 2.0 與 3.0 的不同,但提到了一樣貓咪的狀況,並請注意這兩句:

If you color-pick it after you'll see that the saturation has gone up… in fact, it's changed from 0 from 9 the moment the color effect isn't at 0.
After some testing, this only happens when the saturation is below 9.

譯:
如果你(改變顏色)之後用顏色吸管吸取顏色,會發現飽和度(彩度)被提高了...具體的說,一旦做了顏色不為0的改變,飽和度(彩度)便從0變成了9
經過一些測試,這種狀況只發生在
飽和度(彩度)小於9的時候。

多年之後,有個回應提到了一樣的問題

多年之後,有個回應提到了一樣的狀況

這篇討論繼續往下看,就看到了真相: Yes, it's intentional. (譯:對,這是故意的

真相

真相: Yes, it's intentional.

故意的!?瞎毀,然後就一段程式。不過幸運的是這段程式有寫註解,大意是:
這段程式強迫把灰階值變成稍微飽和,這樣些許的色彩改變才看得出來

// this code forces grayscale values to be slightly saturated

// so that some slight change of hue will be visible

const float minLightness = 0.11 / 2.0;

const float minSaturation = 0.09;

if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);

else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);

遠遠聽到有人在說:傑夫老師有翻譯跟沒翻譯不都一樣,看不懂呀!

傑夫老師會回說,有程式不會自己看呀~

對~我們來看程式,下面這段是 github 上最新的程式,程式雖然已經被修改了,但功能沒改變
https://github.com/LLK/scratch-render/blob/develop/src/EffectTransform.js

這段程式在函式 transformColor (drawable, inOutColor, effectMask) 中,也就是要做顏色改變時會執行到的函式。

const hsv = rgbToHsv(inOutColor, __hsv);


// this code forces grayscale values to be slightly saturated

// so that some slight change of hue will be visible

// const float minLightness = 0.11 / 2.0;

const minV = 0.11 / 2.0;

// const float minSaturation = 0.09;

const minS = 0.09;

// if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);

if (hsv[2] < minV) {

hsv[0] = 0;

hsv[1] = 1;

hsv[2] = minV;

// else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);

} else if (hsv[1] < minS) {

hsv[0] = 0;

hsv[1] = minS;

}

在看之前,先要知道 Scratch 的顏色其實是可以對應到 HSV 的。不清楚 HSV 的請參考 wikipedia HSL和HSV色彩空間

顏色 對應到 Hue (色調) 程式中的 hsv[0]
彩度 對應到 Saturation (飽和) 程式中的 hsv[1]
亮度 對應到 Lightness (明度、亮度) 程式中的 hsv[2]

hsv[0], [1], [2] 這三個值再從 rgbToHsv 的註解中知道範圍是從 0~1

那來看 else if 這段程式:

} else if (hsv[1] < minS) { //檢查要改變的顏色的彩度,如果小於minS(0.09)
hsv[0] = 0;
//就把顏色設為0
hsv[1] = minS;
//再把彩度設為0.09
}

白色的三個值都是 0,所以這個判斷會成立,那彩度 hsv[1] 就會被改成 0.09。剛剛說了程式中 hsv 值的範圍都是 0~1,那麼 0.09 算回 Scratch 的彩度就是 9。

好了,真相大白,只要顏色的彩度小於 9,當執行『圖像效果顏色改變』時,彩度就會被強迫設為 9,而我們看到的白色顏色改變就是這符合這個狀況。換句話說,全白的顏色因為沒有任何彩度,對它做任的何顏色改變,也感覺不到有變顏色,所以程式中將彩度調高一點,才能讓顏色的改變顯現出來。這句話其實就是前面那段程式註解要說明的事。

別走!事情還沒結束,程式還有一段,來看看什麼意思:

if (hsv[2] < minV) { //如果要改變的顏色的亮度小於minV(0.055)
hsv[0] = 0;
//就把顏色設為0
hsv[1] = 1;
//再把彩度設為1
hsv[2] = minV;
//最後把亮度設為0.055
}

這段程式的目的,就是要把亮度太低的顏色,稍微調高亮度並把彩度調到最高,這樣改變顏色後才能看出顏色。不過,由於亮度還是太低,傑夫老師用色域較廣的螢幕並把螢幕亮度打到最亮,顏色的變化也沒有很明顯,還是很接近黑色。

有興趣研究的朋友,可以把一個黑色(顏色 0、彩度 0、亮度 0)的角色用『圖像效果顏色改變 200』改變顏色之後,就會發現顏色還是接近黑色,但其實已經變成了(顏色 0、彩度 100、亮度 5喔~

好啦~這個 Scratch 鮮為人知的秘辛就說明到這邊,下次再跟大家分享。Bye now~