RaspberryPiで有機ELディスプレイ(3)
SSD1306で直線を描画する
どもです。
ここ最近、SSD1306をarduinoとRaspberryPiで動かしています。
前回までのエントリで、RaspberryPiにドットができるようになりました。
今回のエントリではドットの描画を発展させて、直線を描画してみます。
1. 開発環境
毎度ですが、開発環境です。
項目 | 内容 |
---|---|
OS | Windows10 Pro(1909) |
CPU | i7-8700 |
メモリ | 16GB |
IDE | Eclipse Ver.2019-06(4.12.0) Build id 20190614-1200 |
また、RaspebrryPi/pigpioの環境は以下の通りです。
項目 | 内容 |
---|---|
HW | RaspberryPi3 model B |
OS | Raspbian GNU/Linux buster |
ライブラリ | pigpio Version 71 (gpioVersion()関数で確認) |
2. 直線の計算式
直線は、以下の式で表せます。
$$y=ax-b$$
ここで、プログラムで直線を描画する場合、入力する情報は多くの場合が直線が通る2つの点の座標です。
従って、入力される2つの座標から、上の式の「a」と「b」を求める必要があります。
小難しく書いていますが、仮に2つの点Aと点Bを通る直線のaとbは、以下の計算で求められます。
$$点A(x_0,y_0), 点B(x_1,y_1) \\
a=\frac{y_1-y_0}{x_1-x_0} \\
b=y_1-ax_1$$
これらの計算を、C言語で実装します。
3. 実装
3.1. 描画処理
それでは、上述の処理をC言語で実装します。
int deltaX = x1 - x0;
int deltaY = y1 - y0;
int yIntercept = 0;
yIntercept = y1 - ((deltaY * x1) / deltaX);
for (xPoint = x0; xPoint < x1; xPoint++) {
yPoint = (deltaY * xPoint / deltaX) + yIntercept;
drawPixel(xPoint, yPoint);
}
ここで、「a」は割り算を実施しません。
データ型を整数型で宣言しているので、x座標とy座標のそれぞれの差分の結果次第では、値が「0」で算出されてしまうからです。
3.2. 呼び出し側
上記の実装を用いて、色々な直線を描画します。
以下のようにして、処理を描画します。
for (x = 1; x < 128; x += 8) {
display.drawLine(0, 0, x, y);
display.display();
}
x = 127;
for (y = 63; 0 ≤ y; y -= 4) {
display.drawLine(1, 1, x, y);
display.display();
}
4. 実行
実際にプログラムを実行します。
プログラムを実行すると、SSD1306に下のような直線が描画されます。
パッと見ると、イイ感じに直線が描画されています。
しかし、よくよく注意して見ると、イケていない部分があります。
それは、画面の左側の方です。
…コレ、直線か?
5. プログラムの改修
5.1. 直線に見えない原因
この問題は、直線の傾き(「a」)が1以上の場合に発生します。
即ち、xの値が1増えた場合、yの値の増加は1以上です。
そのため、隣のドットとの縦方向の距離(間にあるドットの個数)は、1以上となります。
これにより、直線を引いているはずが、直線に見えない直線が描画される、という結果になります。
5.2. 解決(方法)
原因が分かったトコロで、解決方法です。
それは、「直線の傾きが1よりも大きい場合には、逆関数で描画する」という方法です。
これまでの方法では、x座標からy座標を算出してきました。
これを、「y座標からx座標を計算する」ように変更します。
5.3. 解決(実装)
傾きの判定と判定結果、および描画の処理は以下になります。
int isSteep = 0;
isSteep = abs(y1 - y0) > abs(x1 - x0);
if (0 != isSteep) {
SWAP(&x0, &y0);
SWAP(&x1, &y1);
}
int deltaX = x1 - x0;
int deltaY = y1 - y0;
int yIntercept = 0;
yIntercept = y1 - ((deltaY * x1) / deltaX);
int yPoint = y0;
int xPoint = x0;
for (xPoint = x0; xPoint < x1; xPoint++) {
yPoint = (deltaY * xPoint / deltaX) + yIntercept;
if (isSteep) {
this->drawPixel(yPoint, xPoint);
} else {
this->drawPixel(xPoint, yPoint);
}
}
2つの点のx座標とy座標の差分を比較して、y座標の差分がx座標の差分よりも大きかった場合に「傾きが1以上」と判定ができます。
そして、傾きが1以上の場合には、予めx座標とy座標を入れ替えておきます。
描画の際には、x座標とy座標を入れ替えるようにします。
5.4. 解決(実行結果)
上記のコードを実行した結果を示します。
なお、関数の呼び出し部分は、前述のコードと同じです。
先の「問題」となった図と比較すると、画面左側にも「直線」が描画されていることが分かります。
6. まとめ
今回は、RaspberryPiとSSD1306で直線を描画してみました。
エントリ中では、原点(画面左下)から画面の隅に対してのみ直線を引いています。
しかし、今回のエントリで紹介したコードでも、任意の箇所に直線を引くことができます。
ただし!
x0よりもx1の方が大きくなければならない、という条件がありますが…。
(この辺りの処理は、本エントリの内容にあまり関係ないので、省略しています。)
色々参考になれば幸いです。
ではっ!
ディスカッション
コメント一覧
まだ、コメントがありません