# 顏色 在 [E-game 打寇島](https://www.egame.kh.edu.tw/) 的顏色積木中,可供選擇的顏色只有 70 種。 ![image](https://hackmd.io/_uploads/S1tayMyFJe.png) :::spoiler 所有顏色 `#ffffff` `#cccccc` `#c0c0c0` `#999999` `#666666` `#333333` `#000000` `#ffcccc` `#ff6666` `#ff0000` `#cc0000` `#990000` `#660000` `#330000` `#ffcc99` `#ff9966` `#ff9900` `#ff6600` `#cc6600` `#993300` `#663300` `#ffff99` `#ffff66` `#ffcc66` `#ffcc33` `#cc9933` `#996633` `#663333` `#ffffcc` `#ffff33` `#ffff00` `#ffcc00` `#999900` `#666600` `#333300` `#99ff99` `#66ff99` `#33ff33` `#33cc00` `#009900` `#006600` `#003300` `#99ffff` `#33ffff` `#66cccc` `#00cccc` `#339999` `#336666` `#003333` `#ccffff` `#66ffff` `#33ccff` `#3366ff` `#3333ff` `#000099` `#000066` `#ccccff` `#9999ff` `#6666cc` `#6633ff` `#6600cc` `#333399` `#330099` `#ffccff` `#ff99ff` `#cc66cc` `#cc33cc` `#993399` `#663366` `#330033` ::: 若想使用其他顏色,除了使用隨機,有沒有辦法讓每次執行都是想要的顏色呢? ## 設定顏色可以帶什麼參數? 從積木來看,要設定顏色,就必須使用 **「設定顏色」** 的積木。 ![image](https://hackmd.io/_uploads/SJQyUMkF1x.png) 程式碼如下: ```js Turtle.penColour(myColor); ``` 這行程式會導致下面兩行程式碼的執行(此為簡化後的程式碼): ```js Turtle.ctxScratch.strokeStyle = myColor; Turtle.ctxScratch.fillStyle = myColor; ``` 其中 `Turtle.ctxScratch` 為 [`CanvasRenderingContext2D`][CanvasRenderingContext2D] 物件。 > CanvasRenderingContext2D 是 HTML5 Canvas API 提供的介面,用於在 <canvas> 元素上繪製 2D 圖形,例如線條、形狀、文字和影像。 ### strokeStyle 和 fillStyle 可以是什麼? 我們知道該物件的屬性 [`strokeStyle`][strokeStyle] 及 [`fillStyle`][fillStyle] 可以是以下值之一: - 作為 CSS [`<color>`][color] 值解析的字串。 - [`CanvasGradient`][CanvasGradient] 物件(線性或徑向漸層)。 - [`CanvasPattern`][CanvasPattern] 物件(重複的圖像)。 明顯地,後兩種無法從積木取得,我們只需要關注第一種:「CSS [`<color>`][color] 值解析的字串」。 ### \<color\> 允許哪些格式? [`<color>`][color] 包含了非常多不同格式的字串,在此不一一列出,而其中可以由積木湊出的格式只有 [`<hex-color>`][hex-color]。 ### \<hex-color\> 允許哪些格式? [`<hex-color>`][hex-color] 的格式如下: - `#RGB` - `#RGBA` - `#RRGGBB` - `#RRGGBBAA` 前兩種目前被認為無法以積木湊出,第三種可由「顏色」或「隨機顏色」取得。 而第四種,可由第三種與數字做加法取得。 ## 半透明顏色 假設我們想要半透明的紅色 `#FF000080`,可以用以下積木設定: ![image](https://hackmd.io/_uploads/BJKU5QyYyg.png) 各顏色的效果如下: | `#FF000010` | `#FF000040` | `#FF000080` | `#FF0000FF` = `#FF0000` | | ----------- | ----------- | ----------- | ----------------------- | | ![image](https://hackmd.io/_uploads/HJ5VjXkY1g.png) | ![image](https://hackmd.io/_uploads/HkgYsX1Kke.png) | ![image](https://hackmd.io/_uploads/SJ0hiXkFkx.png) | ![image](https://hackmd.io/_uploads/ByoW37yK1g.png) | ### 顏色混合 我們可以在 `#FF0000` 上方疊加 `#FFFF0080`(即半透明的 `#FFFF00`),得到 `#FF8000`。 | `#FF0000` | `#FF8000` | `#FFFF00` | | ----------- | ----------- | ----------- | | ![image](https://hackmd.io/_uploads/ByoW37yK1g.png) | ![image](https://hackmd.io/_uploads/S1zXuVktkl.png) | ![image](https://hackmd.io/_uploads/SypKSrJF1x.png) | 這種橘色並不存在積木的選項裡(較接近的有 `#ff9900` 和 `#ff6600`),這意味著我們成功創造出了選項之外的顏色。 ## 湊出指定的顏色 要湊出指定的顏色,主要分成兩件事情: - 使用的顏色(Source Color) - 每個顏色所佔權重(Weights) 為了選擇正確的顏色和權重,我們要先了解顏色是如何疊加的。 ### 顏色疊加的計算方式 E-game 在繪製顏色時是透過 `Turtle.ctxScratch` 物件,其 [`globalCompositeOperation`][globalCompositeOperation] 屬性的值為預設值 `"source-over"`。 :::spoiler 補充:Source-Over 混合算法 設 $C_s$ 為源色彩(上層),$C_d$ 為目標色彩(下層),其對應的透明度分別為 $\alpha_s$ 和 $\alpha_d$。Source-over 合成運算可表示如下: 對於最終的透明度 $\alpha_r$: $$\alpha_r = \alpha_s + \alpha_d(1 - \alpha_s)$$ 對於最終色彩 $C_r$ 的每個色彩通道(R、G、B): $$C_r = \frac{C_s\alpha_s + C_d\alpha_d(1-\alpha_s)}{\alpha_r}$$ 展開至個別 RGB 通道: $$ \begin{align*} R_r &= \frac{R_s\alpha_s + R_d\alpha_d(1-\alpha_s)}{\alpha_s + \alpha_d(1-\alpha_s)} \\ G_r &= \frac{G_s\alpha_s + G_d\alpha_d(1-\alpha_s)}{\alpha_s + \alpha_d(1-\alpha_s)} \\ B_r &= \frac{B_s\alpha_s + B_d\alpha_d(1-\alpha_s)}{\alpha_s + \alpha_d(1-\alpha_s)} \end{align*} $$ 其中: - $(R_s, G_s, B_s)$ 代表源色彩的 RGB 值 - $(R_d, G_d, B_d)$ 代表目標色彩的 RGB 值 - $(R_r, G_r, B_r)$ 代表最終色彩的 RGB 值 - 所有色彩值的範圍為 $[0, 255]$ - 所有透明度值的範圍為 $[0, 1]$ ::: ### 選擇要使用的顏色 Source-Over 就相當於將疊加的顏色做 [凸組合(Convex combination)][凸組合],其主要特性為: - 相當於權重和。 - 每個權重都介於 0 到 1 之間。 簡單地說,疊加後的結果必定落在使用的顏色所為出的凸幾何圖形中。 例如我們要湊出 `#123456` ,我們可以選擇 `#000000` `#990000` `#33CC00` `#000099`。 (我們最多需要 4 種不同顏色,若取 5 種不同顏色,則必定可忽略其中一種) ### 求出每個顏色所佔權重 在此之前,我們先決定疊加順序為:`#000000` `#990000` `#33CC00` `#000099`。 第一個顏色可理解為底色,直接將其不透明度設為 1,組合出的顏色才不會受背景圖片影響。 ![image](https://hackmd.io/_uploads/rJ6e9L1tJe.png) 繪製出的顏色為 `#1f334d` 。 :::info 其中 plus 函式只是一般的加法。 ::: 接著由下而上調整透明度,調整時一邊執行積木,查看繪製出的顏色。 ![image](https://hackmd.io/_uploads/SklAtLJYJg.png) 繪製出的顏色為 `#123456` 。 :::info - 之所以要由下而上調整,是因為後面的透明度會影響前面的權重,反之則不會。 - 藍色亮度只由 `#000099` 貢獻,因此調整它時可直接參考繪製出的藍色亮度。 同理,綠色亮度只由 `#33CC00` 貢獻,因此調整它時可直接參考繪製出的綠色亮度。 ::: ### 可以湊出所有顏色嗎? 從 [凸組合][凸組合] 的角度來看,若要湊出所有顏色,則需要有 RGB 立方體的八個頂點。換句話說: > 我們可以湊出所有顏色,若且唯若我們擁有以下八種顏色: > `#000000` `#FF0000` `#00FF00` `#0000FF` `#FFFF00` `#00FFFF` `#FF00FF` `#FFFFFF` (上述八種顏色為 RGB 立方體的頂點,它們的凸組合可以達到立方體內的任何一點。) 但是非常遺憾,我們手上只有八個顏色中的四個,因此無法湊出所有顏色。 [CanvasRenderingContext2D]: <https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D> [strokeStyle]: <https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle> [fillStyle]: <https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle> [color]: <https://developer.mozilla.org/en-US/docs/Web/CSS/color_value> [CanvasGradient]: <https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient> [CanvasPattern]: <https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern> [hex-color]: <https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color> [globalCompositeOperation]: <https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation> [凸組合]: <https://zh.wikipedia.org/zh-tw/%E5%87%B8%E7%BB%84%E5%90%88>