MoveTo、LineToメソッドで描いた線とPen.Widthの特性。。。とScanLineについて。

Delphi,Programing,TBitmap,TCanvas,TImage,VCL

TBitmapコンポーネントなどには、そのBitmap上に線を引くために
・MoveTo(x,y)
・LineTo(x,y)
というメソッドが用意されてます。

MoveTo(x,y)で線の始点になる座標を指定し、LineTo(x,y)で指定した座標まで直線を描きます。

また、Pen.Widthプロパティで線の太さを指定できます。
Pen.Widthプロパティに1を指定すれば1ピクセル、2を指定すれば2ピクセルの太さの線を描けます。

ではこれをふまえて、以下のプログラムを見てみます。

procedure LineDraw;
var
Bitmap  : TBitmap;
begin
Bitmap  := TBitmap.Create;
Bitmap.Width    := 10;
Bitmap.Height   := 10;
Bitmap.Canvas.Pen.Color := clRed;
Bitmap.Canvas.Pen.Width := 1;
Bitmap.Canvas.MoveTo(5,0);
Bitmap.Canvas.LineTo(10,0);
end;

解説用にざっくり書きましたので、実用性等は言及無しで(
上記のプロシージャは、新たにBitmapを生成して縦横10×10の大きさに指定しています。
このBitmapに(5,0)から(5,10)に向かって太さ1ピクセルの赤い縦線を描きます。

つまりこうですね。
f:id:tsubakurame-1913:20160906175507p:plain

これは簡単です。誰にでもイメージできると思います。
では、もし仮にPen.Widthが2以上になった時は、どのように描画されるのでしょうか?
縦に2ピクセル分塗られるわけですが、それはx=5の位置ともう1ヶ所はどこでしょうか?
x=4なのか、x=6なのか。。。

ということでこれを調べましょう。
どうやって調べるかというと。
指定したピクセルの色情報を抜き出すことで、どのピクセルが塗られているのかを調べることにします。

そのためにScanLineを使います。

上記のソースをちょっといじりましょう。

procedure LineDraw;
var
Bitmap  : TBitmap;
pByte   : PByteArray;
begin
Bitmap  := TBitmap.Create;
Bitmap.Width        := 10;
Bitmap.Height       := 10;
Bitmap.PixelFormat  := pf24bit;
Bitmap.Canvas.Pen.Color := clBlack;
Bitmap.Canvas.Pen.Width := 2;
Bitmap.Canvas.MoveTo(5,0);
Bitmap.Canvas.LineTo(10,0);
pByte   := Bitmap.ScanLine[0];
end;

こうなります。
PixelFormatはいろいろ種類がありますが、24bitが一般的です。
RGBそれぞれ8bit(0~255)×3です。

pByte  := Bitmap.ScanLine[0];

でpByteにBitmap上、y=0の1行分のデータがまるっと配列として代入されます。
このとき、RGBカラー情報がすべて別に代入されるので、配列の要素数としてはwidth×3となります。
今回だと30ってことですね。
配列の並び順としては、RGBではなく逆のBGRで並んでいることに注意。

あとは、pByteをMemoコンポーネントでもcsvにして吐き出すでもいいので出力してやればOK。

ということで、Pen.Widthを2~5まで変化させて、どのように直線が描かれるのかをまとめたのが以下の画像です。

f:id:tsubakurame-1913:20160906181239p:plain
f:id:tsubakurame-1913:20160906181241p:plain

と、このような結果になりました。
Pen.Widthが奇数の時は、指定した座標を中心に描画。
Pen.Widthが偶数の時は、指定した座標よりマイナス方向へはみ出して描画されるようです。

仕事が詰まっているので今回は必要だった縦方向のみの検証です。
おそらく横方向にも同じ特性が言えるのではないかと思います。

精密な描画を求められた時、1ピクセルの計算ズレも致命な欠陥に繋がるのでこの特性は覚えておきたいですね。