Delphi中如何編寫圖像解析組件
Delphi作為一個(gè)強(qiáng)大的RAD開發(fā)工具,在應(yīng)用軟件的開發(fā)方面一直有著它的獨(dú)特優(yōu)勢(shì)。這種優(yōu)勢(shì)同樣體現(xiàn)在圖像相關(guān)軟件的開發(fā)上。如果你要在桌面上放置一張圖像,只需要簡(jiǎn)單的在桌面上放置一個(gè)Image控件,然后就可以通過(guò)其Image屬性任意的加載BMP、WMF、EMF等格式的圖像。如果還想增加對(duì)JPEG的支持,只需要添加一個(gè)JPEG單元即可。甚至在Image中加載一張JPEG后,Delphi會(huì)自動(dòng)添加一個(gè)JPEG單元。一切做起來(lái)就是這么的簡(jiǎn)單。基本格式都已經(jīng)封裝在了VCL中,那么Delphi對(duì)類似JPEG這樣圖像格式的支持是如何實(shí)現(xiàn)的呢?
其實(shí)從TPicture中很容易看出其中的實(shí)現(xiàn)過(guò)程,它可以理解為所有圖像對(duì)象的容器。
如JPEG.pas中有如下兩句代碼:
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'JPEG Image File',見JConsts.pas)
什么意思呢?可以理解為將TJPEGImage注冊(cè)為jpeg、jpg兩種后綴圖像文件的類。
其實(shí)質(zhì)就是將后綴,圖像描述,具體圖像解析類等信息保存到了FileFormats。
具體見如下代碼:
var FileFormats: TFileFormatsList = nil;
class PRocedure TPicture.RegisterFileFormat(const AExtension,
ADescription: string; AGraphicClass: TGraphicClass);
begin
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
end;
function GetFileFormats: TFileFormatsList;
begin
if FileFormats = nil then FileFormats := TFileFormatsList.Create;
Result := FileFormats;
end;
而TPicture默認(rèn)支持四種圖像格式是因?yàn)門FileFormatsList的構(gòu)造函數(shù)中已進(jìn)行了添加。
constructor TFileFormatsList.Create;
begin
inherited Create;
Add('wmf', SVMetafiles, 0, TMetafile);
Add('emf', SVEnhMetafiles, 0, TMetafile);
Add('ico', SVIcons, 0, TIcon);
Add('bmp', SVBitmaps, 0, TBitmap);
end;
也正是通過(guò)FileFormats中保存的信息,控件OpenPictureDialog中自動(dòng)生成了所支持文件類型的列表。
那么該如何編寫這些圖像解析類呢?
TGraphic是TBitmap、TIcon、TMetafile對(duì)象的基類。同樣這里的圖像解析類也應(yīng)該從TGraphic派生,利用很多VCL中已經(jīng)封裝了的代碼,可以省去很多工作。
實(shí)現(xiàn)基本功能一般只需要重載三個(gè)成員:
TXXXImage = class(TGraphic)
protected
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;//繪制圖像到畫布
public
procedure LoadFromStream(Stream: TStream); override; //從流中獲取圖像數(shù)據(jù)
procedure SaveToStream(Stream: TStream); override; //將圖像數(shù)據(jù)寫入流中
end;
因?yàn)門Graphic.LoadFromFile/TGraphic.SaveToFile中已經(jīng)實(shí)現(xiàn)了由文件名讀取數(shù)據(jù)到流的/將流中的數(shù)據(jù)寫入到對(duì)應(yīng)文件的功能,無(wú)特殊需要這里可以不用重載。而成員Draw自然就是用于實(shí)現(xiàn)將圖像繪制到畫布,由于TCanvas對(duì)GDI的完善封裝,這里不需要考慮如何將圖像利用GDI繪制到窗體的這個(gè)過(guò)程。剩下的就只是編寫圖像解析部分的代碼啦。
下面就以RAS格式為例做進(jìn)一步的探討。
這里沒(méi)有用TGraphic作為基類,而是用了TBitmap,這樣進(jìn)一步把Draw的實(shí)現(xiàn)過(guò)程都省了,只需要在LoadFromStream中實(shí)現(xiàn)轉(zhuǎn)化為位圖的過(guò)程就可以了。
type
TRASGraphic = class(TBitmap)
public
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToStream(Stream: TStream); override;
end;
//定義描述RAS文件頭的記錄類型
TRASHeader = packed record
Magic, //標(biāo)記
Width, //寬
Height, //高
Depth, //色深
Length, //圖像數(shù)據(jù)長(zhǎng)度,可能會(huì)等于0
RasType, //格式類型
MapType, //調(diào)色板類型
MapLength: Cardinal; //調(diào)色板數(shù)據(jù)長(zhǎng)度
end;
//定義一個(gè)用來(lái)描述RAS文件頭的記錄類型是非常必要的
const
//定義代表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;
//定義代表調(diào)色板類型的常量
RMT_NONE = 0;//無(wú)調(diào)色板數(shù)據(jù)
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{如果RAS的格式為RT_OLD,數(shù)據(jù)長(zhǎng)度可能為0}
function SwapLong(const Value: Cardinal): Cardinal;
asm
BSWAP EAX//調(diào)用字節(jié)交換指令
end;
//拋出異常,參數(shù)為具體的異常信息
procedure RasError(const ErrorString: String);
begin
raise EInvalidGraphic.Create(ErrorString);
end;
{下面是實(shí)現(xiàn)部分的代碼。}
procedure TRASGraphic.LoadFromStream(Stream: TStream);
var
Header: TRASHeader;
Row8: PByte;
Row24: PRGBTriple;
Row32: PRGBQuad;
PMap: PByte;
Y: Integer;
I: Integer;
MapReaded: Boolean;
Pal: TMaxLogPalette;
R,G,B:array[0..255] of Byte;
ColorByte: Byte;
begin
with Stream do
begin
ReadBuffer(Header, SizeOf(Header)); //將文件頭數(shù)據(jù)讀取到記錄Header中
with Header do
begin
Width := SwapLong(Width);
Height := SwapLong(Height);
Depth := SwapLong(Depth);
Length := SwapLong(Length);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
MapLength := SwapLong(MapLength);
end;
//由于讀取數(shù)據(jù)的順序問(wèn)題,這里需要調(diào)用上面的SwapLong改變順序。
if (Header.Magic = $956AA659) and
(Header.Width<>0) and (Header.Height<>0) and
(Header.Depth in [1,8,24,32]) and (Header.RasType in [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) then
begin
Width := Header.Width;
Height := Header.Height;
MapReaded := False;
case Header.Depth of
1:PixelFormat := pf1Bit;
8:
begin
PixelFormat := pf8Bit;
case Header.MapType of
RMT_NONE:
begin
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
for I := 0 to 255 do
begin
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
end;
Palette := CreatePalette(PLogPalette(@Pal)^);
//當(dāng)圖像色深為8位,而又不存在調(diào)色板信息時(shí),創(chuàng)建一個(gè)8位的灰度調(diào)色板
end;
RMT_EQUAL_RGB:
begin
if (Header.MapLength = 3*256) then
begin
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
ReadBuffer(R,256);
ReadBuffer(G,256);
ReadBuffer(B,256);
for I := 0 to 255 do
begin
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
end;
Palette := CreatePalette(PLogPalette(@Pal)^);
//讀取文件中的調(diào)色板信息
//相關(guān)調(diào)色板操作的API請(qǐng)查詢MSDN
end
else
RasError('調(diào)色板長(zhǎng)度錯(cuò)誤!');
MapReaded := True;
end;
RMT_RAW:
begin
RasError('不支持的文件格式!');
end;
end;
end;
24:PixelFormat := pf24Bit;
32:
begin
PixelFormat := pf32Bit;
//
end;
end;
if (not MapReaded) and (Header.MapLength>0) then
begin
Position := Position + Header.MapLength;
end;
//如果調(diào)色板長(zhǎng)度不為0,而又未正確讀取相關(guān)信息時(shí),跳過(guò)這一段數(shù)據(jù)
case Header.Depth of
8:
begin
if Header.RasType = RT_BYTE_ENCODED then
begin
//ENCODE
//關(guān)于RLE壓縮的編碼解碼請(qǐng)自行查閱資料
RasError('不支持壓縮格式!');
end
else
begin
for Y := 0 to Height-1 do
begin
Row8:=ScanLine[Y];
ReadBuffer(Row8^,Width);
if (Width mod 2)=1 then
begin
Position := Position + 1;
end;
end;
end;
end;{end of 8Bit}
24:
begin
case Header.RasType of
RT_OLD,
RT_STANDARD:
begin
for Y := 0 to Height-1 do
begin
Row24:=ScanLine[Y];
ReadBuffer(Row24^,Width*3);
if (Width mod 2)=1 then
begin
Position := Position + 1;
end;
end;
end;
RT_BYTE_ENCODED:
begin
//ENCODE
//關(guān)于RLE壓縮的編碼解碼請(qǐng)自行查閱資料
RasError('不支持壓縮格式!');
end;
RT_FORMAT_RGB:
begin
for Y := 0 to Height-1 do
begin
Row24:=ScanLine[Y];
ReadBuffer(Row24^,Width*3);
for I := 0 to Width-1 do
begin
ColorByte := Row24^.rgbtRed;
Row24^.rgbtRed := Row24^.rgbtBlue;
Row24^.rgbtBlue := ColorByte;
Inc(Row24);
end;
//當(dāng)為RT_FORMAT_RGB格式時(shí),按RGB獲取數(shù)據(jù),這里需要交換R和B的值
if (Width mod 2)=1 then
begin
Position := Position + 1;
end;
end;
end;{end of RT_FORMAT_RGB}
else
RasError('不支持的文件格式!');
end;
end;{end of 24Bit}
32:
begin
case Header.RasType of
RT_OLD,
RT_STANDARD:
begin
for Y := 0 to Height-1 do
begin
Row32:=ScanLine[Y];
ReadBuffer(Row32^,Width*4);
for I := 0 to Width-1 do
begin
ColorByte := Row32^.rgbReserved;
Row32^.rgbReserved := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(Row32);
end;
//32位色時(shí),需要調(diào)整讀取后數(shù)據(jù)的順序
end;
end;
RT_BYTE_ENCODED:
begin
//ENCODE
//關(guān)于RLE壓縮的編碼解碼請(qǐng)自行查閱資料
RasError('不支持壓縮格式!');
end;
RT_FORMAT_RGB:
begin
For Y := 0 to Height-1 do
begin
Row32:=ScanLine[Y];
ReadBuffer(Row32^,Width*4);
for I := 0 to Width-1 do
begin
ColorByte := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbReserved;
Row32^.rgbReserved := ColorByte;
ColorByte := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(Row32);
end;
//這里將順序調(diào)整和R和B值的交換的代碼進(jìn)行了合并
end;
end;{end of RT_FORMAT_RGB}
else
RasError('不支持的文件格式!');
end;{end of 32Bit}
end;
else
begin
FreeImage;
RasError('不支持的文件格式!');
end;
end;
end
else
RasError('不支持的文件格式!');
end;{end with}
end;
{上面的代碼中多次出現(xiàn)如下代碼:
if (Width mod 2)=1 then
begin
Position := Position + 1;
end;
這是因?yàn)槊啃械臄?shù)據(jù)都要按字對(duì)齊,既每行的數(shù)據(jù)都要用偶數(shù)的字節(jié)記錄。當(dāng)每個(gè)像素的顏色信息用1字節(jié)(8位)或3字節(jié)(24位)記錄且每行像素?cái)?shù)為奇數(shù)時(shí),要補(bǔ)齊一個(gè)字節(jié)。所以這里跳過(guò)一個(gè)字節(jié)。
后面代碼中的
if (Width mod 2) = 1 then
begin
FillByte:=0;
Stream.Write(FillByte,1);
end;
也是基于同一道理。}
procedure TRASGraphic.SaveToStream(Stream: TStream);
var
Header: TRASHeader;
Row8: PByte;
Row24: PRGBTriple;
Row32: PRGBQuad;
FillByte: Byte;
Y: Integer;
I: Integer;
Pal: TMaxLogPalette;
R,G,B:array[0..255] of Byte;
begin
Header.Magic := $956AA659;
Header.Width := SwapLong(Width);
Header.Height := SwapLong(Height);
Header.RasType := SwapLong(RT_STANDARD);
if (PixelFormat = pf1bit) or (PixelFormat = pf4bit) then
PixelFormat:=pf8bit
else if (PixelFormat <> pf8bit) and (PixelFormat <> pf24bit) and (PixelFormat <> pf32bit) then
PixelFormat:=pf24bit;
case PixelFormat of
pf8bit:
begin
Header.Length := SwapLong(Height*(Width+(Width mod 2)));
Header.Depth := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer(Header,SizeOf(Header));
GetPaletteEntries(Palette, 0, 256, Pal.palPalEntry);
for I := 0 to 255 do
begin
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
end;
//相關(guān)調(diào)色板操作的API請(qǐng)查詢MSDN
Stream.WriteBuffer(R,256);
Stream.WriteBuffer(G,256);
Stream.WriteBuffer(B,256);
for Y := 0 to Height-1 do
begin
Row8 := ScanLine[Y];
Stream.WriteBuffer(Row8^,Width);
if (Width mod 2) = 1 then
begin
FillByte:=0;
Stream.Write(FillByte,1);
end;
end;
end;
pf32bit:
begin
Header.Length := SwapLong(Height*Width*4);
Header.Depth := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Header,SizeOf(Header));
for Y := 0 to Height-1 do
begin
Row32 := ScanLine[Y];
for I := 0 to Width-1 do
begin
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Inc(Row32);
end;
end;
end;
else
begin
Header.Length := SwapLong(Height*Width*3);
Header.Depth := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Header,SizeOf(Header));
for Y := 0 to Height-1 do
begin
Row24 := ScanLine[Y];
Stream.WriteBuffer(Row24^,Width*3);
if (Width mod 2) = 1 then
begin
FillByte:=0;
Stream.Write(FillByte,1);
end;
end;
end;
end;
//SaveToStream基本上就是LoadFromStream的逆過(guò)程。
end;
initialization
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
finalization
TPicture.UnregisterGraphicClass(TRASGraphic);
加上這幾句代碼,一個(gè)完整的圖像解析組件就完成了。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注