ScanLine
24 ビット ビットマップ ピクセル操作にプロパティを使用する方法は? Pixels
非常に頻繁に使用されるプロパティではなく、それを使用することを好むのはなぜですか?
1 に答える
1.はじめに
この投稿ではScanLine
、24 ビット ビットマップ ピクセル形式のみのプロパティの使用法と、実際に使用する必要があるかどうかについて説明します。最初に、このプロパティが非常に重要な理由を見てみましょう。
2. スキャンラインかどうか...?
ScanLine
プロパティを使用するなどのトリッキーな手法を使用する理由を自問することができPixels
ます。その答えは、比較的小さなピクセル領域でもピクセル変更を実行すると、大きなパフォーマンスの違いが顕著になることです。
このプロパティは、デバイス コンテキストの色の値を取得および設定するために、 Windows API 関数と をPixels
内部的に使用します。技術のパフォーマンス不足は、通常、ピクセルの色の値を変更する前に取得する必要があることです。これは、言及された両方の Windows API 関数の呼び出しを内部的に意味します。プロパティは、ビットマップ ピクセル データが格納されているメモリへの直接アクセスを提供するため、この競争に勝っています。また、直接メモリ アクセスは、2 つの Windows API 関数呼び出しよりも高速です。GetPixel
SetPixel
Pixels
ScanLine
ただし、プロパティが完全に悪いというわけではなくPixels
、すべての場合に使用を避ける必要があります。たとえば、いくつかのピクセル (大きな領域ではない) をときどき変更する場合は、それでPixels
十分な場合があります。ただし、ピクセル領域を操作する場合は使用しないでください。
3. ピクセルの奥深く
3.1 生データ
ビットマップのピクセル データ (ここでは生データと呼びましょう) は、1 ピクセルごとに色成分の強度値のシーケンスを含む 1 次元のバイト配列として想像できます。ビットマップ内のすべてのピクセルは、使用されるピクセル形式に応じて固定数のバイトで構成されます。
たとえば、24 ビットのピクセル形式には、その色成分 (赤、緑、青のチャネル) ごとに 1 バイトがあります。次の図は、このような 24 ビット ビットマップの生データバイト配列を想像する方法を示しています。ここで色付きの四角形はそれぞれ 1 バイトを表します。
3.2 ケーススタディ
24 ビットのビットマップ 3x2 ピクセル (幅 3 ピクセル、高さ 2 ピクセル) があると想像してください。ここでは、いくつかの内部構造を説明し、ScanLine
プロパティの使用方法の原則を示しますので、覚えておいてください。内部を深く見るためにスペースが必要なため、非常に小さいです(明るい視力を持っている人は、ここでpng形式のそのような画像の緑色の例です ↘ ↙ :-)
3.3 画素構成
最初に、ビットマップ イメージのピクセル データが内部でどのように格納されているかを見てみましょう。生データを見てください。次の画像は生データのバイト配列を示しています。ここでは、小さなビットマップの各バイトとその配列内のインデックスを確認できます。また、3 バイトのグループが個々のピクセルを形成する方法と、これらのピクセルがビットマップ上にある座標を確認できます。
同じものの別のビューは、次の画像を提供します。各ボックスは、そこにある架空のビットマップの 1 ピクセルを表します。各ピクセルで、その座標と生データバイト配列からのインデックスを含む 3 バイトのグループを確認できます。
4. 色とともに生きる
4.1. 初期値
すでに知っているように、架空の 24 ビット ビットマップのピクセルは 3 バイト (各カラー チャネルに 1 バイト) で構成されています。想像の中でこのビットマップを作成したとき、すべてのピクセルのすべてのバイトは、意図に反して最大バイト値 (255) に初期化されました。これは、すべてのチャネルが最大の色強度を持つようになったことを意味します。
各ピクセルのこれらの初期チャネル値からどの色が混合されているかを見ると、ビットマップがentirely white
. そのため、Delphi で 24 ビット ビットマップを作成すると、最初は白になります。デフォルトでは、白はすべてのピクセル形式でビットマップになりますが、初期の生データのバイト値が異なる場合があります。
5.スキャンラインの秘密の生活
上記の読みから、ビットマップ データが生データバイト配列に格納される方法と、これらのデータから個々のピクセルがどのように形成されるかを理解していただければ幸いです。次に、プロパティ自体に移り、生データScanLine
の直接処理にどのように役立つかを説明します。
5.1. スキャンラインの目的
この投稿のメイン ディッシュであるプロパティは、ビットマップ内の指定された行に属する生データScanLine
バイトの配列の最初のバイトへのポインタを返す、読み取り専用のインデックス付きプロパティです。言い換えれば、特定の行の生データバイトの配列へのアクセスを要求し、受け取るものはその配列の最初のバイトへのポインターです。このプロパティの index パラメータは、これらのデータを取得する行の 0 ベースのインデックスを指定します。
次の画像は、架空のビットマップと、ScanLine
さまざまな行インデックスを使用してプロパティによって取得されるポインターを示しています。
5.2. スキャンラインの利点
したがって、私たちが知っていることScanLine
から、特定の行データのバイト配列へのポインターを与えることを要約できます。そして、その生データの行配列を使用して作業できます-そのバイトを読み取ったり上書きしたりできますが、特定の行の配列境界の範囲内でのみです。
さて、特定の行の各ピクセルの色強度の配列があります。そのような配列の反復を考慮してください。この配列を 1 バイトずつループして、ピクセルの 3 つの色部分の 1 つだけを調整するのはあまり快適ではありません。以前と同じように、ピクセルをループして、反復ごとに 3 つのカラー バイトすべてを一度に調整する方がよいでしょPixels
う。
5.3. ピクセルを飛び越える
行配列ループを単純化するには、ピクセル データに一致する構造体が必要です。幸いなことに、24 ビット ビットマップには次のRGBTRIPLE
構造があります。のように翻訳された Delphi ではTRGBTriple
。この構造は、要するに次のようになります (各メンバーは 1 つのカラー チャネルの強度を表します)。
type
TRGBTriple = packed record
rgbtBlue: Byte;
rgbtGreen: Byte;
rgbtRed: Byte;
end;
私は 2009 年以前のバージョンの Delphi を使用している人にも寛容になるように努めており、それによってコードがより理解しやすくなるため、反復にはポインター演算を使用しませんが、次の例ではポインターを含む固定長配列を使用します (ポインター以下の Delphi 2009 では、算術演算は読みにくくなります)。
これでTRGBTriple
、ピクセルの構造ができたので、行配列の型を定義します。これにより、ビットマップ行ピクセルの反復が簡素化されます。これは、ShadowWnd.pas ユニット (とにかく、1 つの興味深いクラスのホーム) から借りたものです。ここにあります:
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..4095] of TRGBTriple;
ご覧のとおり、1 行あたり 4096 ピクセルの制限があり、通常は幅の広い画像にはこれで十分です。これで十分でない場合は、上限を増やしてください。
6.実際のスキャンライン
6.1. 2行目を黒くする
最初の例から始めましょう。架空のビットマップをオブジェクト化し、適切な幅、高さ、およびピクセル形式 (または、必要に応じてビット深度) を設定します。次にScanLine
、行パラメーター 1 を使用して、2 番目の行の生データバイト配列へのポインターを取得します。取得したポインタはRowPixels
、 の配列を指す変数に割り当てます。TRGBTriple
そのため、それ以降、行ピクセルの配列として取得できます。次に、この配列をビットマップの幅全体で反復し、各ピクセルのすべての色の値を 0 に設定します。これにより、最初の行が白 (前述のようにデフォルトでは白) で 2 行目が黒のビットマップになります。 . このビットマップはファイルに保存されますが、実際に非常に小さいので、驚かないでください。
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..4095] of TRGBTriple;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
Bitmap: TBitmap;
Pixels: PRGBTripleArray;
begin
Bitmap := TBitmap.Create;
try
Bitmap.Width := 3;
Bitmap.Height := 2;
Bitmap.PixelFormat := pf24bit;
// get pointer to the second row's raw data
Pixels := Bitmap.ScanLine[1];
// iterate our row pixel data array in a whole width
for I := 0 to Bitmap.Width - 1 do
begin
Pixels[I].rgbtBlue := 0;
Pixels[I].rgbtGreen := 0;
Pixels[I].rgbtRed := 0;
end;
Bitmap.SaveToFile('c:\Image.bmp');
finally
Bitmap.Free;
end;
end;
6.2. 輝度を使用したグレースケール ビットマップ
意味のある例として、輝度を使用してビットマップをグレースケールする手順をここに投稿します。上から下まですべてのビットマップ行の反復を使用します。行ごとに、生データへのポインターが取得され、以前と同様にピクセルの配列として取得されます。その配列の各ピクセルについて、次の式によって輝度値が計算されます。
Luminance = 0.299 R + 0.587 G + 0.114 B
次に、この輝度値が反復ピクセルの各色成分に割り当てられます。
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..4095] of TRGBTriple;
procedure GrayscaleBitmap(ABitmap: TBitmap);
var
X: Integer;
Y: Integer;
Gray: Byte;
Pixels: PRGBTripleArray;
begin
// iterate bitmap from top to bottom to get access to each row's raw data
for Y := 0 to ABitmap.Height - 1 do
begin
// get pointer to the currently iterated row's raw data
Pixels := ABitmap.ScanLine[Y];
// iterate the row's pixels from left to right in the whole bitmap width
for X := 0 to ABitmap.Width - 1 do
begin
// calculate luminance for the current pixel by the mentioned formula
Gray := Round((0.299 * Pixels[X].rgbtRed) +
(0.587 * Pixels[X].rgbtGreen) + (0.114 * Pixels[X].rgbtBlue));
// and assign the luminance to each color component of the current pixel
Pixels[X].rgbtRed := Gray;
Pixels[X].rgbtGreen := Gray;
Pixels[X].rgbtBlue := Gray;
end;
end;
end;
そして、上記の手順の可能な使用法。この手順は、24 ビット ビットマップに対してのみ使用できることに注意してください。
procedure TForm1.Button1Click(Sender: TObject);
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('c:\ColorImage.bmp');
if Bitmap.PixelFormat <> pf24bit then
raise Exception.Create('Incorrect bit depth, bitmap must be 24-bit!');
GrayscaleBitmap(Bitmap);
Bitmap.SaveToFile('c:\GrayscaleImage.bmp');
finally
Bitmap.Free;
end;
end;