DBImage—思路一(The DBImage - take one)
当试图使用Delphi做新的尝试时,我所做的第一件事是向Delphi的自带帮助寻求方法。这是帮助文档将回答:TDBImage(在组件面板的Data Controls页)表示数据库当前记录的一个BLOB字段的图形图像。使用TDBImage表示图形字段值。TDBImage允许表单显示数据库的图形数据。TDBImage仅仅比TImage组件多了一些数据可视属性。其中两个最重要的属性是:DataSource(数据源)和Field(字段)。DataSource(数据源)属性连接图形组件到数据库。在我们的表单上有一个名为DataSource1的DataSource(数据源)组件—代表着一个数据集。Field(字段)属性指出拥有图像的字段(在表中)。
一切都清楚了,现在在表单上放置一个DBImage组件并默认名为DBImage1。为了真正的把DBImage与表的BLOB字段相连,我们仅需要做以下的配置(使用Object Inspector):
DBImage1.DataSource = DataSource1
DBImage1.Field = Picture
为了显示存在Applications表的Picture字段的JPEG图像,这是必需的窍门。
为了验证这样的配置是否可以工作,我们所需做的唯一一件事是设置ADOTable1组件的Active(激活)属性为True即可。在设计时我们就可在Object Inspector(对象检视器)中完成。一旦你这样做了,就会出现以下的对话框:
什么?为什么显示“位图图像无效”呢?我们有JPEG图片而不是BMP图片—问题就在这里吗?让我们再回头看看帮助。
通过在帮助中的一阵点击之后,得出结论:为了得到数据库里的JPG图片,我们得使用TJpegImage对象。为了显示图片,我们需要Image(图像)组件的简单、不可视版本。同时,我们需要用流(Stream)从BLOB对象中载出图片。帮助文档叙述:我们应使用TADOBlobStream来访问或改变ADO数据集中BLOB或memo(备注)字段的值。
第三节 用流引出JPEG—错误的方法
引出JPEG—思路二(Pulling the Jpeg - take two!)
既然我们不能使用DBImage做任何事—从表单中去掉它并放上一个普通的TImage组件(Additional页)命名为ADOImage。不幸的是,Image组件没有任何数据可视(data-aware)的属性,因此,需要一个单独的程序来显示它所表示的数据库表中的图片。完成这件事的最简单的方法是:在表单上放置一个Button(按钮),把所有的程序代码放在它的OnClick事件中,按钮的名称为:“btnShowImage”。
为了使用ADOBLOBStream,帮助文档建议创建一个TADOBlobStream实例,用“流”的方法从数据集中读取图形字段,然后释放BLOB流。在中间的某个地方,我们将需要用LoadFromStream方法从TADOBlobStream对象中载入JPEG图像。Image(图像)组件的Picture(图片)、Graphic(图形)属性将用于真正的存储和显示图片。
字段对象,它是什么?
这时,我假设只需要一点点关于字段对象的知识对于你掌握本章已绰绰有余了。在Delphi数据库的开发中,主要的对象之一是TField对象。字段组件是表示运行(或设计)时的数据集字段的非可视化对象。TADOTable(和其他TDataSet子类)提供设计时对Fields Editor(字段编辑器)的访问方法。Fields Editor使你能选择数据集中你所想包含的字段。更重要的是,它创建了应用程序数据集中使用的字段组件的稳固的列表。为了调用Fields Editor,可以双击TADOTable组件。默认情况下,字段列表是空的。点击Add按钮打开一个对话框,里面列出了Applications表的字段列表。缺省情况下,所有字段都被选择,然后选择OK。
Delphi会按如下的方式给出字段的默认名称:Table(表)名+Field(字段)名。这意味着我们的图片字段名为:ADOTable1Picture。
TADOBlobStream的Create(创建)方法创建一个实例用于读或写一个指定的BLOB字段对象,在这里是ADOTable1Picture字段。
我们在btnShowImage按钮的OnClick事件中写入程序代码。该代码将从当前所选行的Picture字段中读取图片。源代码如下所示:
uses jpeg;
...
procedure TForm1.btnShowImageClick(Sender: TObject);
var bS: TADOBlobStream;
Pic : TJpegImage;
begin
bS := TADOBlobStream.Create
(AdoTable1Picture, bmRead);
try
Pic:=TJpegImage.Create;
try
Pic.LoadFromStream(bS);
ADOImage.Picture.Graphic:=Pic;
finally
Pic.Free;
end;
finally
bS.Free
end;
end;
OK,让我们运行这个工程。当然,设置ADOTable1.Active属性为True。表单显示后,点击按钮,将出现下面的显示:
呃, 怎么哪?代码百分之百的正确但为什么不显示图像呢!记住“永不放弃,永不投降”!让我们深入到字节水平看看到底发生了什么!
第四节 在BLOB中寻找JPEG的开端
OLE对象类型格式—思路三(OLE object type format - take three!)
现在所有我们需要做的是存储图片到磁盘(存为普通的二进制文件)并了解它里门的内容是什么。
所有的图片文件(格式)都有用来唯一的标识图像的文件头。JPG图片文件以所谓的SOI标记开始,该标记的十六进制值是$FFD8。
下面一行代码存储图片字段值到工作目录的相关文件(BlobImage.dat)。在表单的OnCreate事件中放置这条代码,开始工程以后再移除该代码。
ADOTable1Picture.SaveToFile("BlobImage.dat"
![Wink ;) ;)](https://cdn.jsdelivr.net/joypixels/assets/8.0/png/unicode/64/1f609.png)
;
一旦我们有了这个文件。我们就可以使用Hex editor看它的内容。
你相信吗?MS Access把连接的OLE对象的路径作为对象定义的一部分存储在OLE对象字段中。因为OLE对象的存储定义没有被文档化(!?这直接来自于MS),所以没有办法知道真正的图像数据被写之前能得到什么。
分两个部分考虑这个问题。第一:我们需要找到"FFD8"并从那儿开始读取图像。第二:"FFD8"不可能总在文件的同一个位置。结论:我们需要一个函数,返回Access数据库中存储为OLE对象的JPG文件的SOI标记的位置。
正确的方法—思路四(The correct way - take four!)
提供了Blob类型字段后,我们的函数应返回ADOBlobStream中"FFD8"字符串的位置。ReadBuffer(读缓冲区)从流中一个字节一个字节的读取数据。对ReadBuffer的每个调用都会一个字节一个字节的移动流的位置。当两个字节一起引出SOI标记时,函数返回流的位置。这是这个函数:
function JpegStartsInBlob(PicField:TBlobField):integer; var bS : TADOBlobStream; buffer : Word; hx : string; begin Result := -1; bS := TADOBlobStream.Create(PicField, bmRead); try while (Result = -1) and (bS.Position + 1 < bS.Size) do begin bS.ReadBuffer(buffer, 1); hx:=IntToHex(buffer, 2); if hx = "FF" then begin bS.ReadBuffer(buffer, 1); hx:=IntToHex(buffer, 2); if hx = "D8" then Result := bS.Position - 2 else if hx = "FF" then bS.Position := bS.Position-1; end; //if end; //while finally bS.Free end; //try end; 一旦我们有了SOI标记的位置,我们就能使用它在ADOBlob流中找到图片的位置。 uses jpeg; ... procedure TForm1.btnShowImageClick(Sender: TObject); var bS : TADOBlobStream; Pic : TJpegImage; begin bS := TADOBlobStream.Create(AdoTable1Picture, bmRead); try bS.Seek(JpegStartsInBlob(AdoTable1Picture),soFromBeginning); Pic:=TJpegImage.Create; try Pic.LoadFromStream(bS); ADOImage.Picture.Graphic:=Pic; finally Pic.Free; end; finally bS.Free end; end; 运行工程,OK! 现在谁会说编程没有趣味? 注:在真正的代码程序中,我们会在TDataSet的AfterScroll事件中加入代码用于从当前行中读取和显示图像(它在ADOTable1AfterScroll事件过程中)。当应用程序从一个记录滚到另一个时,AfterScroll事件发生。