最近使用 Raspberry Pi Pico 控制 ST7789 TTF LCD 熒幕繪製一些簡單的圖案
但 MicroPython 及 CircuitPython 都沒有原生的圖像工具函式庫
因此在下嘗試試自己編寫一些自己需要使用的功能
但 MicroPython 及 CircuitPython 都沒有原生的圖像工具函式庫
因此在下嘗試試自己編寫一些自己需要使用的功能
雖然沒有原生函式庫,但其實有很多第三方函式庫
不過在下還是希望自己懂得及編寫自己的函式庫
可以在使用其他工具中時,即使沒有第三方函式庫都能夠自行編寫
不過在下還是希望自己懂得及編寫自己的函式庫
可以在使用其他工具中時,即使沒有第三方函式庫都能夠自行編寫
座標幾何
以 SVG 為例,繪製直線最基本只需要 line 再設定 (x1,y1) 及 (x2,y2) 的 2組座標,例如:
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 100 100" version="1.1"> <rect width="100" height="100" fill="#FFFF00"/> <line x1="10" y1="15" x2="70" y2="95" stroke="#000000"/> </svg>
雖然 SVG 可以很簡單定義直線,但仍然需要渲染引擎將圖形渲染到熒幕上,才能讓使用者看到實際的圖案及效果
而且熒幕是以 像素(Pixel) 為單位
亦即是2組座標中間的線段,因此仍然需要像 點陣圖 般逐點繪製,才能呈現在熒幕上
因此若沒有渲染引擎,便需要計算中間的線段每個像素的座標
而且熒幕是以 像素(Pixel) 為單位
亦即是2組座標中間的線段,因此仍然需要像 點陣圖 般逐點繪製,才能呈現在熒幕上
因此若沒有渲染引擎,便需要計算中間的線段每個像素的座標
直線工具
在 座標幾何 中,可以使用
來表達任何直線線段
y=ax+b
來表達任何直線線段
當中的 a 為 斜率(Slope) ,可以使用
a=\frac{y_{2}-y_{1}}{x_{2}-x_{1}}
方程式來計算
而 b 則需要將 x 及 y 代入到方程式中計算
(已知 x1, y1, x2, y2 數值, x 及 y 為 其中1組座標即可)
(已知 x1, y1, x2, y2 數值, x 及 y 為 其中1組座標即可)
y_{1}=\frac{y_{2}-y_{1}}{x_{2}-x_{1}}x_{1}+b
用先前的 SVG 線段例子,將所以 x 及 y 代入
15=\frac{95-15}{70-10}10+b
計算出 a 為 4/3 及 b 為 5/3
即是 SVG 線段例子的方程式為
便可以代入 x 或 y 計算座標
y=\frac{4}{3}x+\frac{5}{3}
便可以代入 x 或 y 計算座標
代入 x 計算 y 的效果
代入 y 計算 x 的效果
會發現 代入 x 計算 y 及 代入 y 計算 x 顯示效果為有些微分別
由於線段的座標必定為 非負整數 ,當計算的結果出現小數,不論使用
由於線段的座標必定為 非負整數 ,當計算的結果出現小數,不論使用
- 向下取整 (Floor / Round Down)
- 向上取整 (Ceil / Round Up)
- 四捨五入 (Round)
另外,由於例子線段的 闊度 (Width) 少於 高度 (Height) ,代入 x 計算 y 會缺漏部分 y 資料,令線段不連貫
因此要讓線段連貫,必須讓長邊的變數作為代入值
因此要讓線段連貫,必須讓長邊的變數作為代入值
在下編寫了一個簡單的偽代碼
function drawLine(x1, y1, x2, y2) { if (x1 == x2) { for (y = y1 to y2) { drawPixel(x1, y) } } else if (y1 == y2) { for (x = x1 to x2) { drawPixel(x, y1) } } else { h = y2 - y1 w = x2 - x1 a = h / w b = y1 - a * x1 if (abs(h) < abs(w)) { if (x1 > x2) { swap(x1, x2) } for (x = x1 to x2) { y = a * x + b drawPixel(round(x), round(y)) } } else { if (y1 > y2) { swap(y1, y2) } for (y = y1 to y2) { x = (y - b) / a drawPixel(round(x), round(y)) } } } }
圓形及橢圓形工具
SVG 圓形例子
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 100 100" version="1.1"> <rect width="100" height="100" fill="#FFFF00"/> <circle cx="40" cy="30" r="20" fill="none" stroke="#000000"/> </svg>
SVG 橢圓形例子
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 100 100" version="1.1"> <rect width="100" height="100" fill="#FFFF00"/> <ellipse cx="40" cy="30" rx="20" ry="10" fill="none" stroke="#000000"/> </svg>
圖形及橢圓形 在 SVG 同樣也很簡單,設定圓形中心的座標(cx,cy),再設定圓形的半徑(r) 便可以定義圓形
但座標幾何的方式,是透過渲染引擎協助下繪製出線條,並非以像素形式畫出
因此需要計算每個像素的座標,才能製作圓形的點陣圖
但座標幾何的方式,是透過渲染引擎協助下繪製出線條,並非以像素形式畫出
因此需要計算每個像素的座標,才能製作圓形的點陣圖
P_{x}=C_{x}+r_{x}\sin{\theta}
P_{y}=C_{y}+r_{y}\cos{\theta}
當橢圓形的 rx 與 ry 相同,就是圓形
偽代碼程式
function drawEllipse(cx, cy, rx, ry) { for (d = 0 to 359) { x = cx + rx * sin(d) y = cy + ry * cos(d) drawPixel(round(x), round(y)) } }
點陣圖方式繪製圓形
點陣圖方式繪製橢圓形
曲線工具
如果要繪製曲線,在 SVG 都會使用 path 配 d 屬性來繪製曲,亦即是 貝茲曲線 (Beizer Curve)
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 100 100" version="1.1"> <rect width="100" height="100" fill="#FFFF00"/> <path d="M 20,30 Q 60,50 20,70" fill="none" stroke="#000000"/> </svg>
從資料顯示,貝茲曲線的線段座標可以通過
P(t)=\sum_{i=0}^{n}P_{i}\binom{n}{i}t^{i}(1-t)^{n-i}
這方程式列出線段上的座標,但留意 t 的範圍為 0 至 1
偽代碼程式
function factorial(n) { product = 1 for (i = n to 1 step -1) { product *= i } return product } function nPr(n, r) { return round(factorial(n) / factorial(n - r)) } function nCr(n, r) { return round(nPr(n, r) / factorial(r)) } function drawBezierCurve(ps) { n = ps.length for (t = 0 to 1 step 0.01) { x = 0 y = 0 for (i in ps.indices) { m = nCr(n - 1, i) * ((1 - t) ^ (n - 1 - i)) * (t ^ i) x += ps[i].x * m y += ps[i].y * m } drawPixel(round(x), round(y)) } }
點陣圖方式繪製曲線
製作多段曲線
總結
由於這個函式實際是一些數學運算,除了貝茲曲線,其他繪製方法都是回憶一些中小學的數學知識而編寫
將舊知識溫顧知新可以鞏固技術,沒有工具便自行製作工具,可以避免受別人的工具而限制自己
將舊知識溫顧知新可以鞏固技術,沒有工具便自行製作工具,可以避免受別人的工具而限制自己
沒有留言 :
張貼留言