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の環境は以下の通りです。

RaspberryPi
項目 内容
HW RaspberryPi3 model B
OS Raspbian GNU/Linux
buster
ライブラリ pigpio Version 71
(gpioVersion()関数で確認)

SSD1306の設定です。


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の方が大きくなければならない、という条件がありますが…。
(この辺りの処理は、本エントリの内容にあまり関係ないので、省略しています。)

色々参考になれば幸いです。

ではっ!