---
# System prepended metadata

title: 顏色

---

# 顏色

在 [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>
