delphi中如何编写图像解析组件
delphi作为一个强大的rad开发工具,在应用软件的开发方面一直有着它的独特优势。这种优势同样体现在图像相关软件的开发上。如果你要在桌面上放置一张图像,只需要简单的在桌面上放置一个image控件,然后就可以通过其image属性任意的加载bmp、wmf、emf等格式的图像。如果还想增加对jpeg的支持,只需要添加一个jpeg单元即可。甚至在image中加载一张jpeg后,delphi会自动添加一个jpeg单元。一切做起来就是这么的简单。基本格式都已经封装在了vcl中,那么delphi对类似jpeg这样图像格式的支持是如何实现的呢?
其实从tpicture中很容易看出其中的实现过程,它可以理解为所有图像对象的容器。
如jpeg.pas中有如下两句代码:
tpicture.registerfileformat('jpeg', sjpegimagefile, tjpegimage);
tpicture.registerfileformat('jpg', sjpegimagefile, tjpegimage);
(sjpegimagefile = 'jpeg image file',见jconsts.pas)
什么意思呢?可以理解为将tjpegimage注册为jpeg、jpg两种后缀图像文件的类。
其实质就是将后缀,图像描述,具体图像解析类等信息保存到了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默认支持四种图像格式是因为tfileformatslist的构造函数中已进行了添加。
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;
也正是通过fileformats中保存的信息,控件openpicturedialog中自动生成了所支持文件类型的列表。
那么该如何编写这些图像解析类呢?
tgraphic是tbitmap、ticon、tmetafile对象的基类。同样这里的图像解析类也应该从tgraphic派生,利用很多vcl中已经封装了的代码,可以省去很多工作。
实现基本功能一般只需要重载三个成员:
txxximage = class(tgraphic)
protected
procedure draw(acanvas: tcanvas; const rect: trect); override;//绘制图像到画布
public
procedure loadfromstream(stream: tstream); override; //从流中获取图像数据
procedure savetostream(stream: tstream); override; //将图像数据写入流中
end;
因为tgraphic.loadfromfile/tgraphic.savetofile中已经实现了由文件名读取数据到流的/将流中的数据写入到对应文件的功能,无特殊需要这里可以不用重载。而成员draw自然就是用于实现将图像绘制到画布,由于tcanvas对gdi的完善封装,这里不需要考虑如何将图像利用gdi绘制到窗体的这个过程。剩下的就只是编写图像解析部分的代码啦。
下面就以ras格式为例做进一步的探讨。
这里没有用tgraphic作为基类,而是用了tbitmap,这样进一步把draw的实现过程都省了,只需要在loadfromstream中实现转化为位图的过程就可以了。
type
trasgraphic = class(tbitmap)
public
procedure loadfromstream(stream: tstream); override;
procedure savetostream(stream: tstream); override;
end;
//定义描述ras文件头的记录类型
trasheader = packed record
magic, //标记
width, //宽
height, //高
depth, //色深
length, //图像数据长度,可能会等于0
rastype, //格式类型
maptype, //调色板类型
maplength: cardinal; //调色板数据长度
end;
//定义一个用来描述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;
//定义代表调色板类型的常量
rmt_none = 0;//无调色板数据
rmt_equal_rgb = 1;
rmt_raw = 2;
{如果ras的格式为rt_old,数据长度可能为0}
function swaplong(const value: cardinal): cardinal;
asm
bswap eax//调用字节交换指令
end;
//抛出异常,参数为具体的异常信息
procedure raserror(const errorstring: string);
begin
raise einvalidgraphic.create(errorstring);
end;
{下面是实现部分的代码。}
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)); //将文件头数据读取到记录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;