M5Stackでアンチエイリアスを使って円を描く関数を作ってみました。
プログラムはM5Stackのスケッチですが、描画アルゴリズムはM5Stackに特化したものではなく、一般的なものです。このプログラム自体も元はWindowsのVC6.0で作られたもの(https://www.programmersought.com/article/88002266361/ ) を参考にしました。
正直に言って、私は描画アルゴリズムに詳しくありません。ほぼ素人と言っていいレベルです。このアルゴリズムはXiaolin Wu(ウー・シャオリン)という大学教授のものらしいのですが、正直その仕組みはよくわかりません。でも、仕組みがわからなくても使ってるものってたくさんあると思うので、それで勘弁してください。(プログラムは数行なので、それほど難しいものではないはずです。私は説明できませんが)ウー・シャオリンのアンチエイリアス・アルゴリズムについては、英語ではそれなりに情報があったのですが、日本語ではほとんど情報がなかったことはちょっと気になっています。
プログラムについて
プログラムはBボタンを押す毎に、既存関数版とアンチエイリアス版が表示されます。
少しだけ説明すると、アルファブレンドの計算式は
背景色 + (前景色 - 背景色)*アルファ値(0〜1)
で、これをR,G,Bのそれぞれについて計算するのだそうです。
color565()は24bitカラーを16bitカラーにする処理で、M5StackのLCDが16bitカラーのためです。
// 21/01/22 追記
アルファブレンド関数を外出しにしました。
// original code from https://www.programmersought.com/article/88002266361/
// アンチエイリアスで、円を描画する。
#include <M5Stack.h>
void setup() {
Serial.begin(115200);
M5.begin();
M5.Power.begin();
M5.Lcd.setBrightness(200); //バックライトの明るさを0(消灯)~255(点灯)で制御
}
inline uint16_t alphaBlend(
uint8_t r, uint8_t g, uint8_t b, // 前景色
uint8_t rr, uint8_t gg, uint8_t bb, // 背景色
double d) // アルファ値(0〜1)
{
return M5.Lcd.color565(
rr + (r - rr) * d, gg + (g - gg) * d, bb + (b - bb) * d);
}
// 8 points system Circle
void div_8(int centerX, int centerY, int x, int y,
uint8_t r, uint8_t g, uint8_t b,
uint8_t rr, uint8_t gg, uint8_t bb, double d) // d アルファ値(0〜1)
{
M5.lcd.drawPixel(centerX + x, centerY + y, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX + y, centerY + x, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX + x, centerY - y, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX + y, centerY - x, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX - x, centerY + y, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX - y, centerY + x, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX - x, centerY - y, alphaBlend(r, g, b, rr, gg, bb, d));
M5.lcd.drawPixel(centerX - y, centerY - x, alphaBlend(r, g, b, rr, gg, bb, d));
}
void OnwuCir(int centerX, int centerY, int r,
uint8_t rgbR, uint8_t rgbG, uint8_t rgbB, // 図形色
uint8_t rgbRR, uint8_t rgbGG, uint8_t rgbBB, // 背景色
bool insideDraw = true) // 内側線を描く/描かない
{
double e;
int y = r;
for (int x = 0; x <= y; x ++) // 1/8 circle Videos
{
// points to count down from the ideal circle, as the brightness parameter
e = y - sqrt(r * r - (x + 1) * (x + 1));
if (e >= 1)
{
e--;
y--;
}
div_8(centerX, centerY, x, y, rgbR, rgbG, rgbB, rgbRR, rgbGG, rgbBB, 1 - e);
if (insideDraw)
div_8(centerX, centerY, x, y - 1, rgbR, rgbG, rgbB, rgbRR, rgbGG, rgbBB, e);
}
}
void loop() {
static bool firstTime = true;
M5.update();
if (firstTime || M5.BtnB.wasPressed()) {
firstTime = false;
static int state = 0;
// 図形色・文字色
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
// 背景色
uint8_t rr = 255;
uint8_t gg = 255;
uint8_t bb = 255;
M5.lcd.clear(M5.Lcd.color565(rr, gg, bb));
M5.Lcd.setCursor(10, 10);
M5.Lcd.setTextColor(M5.Lcd.color565(r, g, b));
M5.Lcd.setTextFont(4);
M5.Lcd.println(String(state));
switch (state)
{
case 0:
// 既存関数で円周を描く
M5.Lcd.drawCircle(160, 120, 100, M5.Lcd.color565(r, g, b));
break;
case 1:
// アンチエイリアス関数で円周を描く
OnwuCir(160, 120, 100, r, g, b, rr, gg, bb);
break;
case 2:
// 既存関数で円を塗りつぶす
M5.Lcd.fillCircle(160, 120, 100, M5.Lcd.color565(r, g, b));
break;
case 3:
// アンチエイリアス関数で円周を描いた後、円を塗りつぶす
OnwuCir(160, 120, 100, r, g, b, rr, gg, bb);
M5.Lcd.fillCircle(160, 120, 100, M5.Lcd.color565(r, g, b));
break;
case 4:
// アンチエイリアス関数で円周を描いた後、円を塗りつぶす(内側を描く処理を省くことでやや高速)
OnwuCir(160, 120, 100, r, g, b, rr, gg, bb, false);
M5.Lcd.fillCircle(160, 120, 100, M5.Lcd.color565(r, g, b));
break;
default:
break;
}
state = (state < 4) ? state + 1 : 0;
}
}