最近使用 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))
}
}
點陣圖方式繪製曲線
製作多段曲線
總結
由於這個函式實際是一些數學運算,除了貝茲曲線,其他繪製方法都是回憶一些中小學的數學知識而編寫
將舊知識溫顧知新可以鞏固技術,沒有工具便自行製作工具,可以避免受別人的工具而限制自己
將舊知識溫顧知新可以鞏固技術,沒有工具便自行製作工具,可以避免受別人的工具而限制自己
沒有留言 :
張貼留言