《Delphi图像处理 -- 数据类型及内部过程》一文中定义了基本的图像数据类型及一些内部过程,本文进一步将Delphi常用的图像类型转换为图像处理所需的数据结构,为具体的Delphi图像处理过程作准备,同时也要将处理好的图像数据转换为Delphi的常用图像类型。《Delphi图像处理》系列除图像数据转换过程外,其它图像处理过程都统一使用32位ARGB像素格式。
一、数据格式转换。
本节提供了2个互逆的图像数据拷贝过程。ImageCopyFrom是把1 - 32位像素格式的图像数据源结构拷贝到32位像素格式的图像数据目标结构中,而ImageCopyTo则是把32位像素格式的图像数据源结构拷贝到24 - 32位像素格式的图像数据目标结构,转换为低于24位像素格式的图像数据结构应借助TIndexImage类(见《Delphi图像处理 -- 真彩色图像转换为低色彩图像》)。
过程中Dest和Source参数的获取方法见《Delphi图像处理 -- 数据类型及内部过程》和本文第六节的内容;如何使用这2个过程可参见本文的第二节和第四节的内容。
下面以图例的方式简单介绍一下几种像素格式的内存结构,供对图像处理感兴趣,而又对各种像素格式的结构不熟悉的朋友参考:
1、单色图像。每字节8像素,从高位到低位依次排列,与字节位的顺序相反:
高位 低位
字 节 位: 7 6 5 4 3 2 1 0
像素排列: 0 1 2 3 4 5 6 7
2、16色图像。每字节2像素,高四位为第一像素,低四位为第二像素:
高位 低位
字 节 位: 7 6 5 4 3 2 1 0
像素排列: 第1像素 第2像素
3、256色图像。每字节1像素,这个简单,不必图示。
4、15位图像。2字节1像素,R、G、B各分量都占5个位,俗称555格式:
高位 低位
字 节 位: F E D C B A 9 8 7 6 5 4 3 2 1 0
像素排列: 0 r r r r r g g g g g b b b b b
5、16位图像。2字节1像素,R、B分量各占5个位,G分量占6个位,俗称565格式:
高位 低位
字 节 位: F E D C B A 9 8 7 6 5 4 3 2 1 0
像素排列: r r r r r g g g g g g b b b b b
6、24位图像。3字节1像素,从高到低,R、G、B各占1字节,图示略。
7、32位像素。4字节1像素,从高到低,A、R、G、B各占1字节,图示略。
8、无论何种像素格式,其扫描线长度必须是32位(4字节)的整倍数,如果按图像宽度计算出来的扫描线字节不足32位整倍数,必须以0填充。所以,除32位图像像素格式外,其它像素格式都有可能扫描线宽度不等于图像宽度 * 位数 / 8。
二、获取TGraphic的图像数据。
TGraphic是Delphi的图像类基类,特别是其派生类TBitmap,更是Delphi最重要,也是最基础的图像类,它封装了Windows位图的常用操作,其它TGraphic派生类(包括一些第三方派生类)都可以转换为TBitmap,因此,获取了TBitmap的图像数据,也就获取了TGraphic派生类的图像数据。
有多个方法获取TBitmap的图像数据,一是直接在TBitmap的扫描线上操作,其好处是图像处理后直接反映在TBitmap中,不必再进行转换,缺点是由于本图像处理系列采用了统一的32位ARGB像素格式,所以必须设置TBitmap.PixelFormat属性,如此一来,势必破坏了原图像像素格式,而且除原32位图像外,实际上也进行了一次图像数据拷贝;二是不破坏TBitmap的像素格式,直接在TBitmap.ScanLine中解析拷贝图像数据,这是间接的TBitmap图像数据处理,处理完毕,有可能要转换为新的TBitmap;三是,用TBitmap.Handle属性借助Windows API进行数据转换,这种方式与第二种方式差不多,《Delphi图像处理 -- 真彩色图像转换为低色彩图像》一文中就是采用的这种方式。本文采用前2种方式获取TBitmap图像数据,具体采用何种方式由具体情况而定。下面是实现代码:
从上面的代码可以看出,采用第一种方式最简单:先设置其PixelFormat := pf32Bit,然后设定数据结构即可(非32位像素格式图像转换为32位像素格式后必须填充其Alpha分量为255,填充由内部过程FillAlpha完成);第二种方式则很复杂,必须针对pf1Bit到pf32Bit的内存像素解析后进行拷贝,拷贝过程使用了本文第一节介绍的ImageCopyFrom过程。
三、获取GDI+ TGpBitmap的图像数据。
GDI+也是近些年来在Delphi中使用较多的图像类,同TBitmap一样,也有几种方式获取图像数据,获取TGpBitmap图像数据很简单,使用TGpBitmap.Lockbits函数就可搞定。
GetImageData(Bitmap: TGpBitmap; FreeBitmap: Boolean)是间接获取TGpBitmap图像数据的函数,这里面并没有调用任何拷贝过程,而是使用了TGpBitmap.LockBits的一点小技巧,其原理可参见《使用GDI+位图数据扫描线处理图像的小技巧》。如果你看了《使用GDI+位图数据扫描线处理图像的小技巧》。有的的朋友可能发现,那篇文章是用C++写的,而C++中的Bitmap.Lockbits函数中,BitmapData类型的Data是作为一个指针参数传递给函数的,所以预先设定Stride和Scan0是没话可说的,而Delphi中,TBitmapData类型的Data是通过返回值获取而不是通过参数传递的,那么预先设置的Stride和Scan0有何意义呢?呵呵,其实,这就是Delphi默认调用方式的特殊之处了。类似结构之类的数据类型获取返回值,在C/C++,是一个个字段拷贝的,预先设定的值铁定会被返回值覆盖;而Delphi对于这种大于4字节结构类型的返回值却是在调用时以指针方式作为隐含的一个参数传递给函数的,因而,在函数内部对返回值的操作也是针对该指针的,所以Delphi与C++的LockBits函数外形上不一样,实质上都是传递了一个返回值的指针参数。
四、TImageData转换为TGraphic。
图像数据转换为到32位或者24位像素格式时使用了本文第一节介绍的ImageCopyTo过程,其它像素格式的转换借助了TIndexImage类型,该类型在文章《Delphi图像处理 -- 真彩色图像转换为低色彩图像》中。其实,按照常规写法,ImageDataAssignTo过程没有这样复杂,关键是新建的TBitmap对象,只有按照先设定PixelFormat,再设置调色板,最后设置图像尺寸才是最优顺序,否则,不仅在TBitmap内部要多一次数据拷贝,而且图像数据还会被搞乱。
五、TImageData转换为TGpBitmap。
这个24位以下像素格式也要借助TGpIndexImage类型,代码如下:
六、其它。包括新建TImageData对象,获取子图,获取裁剪图等过程(代码较长,已折叠)。
对一个图像的局部数据进行处理,可以借助GetClipImageData或者GetSubImageData过程进行,前者是从图像数据上裁剪一个局部,对它的操作不会影响原图,而后者只是原图数据的一部分,对它的数据处理,就是对原图的局部图像处理。
GetExpandData是内部过程,功能是扩展图像边框,以便正确地处理图像边缘。在很多图像处理过程中,都涉及到图像边缘的处理,如图像缩放、旋转、卷积等操作,这是一个即耗时又麻烦的事,GetExpandData就是对图像预先进行边界扩展,这样在具体处理过程中就不必要作边界判断了。
七、过程调用过程。
本次重新修订,针对有些图像处理过程比较耗时和需要经常性进行调整的问题,对这些图像处理增加了回调处理过程,用于在图像调整时,及时终止前面的图像处理过程,图像处理过程中响应用户回调函数是个较麻烦的事,为此写了2个内部处理过程,存放于此作为本文第七部分(代码就不解释了):
八、内部使用参数表及初始化过程(这些过程是在单元最后部分调用的,因此原样复制于此):
关于图像数据的转换就介绍到这里。
文章中使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。
文章中所用数据类型及一些内部过程见《Delphi图像处理 -- 数据类型及内部过程》。
尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
补充:本文于2010.5.20重新修订