Pxcels[x,y]を使用することで、簡単にプログラミングできることは分かりました。しかし処理の遅さからとても実用的とは言い難いです。
そこで、次にScanLineというプロパティを使って高速化する方法を考えてみます。
ScanLineはTBitmapクラスのプロパティで(実際にはFunction定義されていますが、ヘルプにはプロパティと記載されています...^^;)、ビットマップの指定された1行分のデータを返します。
1行分と強調したのは、ビットマップの幅を越えて値を参照した場合の動作が保証されないためです。
また、ScanLineの戻り値はPointer型です。行内のピクセルを参照するにはPByteArray型のポインタ変数で受け取って、P[x]の様に配列要素としてアクセスします。このことは次のテーマで触れますので、しっかり覚えておいて下さい。
では、新しく追加したScanLineボタンの処理を見てみましょう。
//*********** ScanLine 使用ルーチン ***********
procedure TForm1.btnScanlClick(Sender: TObject);
var
x, y: Integer;
R, G, B: Byte;
Pnt: PByteArray;
begin
Image1.Picture.Bitmap.PixelFormat := pf24bit;
Tmstart;
for y := 0 to Image1.Height -1 do
begin
Pnt := Image1.Picture.Bitmap.ScanLine[y];
for x := 0 to Image1.Width -1 do
begin
//***** R,G,Bに分解
//***** バイトの並びは B->G->R
R := Pnt[x * 3 +2];
G := Pnt[x * 3 +1];
B := Pnt[x * 3 ];
if not chkR.Checked then R := 0;
if not chkG.Checked then G := 0;
if not chkB.Checked then B := 0;
Pnt[x * 3 +2] := R;
Pnt[x * 3 +1] := G;
Pnt[x * 3 ] := B;
end;
end;
Image1.Refresh;
Tmend;
end;
|
今回はTcolor型の代わりにPointer型のPntという変数を用意しています。
Pnt := Image1.Picture.Bitmap.ScanLine[y];
で、1行分のデータが入ります(実際はビットマップデータ内の指定行のアドレスが返される)。
各ピクセルを参照するのはPnt[x]となるのですが、ビットマップのフォーマットを24ビットカラーとしているので、3バイトで1ピクセル分のデータとなります。
RGB各成分を取り出すのは、例によって下位バイトからになるのですが、今度はビットマスクでは無く直接添字から取り出せます。
R := Pnt[x * 3 +2];
G := Pnt[x * 3 +1];
B := Pnt[x * 3 ];
メモリ上には次のようにデータが入ってることになります。
| 0ピクセル目 | 1ピクセル目 | ..... | ||||
| +0 | +1 | +2 | +0 | +1 | +2 | |
| 青 | 緑 | 赤 | 青 | 緑 | 赤 | |
結果を反映するにはRGBデータをそのまま元の所に返すだけす。RGB成分を直接操作しているのでRgb関数も必要ありません。
Pnt[x * 3 +2] := R;
さて速度はどうでしょうか?
前の結果が3000ミリ秒台だったのに対して、今回は30〜40ミリ秒でできてしまいました。驚くべき結果ですね!
満足いく結果が得られたわけですが、次にプログラミングの効率化という点で考えてみるとどうでしょうか?
RGBの成分要素を取り出すのに、+3,+2とアドレス計算をしているのはどうもスマートではありません。
また、例題を24ビット画像から32画像を扱うように改造する場合はどうでしょう?
この例題では極めてシンプルな処理に限っているのですが、実際に実用的なプログラムを書く場合何倍も複雑になり、こういうスマートでない部分がデバッグやメンテナンスのネックになるものです。
次のテーマでは、配列を上手く使いスマートで効率的なプログラミングを目指します。