Delphi で画像解析コンポーネントを作成する方法
Delphi は強力な RAD 開発ツールとして、アプリケーション ソフトウェア開発において常に独自の利点を持っています。この利点は画像関連ソフトウェアの開発にも反映されます。デスクトップに画像を配置したい場合は、Image コントロールをデスクトップに配置するだけで、その Image プロパティを通じて BMP、WMF、EMF などの形式の画像を任意に読み込むことができます。 JPEG のサポートを追加する場合は、JPEG ユニットを追加するだけです。 Image に JPEG をロードした後でも、Delphi は自動的に JPEG ユニットを追加します。すべてがとても簡単です。基本的な形式は VCL にカプセル化されていますが、Delphi は JPEG などの画像形式のサポートをどのように実装するのでしょうか?
実際、TPicture から実装プロセスを確認するのは簡単です。TPicture は、すべての画像オブジェクトのコンテナーとして理解できます。
たとえば、JPEG.pas には次の 2 行のコードがあります。
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'JPEG 画像ファイル'、JConsts.pas を参照)
それはどういう意味ですか?これは、TJPEGImage を 2 つのサフィックス (jpeg と jpg) が付いた画像ファイルとして登録するクラスとして理解できます。
本質は、サフィックス、画像の説明、特定の画像解析クラス、およびその他の情報を FileFormats に保存することです。
詳細については、次のコードを参照してください。
var FileFormats: TFileFormatsList = nil;
class PROcedure TPicture.RegisterFileFormat(const AExtension,
A説明: 文字列; AGraphicClass: TGraphicClass);
始める
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
終わり;
関数 GetFileFormats: TFileFormatsList;
始める
FileFormats = nil の場合、FileFormats := TFileFormatsList.Create;
結果 := ファイル形式;
終わり;
TPicture は、TFileFormatsList のコンストラクターに追加されているため、デフォルトで 4 つの画像形式をサポートします。
コンストラクター TFileFormatsList.Create;
始める
継承された作成。
Add('wmf', SVMetafiles, 0, TMetafile);
Add('emf', SVEnhMetafiles, 0, TMetafile);
Add('ico', SVIcons, 0, TIcon);
Add('bmp', SVBitmaps, 0, TBitmap);
終わり;
OpenPictureDialog コントロールは、FileFormats に保存された情報を使用して、サポートされているファイル タイプのリストを自動的に生成します。
では、これらの画像解析クラスはどのように記述すればよいのでしょうか?
TGraphic は、TBitmap、TIcon、および TMetafile オブジェクトの基本クラスです。同様に、ここでの画像解析クラスも TGraphic から派生する必要があり、VCL にカプセル化された多くのコードを使用することで、多くの作業を節約できます。
基本的な関数を実装するには、通常、次の 3 つのメンバーをオーバーロードするだけで済みます。
TXXXImage = クラス(Tグラフィック)
保護された
procedure Draw(ACanvas: TCanvas; const Rect: TRect);//画像をキャンバスに描画します
公共
プロシージャ LoadFromStream(Stream: TStream); // ストリームから画像データを取得します。
プロシージャ SaveToStream(Stream: TStream); // ストリームに画像データを書き込みます。
終わり;
TGraphic.LoadFromFile/TGraphic.SaveToFile にはファイル名からストリームへのデータ読み込み/ストリーム内のデータを該当ファイルへ書き込む機能が既に実装されているため、特別な必要がなければオーバーロードする必要はありません。 TCanvas は GDI を完全にカプセル化しているため、GDI を使用してフォームに画像を描画するプロセスを考慮する必要はありません。残っているのは、画像解析部分のコードを記述することだけです。
さらなる議論のために、RAS フォーマットを例として取り上げてみましょう。
ここでは基本クラスとして TGraphic を使用せず、TBitmap を使用することで、Draw の実装処理をさらに省き、ビットマップへの変換処理を LoadFromStream に実装するだけで済みます。
タイプ
TRASGraphic = クラス(TBitmap)
公共
プロシージャ LoadFromStream(Stream: TStream);
プロシージャ SaveToStream(Stream: TStream);
終わり;
// RAS ファイルヘッダーを記述するレコードタイプを定義します
TRASHeader = パックされたレコード
マジック、//マーク
幅、//幅
高さ、//高い
深度、//色の深度
長さ、//画像データの長さは 0 に等しい可能性があります
RasType, //フォーマットタイプ
MapType, //パレットタイプ
MapLength: Cardinal; //パレットのデータ長;
終わり;
// RAS ファイルのヘッダーを記述するために使用されるレコード タイプを定義することが非常に必要です
定数
//すべてのタイプの RAS を表す定数を定義します
RT_OLD = 0;
RT_STANDARD = 1;
RT_BYTE_ENCODED = 2;
RT_FORMAT_RGB = 3;
RT_FORMAT_TIFF = 4;
RT_FORMAT_IFF = 5;
RT_EXPERIMENTAL = $FFFF;
//パレットのタイプを表す定数を定義します
RMT_NONE = 0; //パレットデータなし
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{RASのフォーマットがRT_OLDの場合、データ長は0になる場合があります}
関数 SwapLong(const 値: Cardinal): Cardinal;
アズム
BSWAP EAX//バイト交換命令の呼び出し
終わり;
//例外をスローします。パラメータは特定の例外情報です
プロシージャ RasError(const ErrorString: String);
始める
raise EInvalidGraphic.Create(ErrorString);
終わり;
{以下は実装部分のコードです。 }
プロシージャ TRASGraphic.LoadFromStream(Stream: TStream);
変数
ヘッダー: TRASヘッダー;
行8: Pバイト;
行 24: PRGBTriple;
行 32: PRGBQuad;
Pマップ: Pバイト;
Y: 整数。
I: 整数。
読み取られたマップ: ブール値;
Pal: TMaxLogPalette;
R、G、B: バイトの配列[0..255]。
カラーバイト: バイト;
始める
ストリームドゥで
始める
ReadBuffer(Header, SizeOf(Header)); // ファイルヘッダーデータをレコードヘッダーに読み込みます。
ヘッダー付き
始める
幅 := SwapLong(Width);
高さ := SwapLong(高さ);
深さ := SwapLong(深さ);
長さ := SwapLong(長さ);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
MapLength := SwapLong(MapLength);
終わり;
//データを読み取る順序により、順序を変更するには上記の SwapLong を呼び出す必要があります。
if (Header.Magic = $956AA659) および
(Header.Width<>0) および (Header.Height<>0) および
([1,8,24,32] の Header.Depth) および ([RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB] の Header.RasType)
始める
幅 := ヘッダー.幅;
高さ := ヘッダーの高さ;
MapReaded := False;
ケースのヘッダーの深さ
1:ピクセルフォーマット:= pf1Bit;
8:
始める
ピクセルフォーマット:= pf8Bit;
case Header.MapType の
RMT_NONE:
始める
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
for I := 0 ~ 255 を実行します
始める
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
終わり;
パレット := CreatePalette(PLogPalette(@Pal)^);
//画像の色深度が 8 ビットで、パレット情報が存在しない場合、8 ビットのグレースケール パレットを作成します
終わり;
RMT_EQUAL_RGB:
始める
if (Header.MapLength = 3*256) then
始める
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
ReadBuffer(R,256);
ReadBuffer(G,256);
ReadBuffer(B,256);
for I := 0 ~ 255 を実行します
始める
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
終わり;
パレット := CreatePalette(PLogPalette(@Pal)^);
//ファイル内のパレット情報を読み取る
// API 関連のパレット操作については、MSDN を確認してください。
終わり
それ以外
RasError('パレットの長さが間違っています!');
MapReaded := True;
終わり;
RMT_RAW:
始める
RasError('サポートされていないファイル形式!');
終わり;
終わり;
終わり;
24:ピクセルフォーマット:= pf24Bit;
32:
始める
ピクセルフォーマット:= pf32Bit;
//
終わり;
終わり;
if (MapReaded ではない) かつ (Header.MapLength>0)
始める
位置 := 位置 + Header.MapLength;
終わり;
//パレット長が0ではなく、関連情報が正しく読み取れない場合は、このデータをスキップしてください
ケースのヘッダーの深さ
8:
始める
Header.RasType = RT_BYTE_ENCODED の場合
始める
//エンコード
//RLE圧縮のエンコード、デコードについてはご自身でご確認ください。
RasError('圧縮形式はサポートされていません!');
終わり
それ以外
始める
for Y := 0 から Height-1 まで
始める
行8:=スキャンライン[Y];
ReadBuffer(Row8^,Width);
if (幅 mod 2)=1 then
始める
位置 := 位置 + 1;
終わり;
終わり;
終わり;
終わり;{8ビットの終わり}
24:
始める
case Header.RasType の
RT_OLD、
RT_標準:
始める
for Y := 0 から Height-1 まで
始める
行 24:= スキャンライン[Y];
ReadBuffer(Row24^,Width*3);
if (幅 mod 2)=1 then
始める
位置 := 位置 + 1;
終わり;
終わり;
終わり;
RT_BYTE_ENCODED:
始める
//エンコード
//RLE圧縮のエンコード、デコードについてはご自身でご確認ください。
RasError('圧縮形式はサポートされていません!');
終わり;
RT_FORMAT_RGB:
始める
for Y := 0 から Height-1 まで
始める
行 24:= スキャンライン[Y];
ReadBuffer(Row24^,Width*3);
for I := 0 から width-1 まで
始める
ColorByte := Row24^.rgbtRed;
Row24^.rgbtRed := Row24^.rgbtBlue;
Row24^.rgbtBlue := ColorByte;
Inc(行24);
終わり;
//RT_FORMAT_RGB形式の場合はRGBでデータを取得します、ここでRとBの値を交換する必要があります
if (幅 mod 2)=1 then
始める
位置 := 位置 + 1;
終わり;
終わり;
終わり;{RT_FORMAT_RGB の終わり}
それ以外
RasError('サポートされていないファイル形式!');
終わり;
終わり;{24ビットの終わり}
32:
始める
case Header.RasType の
RT_OLD、
RT_標準:
始める
for Y := 0 から Height-1 まで
始める
行 32:= スキャンライン[Y];
ReadBuffer(Row32^,Width*4);
for I := 0 から width-1 まで
始める
ColorByte := Row32^.rgbReserved;
Row32^.rgbReserved := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(行32);
終わり;
//32ビットカラーを使用する場合は、読み込み後にデータの順序を調整する必要があります。
終わり;
終わり;
RT_BYTE_ENCODED:
始める
//エンコード
//RLE圧縮のエンコード、デコードについてはご自身でご確認ください。
RasError('圧縮形式はサポートされていません!');
終わり;
RT_FORMAT_RGB:
始める
Y := 0 から高さ-1 の場合、次のようにします。
始める
行 32:= スキャンライン[Y];
ReadBuffer(Row32^,Width*4);
for I := 0 から width-1 まで
始める
ColorByte := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbReserved;
Row32^.rgbReserved := ColorByte;
ColorByte := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(行32);
終わり;
//順序調整とRとBの値の交換のためのコードがここにマージされます。
終わり;
終わり;{RT_FORMAT_RGB の終わり}
それ以外
RasError('サポートされていないファイル形式です!');
終わり;{32ビットの終わり}
終わり;
それ以外
始める
無料画像;
RasError('サポートされていないファイル形式です!');
終わり;
終わり;
終わり
それ以外
RasError('サポートされていないファイル形式です!');
終了;{で終わる}
終わり;
{次のコードは上記のコード内に複数回出現します:
if (幅 mod 2)=1 then
始める
位置 := 位置 + 1;
終わり;
これは、各行のデータがワード単位で整列されている必要がある、つまり、各行のデータが偶数バイトで記録されている必要があるためです。各画素の色情報が 1 バイト(8 ビット)または 3 バイト(24 ビット)で記録されており、各行の画素数が奇数の場合は 1 バイトをパディングする必要があります。したがって、ここでは 1 バイトがスキップされます。
背後のコードで
if (幅 mod 2) = 1 then
始める
FillByte:=0;
Stream.Write(FillByte,1);
終わり;
これも同じ原理に基づいています。 }
プロシージャ TRASGraphic.SaveToStream(Stream: TStream);
変数
ヘッダー: TRASヘッダー;
行8: Pバイト;
行 24: PRGBTriple;
行 32: PRGBQuad;
FillByte: バイト;
Y: 整数。
I: 整数。
Pal: TMaxLogPalette;
R、G、B: バイトの配列[0..255]。
始める
Header.Magic := $956AA659;
Header.Width := SwapLong(Width);
Header.Height := SwapLong(Height);
Header.RasType := SwapLong(RT_STANDARD);
(PixelFormat = pf1bit) または (PixelFormat = pf4bit) の場合
PixelFormat:=pf8bit
else if (PixelFormat <> pf8bit) および (PixelFormat <> pf24bit) および (PixelFormat <> pf32bit) then
ピクセルフォーマット:=pf24bit;
の場合の PixelFormat
pf8ビット:
始める
Header.Length := SwapLong(高さ*(幅+(幅mod 2)));
Header.Depth := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer(ヘッダー,サイズOf(ヘッダー));
GetPaletteEntries(Palette, 0, 256, Pal.palPalEntry);
for I := 0 ~ 255 を実行します
始める
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
終わり;
// API 関連のパレット操作については、MSDN を確認してください。
Stream.WriteBuffer(R,256);
Stream.WriteBuffer(G,256);
Stream.WriteBuffer(B,256);
for Y := 0 から Height-1 まで
始める
行 8 := スキャンライン[Y];
Stream.WriteBuffer(Row8^,Width);
if (幅 mod 2) = 1 then
始める
FillByte:=0;
Stream.Write(FillByte,1);
終わり;
終わり;
終わり;
pf32ビット:
始める
Header.Length := SwapLong(高さ*幅*4);
Header.Depth := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(ヘッダー,サイズOf(ヘッダー));
for Y := 0 から Height-1 まで
始める
行 32 := スキャンライン[Y];
for I := 0 から width-1 まで
始める
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Inc(行32);
終わり;
終わり;
終わり;
それ以外
始める
Header.Length := SwapLong(高さ*幅*3);
Header.Depth := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(ヘッダー,サイズOf(ヘッダー));
for Y := 0 から Height-1 まで
始める
行 24 := スキャンライン[Y];
Stream.WriteBuffer(Row24^,Width*3);
if (幅 mod 2) = 1 then
始める
FillByte:=0;
Stream.Write(FillByte,1);
終わり;
終わり;
終わり;
終わり;
//SaveToStream は基本的に LoadFromStream の逆の処理です。
終わり;
初期化
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
ファイナライズ
TPicture.UnregisterGraphicClass(TRASGraphic);
これらの数行のコードで、完全な画像解析コンポーネントが完成します。