《Delphi图像处理 -- 数据类型及内部过程》一文中定义了基本的图像数据类型及一些内部过程,本文进一步将Delphi常用的图像类型转换为图像处理所需的数据结构,为具体的Delphi图像处理过程作准备,同时也要将处理好的图像数据转换为Delphi的常用图像类型。《Delphi图像处理》系列除图像数据转换过程外,其它图像处理过程都统一使用32位ARGB像素格式。
一、数据格式转换。
过程定义: // 拷贝Source到32位Dest。如果是8位或者4位Source,必须要有相应的颜色表Colors procedure ImageCopyFrom(var Dest: TImageData; const Source: TImageData; Colors: PRGBQuad = nil); // 拷贝32位Source到32 or 24位Dest。 procedure ImageCopyTo(var Dest: TImageData; const Source: TImageData); 代码实现: procedure ImageCopyFrom(var Dest: TImageData; const Source: TImageData; Colors: PRGBQuad); procedure SetBit1Pixel; asm mov bl, [esi] mov bh, 80h @@pixelLoop1: test bl, bh jz @@pb1 mov eax, 0ffffffffh jmp @@pb2 @@pb1: mov eax, 0ff000000h @@pb2: stosd shr bh, 1 loop @@pixelLoop1 inc esi end; var pixelBits, dstOffset, srcOffset, tmp: Integer; begin if ImageEmpty(Dest) then raise EImageDataError.CreateFmt(EIDErrorDestMust, [32]); if (Source.Scan0 = nil) or (Source.Width <= 0) or (Source.Height <= 0) then raise EImageDataError.Create(EIDErrorNotSupport); tmp := Source.PixelFormat; PixelBits := (tmp shr 8) and $ff; if ((PixelBits = 4) or (PixelBits = 8)) and (Colors = nil) then raise EImageDataError.CreateFmt(EIDErrorColorTable, [PixelBits]); asm push esi push edi push ebx mov eax, Dest mov edx, Source call SetCopyRegs mov dstOffset, ebx mov srcOffset, eax mov eax, pixelBits cmp eax, 32 je @@cpyBit32 cmp eax, 24 je @@cpyBit24 cmp eax, 16 je @@cpyBit16 cmp eax, 8 je @@cpyBit8 cmp eax, 4 je @@cpyBit4 // cmp eax, 1 // je @@cpyBit1 @@cpyBit1: mov eax, 7 and eax, ecx mov tmp, eax shr ecx, 3 @@yLoop1: push ecx jecxz @@1_1 @@xLoop1: push ecx mov ecx, 8 call SetBit1Pixel pop ecx loop @@xLoop1 @@1_1: mov ecx, tmp jecxz @@1_2 call SetBit1Pixel @@1_2: pop ecx add esi, srcOffset add edi, dstOffset dec edx jnz @@yLoop1 jmp @@subReturn @@cpyBit4: mov tmp, ecx shr ecx, 1 mov ebx, colors @@yLoop4: push ecx @@xLoop4: dec ecx js @@4_1 movzx eax, byte ptr [esi] push eax shr eax, 4 mov eax, [ebx+eax*4] or eax, 0ff000000h stosd pop eax and eax, 0fh mov eax, [ebx+eax*4] or eax, 0ff000000h stosd inc esi jmp @@xLoop4 @@4_1: test tmp, 1 jz @@4_2 movzx eax, byte ptr [esi] shr eax, 4 mov eax, [ebx + eax * 4] or eax, 0ff000000h stosd inc esi @@4_2: pop ecx add esi, srcOffset add edi, dstOffset dec edx jnz @@yLoop4 jmp @@subReturn @@cpyBit8: mov ebx, colors @@yLoop8: push ecx @@xLoop8: movzx eax, byte ptr [esi] mov eax, [ebx+eax*4] or eax, 0ff000000h stosd inc esi loop @@xLoop8 pop ecx add esi, srcOffset add edi, dstOffset dec edx jnz @@yLoop8 jmp @@subReturn @@cpyBit16: mov ebx, ecx mov ecx, 0fc06h // 565 cmp byte ptr tmp, 5 jne @@yLoop16 mov ecx, 0f805h // 555 @@yLoop16: push ebx @@xLoop16: lodsw shl eax, 3 stosb shr eax, cl and al, ch stosb shr eax, 5 and al, 0f8h or ax, 0ff00h stosw dec ebx jnz @@xLoop16 pop ebx add esi, srcOffset add edi, dstOffset dec edx jnz @@yLoop16 jmp @@subReturn @@cpyBit24: mov eax, 0ffh @@yLoop24: push ecx @@xLoop24: movsw movsb stosb loop @@xLoop24 pop ecx add esi, srcOffset add edi, ebx dec edx jnz @@yLoop24 jmp @@subReturn @@cpyBit32: @@yLoop32: mov eax, srcOffset push ecx rep movsd pop ecx add esi, eax add edi, ebx dec edx jnz @@yLoop32 @@subReturn: pop ebx pop edi pop esi end; end; procedure ImageCopyTo(var Dest: TImageData; const Source: TImageData); var PixelBits: Integer; begin if ImageEmpty(Source) then raise EImageDataError.CreateFmt(EIDErrorSourceMust, [32]); PixelBits := (Dest.PixelFormat shr 8) and $ff; if (Dest.Scan0 = nil) or (Dest.Width <= 0) or (Dest.Height <= 0) or (PixelBits < 24) then raise EImageDataError.Create(EIDErrorNotSupport); asm push esi push edi push ebx mov eax, Dest mov edx, Source call SetCopyRegs cmp PixelBits, 24 je @@cpyBit24 @@yLoop32: push ecx rep movsd pop ecx add esi, eax add edi, ebx dec edx jnz @@yLoop32 jmp @@Exit @@cpyBit24: @yLoop24: push ecx @xLoop24: movsw movsb inc esi loop @xLoop24 pop ecx add esi, eax add edi, ebx dec edx jnz @yLoop24 @@Exit: pop ebx pop edi pop esi end; end;
本节提供了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图像数据,具体采用何种方式由具体情况而定。下面是实现代码:
过程定义: // 获取Graphic的图像数据。必须用FreeImageData释放数据结构 function GetImageData(Graphic: TGraphic): TImageData; overload; // 获取Bitmap的图像数据。如果isBinding=False,复制Bitmap数据, // 用FreeImageData释放数据结构,否则,Bitmap转换为32位后直接操作扫描线 function GetImageData(Bitmap: TBitmap; isBinding: Boolean): TImageData; overload; 代码实现: procedure FillAlpha(Data: TImageData); asm mov edx, [eax].TImageData.Scan0 mov ecx, [eax].TImageData.Width imul ecx, [eax].TImageData.Height mov eax, 0ff000000h @PixelLoop: or [edx], eax add edx, 4 loop @PixelLoop end; function GetImageData(Graphic: TGraphic): TImageData; var tmp: TBitmap; Data: TImageData; Pal: HPalette; Colors: array[Byte] of TRGBQuad; Count: Integer; begin if Graphic is TBitmap then tmp := Graphic as TBitmap else begin tmp := TBitmap.Create; tmp.Assign(Graphic); end; try if (tmp.PixelFormat = pf8bit) or (tmp.PixelFormat = pf4bit) then begin Pal := tmp.Palette; Count := GetPaletteEntries(Pal, 0, 256, Colors); DeleteObject(Pal); SwapColors(@Colors, Count); end; Data := GetBitmapData(tmp); Result := GetImageData(Data.Width, Data.Height); ImageCopyFrom(Result, Data, @Colors); finally if tmp <> Graphic then tmp.Free; end; end; function GetImageData(Bitmap: TBitmap; isBinding: Boolean): TImageData; var IsFill: Boolean; begin if not isBinding then Result := GetImageData(Bitmap) else begin IsFill := Bitmap.PixelFormat <> pf32Bit; Bitmap.PixelFormat := pf32Bit; Result := GetImageData(Bitmap.Width, Bitmap.Height, 0, Bitmap.ScanLine[Bitmap.Height - 1], pf32bit, True); if IsFill then FillAlpha(Result); end; end;
从上面的代码可以看出,采用第一种方式最简单:先设置其PixelFormat := pf32Bit,然后设定数据结构即可(非32位像素格式图像转换为32位像素格式后必须填充其Alpha分量为255,填充由内部过程FillAlpha完成);第二种方式则很复杂,必须针对pf1Bit到pf32Bit的内存像素解析后进行拷贝,拷贝过程使用了本文第一节介绍的ImageCopyFrom过程。
三、获取GDI+ TGpBitmap的图像数据。
GDI+也是近些年来在Delphi中使用较多的图像类,同TBitmap一样,也有几种方式获取图像数据,获取TGpBitmap图像数据很简单,使用TGpBitmap.Lockbits函数就可搞定。
过程说明: // 锁定Bitmap并返回图像数据结构,直接操作Bitmap扫描线,必须用GpBitmapUnLockData解锁 function GpBitmapLockData(Bitmap: TGpBitmap): TImageData; // 对锁定Bitmap的图像数据结构Data解锁 procedure GpBitmapUnLockData(Bitmap: TGpBitmap; var Data: TImageData); // 获取Bitmap图像数据结构,如果FreeBitmap=True,释放Bitmap。 // 必须用FreeImageData释放返回值 function GetImageData(Bitmap: TGpBitmap; FreeBitmap: Boolean = False): TImageData; overload; 代码实现: function GetImageData(Bitmap: TGpBitmap; FreeBitmap: Boolean): TImageData; var Data: TBitmapData; begin Result := GetImageData(Bitmap.Width, Bitmap.Height); Data.Stride := Result.Stride; Data.Scan0 := Result.Scan0; Data := Bitmap.LockBits(GpRect(0, 0, Result.Width, Result.Height), [imRead, imUserInputBuf], pf32bppARGB); Bitmap.UnlockBits(Data); if FreeBitmap then Bitmap.Free; end; function GetLockData(Bitmap: TGpBitmap; Format: Gdiplus.TPixelFormat): TImageData; begin if Format = pfNone then Format := Bitmap.PixelFormat; if Format > pf32bppARGB then Format := pf32bppARGB; TBitmapData(Result) := Bitmap.LockBits(GpRect(0, 0, Bitmap.Width, Bitmap.Height), [imRead, imWrite], Format); end; function GpBitmapLockData(Bitmap: TGpBitmap): TImageData; begin Result := GetLockData(Bitmap, pf32bppARGB); end; procedure GpBitmapUnLockData(Bitmap: TGpBitmap; var Data: TImageData); var LockMode: TImageLockModes; begin LockMode := [imRead, imWrite]; if Data.LockMode = Byte(LockMode) then begin Bitmap.UnlockBits(TBitmapData(Data)); Data.Scan0 := nil; Data.Reserved := 0; end; end;
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。
过程定义: // 图像数据转换为TBitmap。ImitationColor转换为16位以下时是否仿色 // 如果图像有Alpha信息同时背景色ColorBackground<>0,填充背景 function ImageDataToBitmap(Data: TImageData; PixelFormat: TPixelFormat = pf24Bit; ImitationColor: Boolean = False; ColorBackground: TColor = 0): TBitmap; // 图像数据拷贝到TGraphic。其余参数同上 procedure ImageDataAssignTo(Data: TImageData; Graphic: TGraphic; ImitationColor: Boolean = False; ColorBackground: TColor = 0); overload; 代码实现: type TIndexData = class(TIndexImage) end; procedure ImageDataAssignTo(Data: TImageData; Graphic: TGraphic; ImitationColor: Boolean; ColorBackground: TColor); var tmp: TBitmap; src, dst: TImageData; IdxData: TIndexData; Grays: TGrayStatData; begin if ImageEmpty(Data) or not Assigned(Graphic) then raise EImageDataError.Create(EIDErrorEmptyData); if Graphic is TBitmap then tmp := Graphic as TBitmap else begin tmp := TBitmap.Create; tmp.Assign(Graphic); end; try if not (tmp.PixelFormat in [pf1Bit..pf16Bit]) then begin if tmp.Empty then begin tmp.Width := Data.Width; tmp.Height := Data.Height; end; dst := GetBitmapData(tmp); ImageCopyTo(dst, Data); end else begin IdxData := TIndexData.Create; try if ColorBackground <> 0 then begin src := GetImageData(Data.Width, Data.Height); ImageCopyTo(src, Data); end else begin src := Data; src.AllocScan := False; end; IdxData.SourceData := src; if ColorBackground <> 0 then begin SwapColors(@ColorBackground, 1); IdxData.FillBackground(ColorBackground); end; IdxData.IndexFormat := TIndexFormat(Integer(tmp.PixelFormat) - 1); IdxData.Dithering := ImitationColor; if IdxData.IndexFormat = if1Bit then begin ImageGrayStat(src, Grays); IdxData.MonochromeThreshold := Grays.Average; end; if IdxData.IndexFormat <= if8Bit then tmp.Palette := IdxData.Palette; if tmp.Empty then begin tmp.Width := Data.Width; tmp.Height := Data.Height; end; dst := GetBitmapData(tmp); IdxData.CreateIndexImageData(dst); finally IdxData.Free; end; end; if not (Graphic is TBitmap) then Graphic.Assign(tmp); finally if not (Graphic is TBitmap) then tmp.Free; end; end; function ImageDataToBitmap(Data: TImageData; PixelFormat: TPixelFormat; ImitationColor: Boolean; ColorBackground: TColor): TBitmap; begin Result := TBitmap.Create; Result.PixelFormat := PixelFormat; ImageDataAssignTo(Data, Result, ImitationColor, ColorBackground); end;
图像数据转换为到32位或者24位像素格式时使用了本文第一节介绍的ImageCopyTo过程,其它像素格式的转换借助了TIndexImage类型,该类型在文章《Delphi图像处理 -- 真彩色图像转换为低色彩图像》中。其实,按照常规写法,ImageDataAssignTo过程没有这样复杂,关键是新建的TBitmap对象,只有按照先设定PixelFormat,再设置调色板,最后设置图像尺寸才是最优顺序,否则,不仅在TBitmap内部要多一次数据拷贝,而且图像数据还会被搞乱。
五、TImageData转换为TGpBitmap。
这个24位以下像素格式也要借助TGpIndexImage类型,代码如下:
过程定义: // 图像数据转换Bitmap。ImitationColor转换为16位以下时是否仿色, // 如果图像有Alpha信息同时背景色ColorBackground<>0,填充背景 function ImageDataToGpBitmap(Data: TImageData; PixelFormat: TPixelFormat = pf24bppRGB; ImitationColor: Boolean = False; ColorBackground: TARGB = 0): TGpBitmap; // 图像数据拷贝到Bitmap。其余参数同上 procedure ImageDataAssignTo(Data: TImageData; Bitmap: TGpBitmap; ImitationColor: Boolean; ColorBackground: TARGB = 0); overload; 实现代码: type TIndexData = class(TGpIndexBitmap) end; procedure ImageDataAssignTo(Data: TImageData; Bitmap: TGpBitmap; ImitationColor: Boolean; ColorBackground: TARGB); var src, dst: TImageData; IdxData: TIndexData; Grays: TGrayStatData; Bits: Integer; begin if ImageEmpty(Data) or (Bitmap = nil) then raise EImageDataError.Create(EIDErrorEmptyData); dst := GetLockData(Bitmap, pfNone); try Bits := (dst.PixelFormat shr 8) and $ff; if Bits < 24 then begin IdxData := TIndexData.Create; try if ColorBackground <> 0 then begin src := GetImageData(dst.Width, dst.Height); ImageCopyTo(src, Data); end else begin src := Data; src.AllocScan := False; end; IdxData.SourceData := src; if ColorBackground <> 0 then IdxData.FillBackground(ColorBackground); IdxData.IndexFormat := TIndexFormat(Bits shr 2); IdxData.Dithering := ImitationColor; if IdxData.IndexFormat = if1Bit then begin ImageGrayStat(src, Grays); IdxData.MonochromeThreshold := Grays.Average; end; if IdxData.IndexFormat <= if8Bit then Bitmap.Palette := IdxData.GetPalette; IdxData.CreateIndexImageData(dst); finally IdxData.Free; end end else ImageCopyTo(dst, Data); finally GpBitmapUnLockData(Bitmap, dst); end; end; function ImageDataToGpBitmap(Data: TImageData; PixelFormat: Gdiplus.TPixelFormat; ImitationColor: Boolean; ColorBackground: TARGB): TGpBitmap; begin if ImageEmpty(Data) then raise EImageDataError.Create(EIDErrorEmptyData); Result := TGpBitmap.Create(Data.Width, Data.Height, PixelFormat); ImageDataAssignTo(Data, Result, ImitationColor, ColorBackground); end;
六、其它。包括新建TImageData对象,获取子图,获取裁剪图等过程(代码较长,已折叠)。
过程定义: // 建立新的图像数据结构。ColorBackground为ARGB颜色,必须用FreeImageData释放 function NewImageData(Width, Height: Integer; ColorBackground: LongWord): TImageData; // 获取Data的裁剪图像数据结构。必须用FreeImageData释放,如果裁剪区域超范围,图像数据为空 function GetClipImageData(Data: TImageData; x, y, Width, Height: Integer): TImageData; // 获取Data的子图像数据结构,用于对Data的局部操作。如果子图区域超范围,图像数据为空 function GetSubImageData(const Data: TImageData; x, y, Width, Height: Integer): TImageData; // 如果Data分配了扫描线内存,释放扫描线内存 procedure FreeImageData(var Data: TImageData); // 判断图像数据结构是否为空 function ImageEmpty(Data: TImageData): Boolean; // 用Color清除图像数据,Color为ARGB颜色 procedure ImageClear(Data: TImageData; Color: LongWord = 0); 实现代码: // 功能同ImageEmpty,供BASM过程使用 procedure IsValid32(const Data: TImageData); asm cmp [eax].TImageData.Scan0, 0 je @@1 test [eax].TImageData.PixelFormat, 2000h jz @@1 cmp [eax].TImageData.Width, 0 jle @@1 cmp [eax].TImageData.Height, 0 jle @@1 clc ret @@1: stc end; function ImageEmpty(const Data: TImageData): Boolean; begin Result := (Data.Scan0 = nil) or ((Data.PixelFormat and $2000) = 0) or (Data.Width <= 0) or (Data.Height <= 0); end; procedure SetDataRegs32(const Data: TImageData); asm mov edi, [eax].TImageData.Scan0 mov ecx, [eax].TImageData.Width mov ebx, [eax].TImageData.Stride mov edx, ecx shl edx, 2 sub ebx, edx mov edx, [eax].TImageData.Height end; procedure SetScaleRegs32(const Dest, Source: TImageData); asm mov ecx, [edx].TImageData.Width shl ecx, 2 mov ebx, [edx].TImageData.Stride sub ebx, ecx push ebx // eax = srcOffset = source.Stride - width * 4 mov ecx, [eax].TImageData.Width shl ecx, 2 mov ebx, [eax].TImageData.Stride sub ebx, ecx // ebx = dstOffset = dest.Stride - width * 4 mov edi, [eax].TImageData.Scan0 // edi = dest.Scan0 mov cl, [edx].TImageData.InvertLine cmp cl, [eax].TImageData.InvertLine je @@1 mov esi, [eax].TImageData.Stride mov ecx, [eax].TImageData.Height dec ecx // if (dest->InvertLine != source->InvertLine) imul ecx, esi // { add edi, ecx // edi += ((dest->Height - 1) * dest->Stride) shl esi, 1 // ebx -= (dest->Stride * 2) sub ebx, esi // } @@1: mov esi, [edx].TImageData.Scan0 // esi = source.Scan0 mov ecx, [eax].TImageData.Width mov edx, [eax].TImageData.Height pop eax end; procedure SetCopyRegs32(const Dest, Source: TImageData); asm mov ecx, [edx].TImageData.Width // ecx = width = min(source.Width, dest.Width) cmp ecx, [eax].TImageData.Width jbe @@1 mov ecx, [eax].TImageData.Width @@1: push ecx shl ecx, 2 mov ebx, [edx].TImageData.Stride sub ebx, ecx push ebx // eax = srcOffset = source.Stride - width * 4 mov esi, [edx].TImageData.Scan0 // esi = source.Scan0 mov ebx, [eax].TImageData.Stride sub ebx, ecx // ebx = dstOffset = dest.Stride - width * 4 mov edi, [eax].TImageData.Scan0 // edi = dest.Scan0 mov cl, [edx].TImageData.InvertLine mov edx, [edx].TImageData.Height// edx = height = min(source.Height, dest.Height) cmp edx, [eax].TImageData.Height jbe @@2 mov edx, [eax].TImageData.Height @@2: cmp cl, [eax].TImageData.InvertLine je @@3 mov ecx, [eax].TImageData.Stride mov eax, [eax].TImageData.Height dec eax // if (dest.InvertLine != source.InvertLine) imul eax, ecx // { add edi, eax // edi += ((dest.Height - 1) * dest.Stride) shl ecx, 1 // ebx -= (dest.Stride * 2) sub ebx, ecx // } @@3: pop eax pop ecx end; procedure CopyAlpha(var Dest: TImageData; const Source: TImageData); asm push esi push edi push ebx call SetCopyRegs32 @@yLoop: push ecx @@xLoop: add esi, 3 add edi, 3 movsb loop @@xLoop add esi, eax add edi, ebx pop ecx dec edx jnz @@yLoop pop ebx pop edi pop esi end; procedure FillBackground(Data: TImageData; ColorBackground: LongWord); asm push edi push ebx push edx call SetDataRegs32 pop eax cld @yLoop: push ecx rep stosd pop ecx add edi, ebx dec edx jnz @yLoop pop ebx pop edi end; function NewImageData(Width, Height: Integer; ColorBackground: LongWord): TImageData; begin Result := GetImageData(Width, Height); if ColorBackground <> 0 then FillBackground(Result, ColorBackground); end; procedure FreeImageData(var Data: TImageData); begin ImageData.FreeImageData(Data); end; procedure ImageClear(Data: TImageData; Color: LongWord); begin FillBackground(Data, Color); end; function GetSubData(const Data: TImageData; x, y, Width, Height: Integer): TImageData; asm push esi push edi push ebx mov esi, eax mov eax, Result mov ebx, Width // sub->Width = width test edx, edx // if (x < 0) jge @@1 // { add ebx, edx // sub->Width += x xor edx, edx // x = 0 @@1: // } mov edi, edx add edi, ebx cmp edi, [esi].TImageData.Width jle @@2 // if (sub->Width + x > data->Width) mov ebx, [esi].TImageData.Width sub ebx, edx // sub->Width = data->Width - x @@2: test ebx, ebx jle @@err mov [eax].TImageData.Width, ebx mov ebx, Height // sub->Height = height test ecx, ecx // if (y < 0) jge @@3 // { add ebx, ecx // sub->Height += y xor ecx, ecx // y = 0 @@3: // } mov edi, ecx add edi, ebx cmp edi, [esi].TImageData.Height jle @@4 // if (sub->Height + y > data->Height) mov ebx, [esi].TImageData.Height sub ebx, ecx // sub->Height = data->Height - y @@4: test ebx, ebx jle @@err mov [eax].TImageData.Height, ebx cmp [esi].TImageData.InvertLine, True jne @@5 add ecx, ebx // if (data->InvertLine) neg ecx // y = data->Height - sub->Height - y add ecx, [esi].TImageData.Height @@5: mov ebx, [esi].TImageData.Stride imul ecx, ebx // sub->Scan0 = data->Scan0 + y * data->Stride + x * 4 shl edx, 2 add ecx, edx add ecx, [esi].TImageData.Scan0 mov [eax].TImageData.Scan0, ecx mov [eax].TImageData.Stride, ebx // sub->Stride = data->Stride mov ebx, [esi].TImageData.PixelFormat // sub->PixelFormat = data->PixelFormat mov [eax].TImageData.PixelFormat, ebx mov ebx, [esi].TImageData.Reserved mov [eax].TImageData.Reserved, ebx mov [eax].TImageData.AllocScan, False // sub->AllocScan = FALSE clc jmp @@Exit @@err: stc @@Exit: pop ebx pop edi pop esi end; function GetSubImageData(const Data: TImageData; x, y, Width, Height: Integer): TImageData; asm push ebx mov ebx, Result mov [ebx].TImageData.Scan0, 0 call IsValid32 jc @@Exit push Width push Height push ebx call GetSubData @@Exit: pop ebx end; function GetClipImageData(Data: TImageData; x, y, Width, Height: Integer): TImageData; var sub: TImageData; begin sub := GetSubImageData(Data, x, y, Width, Height); if sub.Scan0 <> nil then begin Result := NewImageData(sub.Width, sub.Height, 0); ImageCopyTo(Result, sub); end; end; // 获取一个边界扩展Radius的剪切图。 function GetExpandData(const Data: TImageData; Radius: Integer; BorderMask: LongWord = $FFFFFFFF): TImageData; overload; var _radius, mask: Integer; asm push esi push edi push ebx mov _radius, edx mov mask, ecx push eax mov ebx, edx shl ebx, 1 mov edx, [eax].TImageData.Height mov eax, [eax].TImageData.Width xor ecx, ecx add edx, ebx add eax, ebx push Result // Result = NewImageData(Data.Width + radius * 2, call NewImageData // Data.Height + radius * 2) pop edx // edx = Data mov eax, Result mov edi, _radius // edi = Result.Scan0 + Result.Stride * radius imul edi, [eax].TImageData.Stride add edi, [eax].TImageData.Scan0 push [eax].TImageData.Scan0 push edi push [eax].TImageData.Width push [eax].TImageData.Stride mov esi, [edx].TImageData.Scan0 // esi = Data.Scan0 mov ebx, [edx].TImageData.Stride mov ecx, [edx].TImageData.Height// ecx = Data.Height mov eax, [edx].TImageData.Width shl eax, 2 sub ebx, eax // ebx = srcOffset cmp [edx].TImageData.InvertLine, True jne @@2 mov eax, [edx].TImageData.Stride dec ecx imul ecx, eax add esi, ecx shl eax, 1 sub ebx, eax mov ecx, [edx].TImageData.Height @@2: mov edx, [edx].TImageData.Width// edx = Data.Width @@cLoop: // for (y = 0; y < src.Height; y ++){ push ecx // { mov eax, [esi] // eax = Data.Scan0[y][0] and eax, mask mov ecx, _radius // memset(edi, eax, radius * 4) rep stosd // edi += radius * 4 mov ecx, edx // memcpy(edi, esi, Data.Width * 4) rep movsd // edi += Data.Width * 4 mov eax, [esi-4] // esi += Data.Width * 4 and eax, mask mov ecx, _radius // eax = Data.Scan0[y][Data.Width - 1] rep stosd // memset(edi, eax, radius * 4) add esi, ebx // esi += srcOffset pop ecx // edi += radius * 4 loop @@cLoop // } pop ecx pop edx // edx = Result.Width mov esi, edi // esi = edi - Result.Stride sub esi, ecx mov ebx, _radius @@bLoop: // for (y = 0; y < Radius; y ++) dec ebx // { js @@bEnd push esi mov ecx, edx // memcpy(edi, esi, Result.Width * 4) @@bxLoop: lodsd and eax, mask stosd // edi += (Result.Width * 4) loop @@bxLoop pop esi jmp @@bLoop // } @@bEnd: pop esi // esi = Result.Scan0 + radius * Result.Stride pop edi // edi = Result.Scan0 mov ebx, _radius // for (y = 0; y < radius; y ++) @@tLoop: // { dec ebx js @@tEnd push esi mov ecx, edx // memcpy(edi, esi, Result.Width * 4) @@txLoop: lodsd and eax, mask stosd // edi += (Result.Width * 4) loop @@txLoop pop esi jmp @@tLoop // } @@tEnd: @@Exit: pop ebx pop edi pop esi end; procedure GetExpandData(const Dest, Source: TImageData; Radius: Integer; var dst, src: TImageData); overload; var tmp: TImageData; begin dst := GetSubData(Dest, 0, 0, Source.Width, Source.Height); tmp := GetSubData(Source, 0, 0, dst.Width, dst.Height); src := GetExpandData(tmp, Radius); end;
对一个图像的局部数据进行处理,可以借助GetClipImageData或者GetSubImageData过程进行,前者是从图像数据上裁剪一个局部,对它的操作不会影响原图,而后者只是原图数据的一部分,对它的数据处理,就是对原图的局部图像处理。
GetExpandData是内部过程,功能是扩展图像边框,以便正确地处理图像边缘。在很多图像处理过程中,都涉及到图像边缘的处理,如图像缩放、旋转、卷积等操作,这是一个即耗时又麻烦的事,GetExpandData就是对图像预先进行边界扩展,这样在具体处理过程中就不必要作边界判断了。
七、过程调用过程。
本次重新修订,针对有些图像处理过程比较耗时和需要经常性进行调整的问题,对这些图像处理增加了回调处理过程,用于在图像调整时,及时终止前面的图像处理过程,图像处理过程中响应用户回调函数是个较麻烦的事,为此写了2个内部处理过程,存放于此作为本文第七部分(代码就不解释了):
type TImageAbort = function(Data: Pointer): BOOL; stdcall; var AbortSubSize: Integer = 256; function ImageSetAbortBlockSize(Size: Integer): Integer; begin Result := AbortSubSize; if Size > 0 then AbortSubSize := Size; end; function ExecuteProc(var Dest: TImageData; const Source: TImageData; ImageProc: Pointer; Args: array of const): Boolean; var Proc: Pointer; asm push esi push edi push ebx mov Proc, ecx mov ecx, Args-4 inc ecx jecxz @@1 mov ebx, Args // param = Args @@paramLoop: push dword ptr [ebx] // for (i = args; i > 0; i --, param -= 4) add ebx, 8 // push(param) loop @@paramLoop @@1: call SetCopyRegs32 call Proc mov eax, True pop ebx pop edi pop esi end; function ExecuteAbort(var Dest: TImageData; const Source: TImageData; AbortProc: Pointer; Args: array of const; Callback: TImageAbort; CallbackData: Pointer): Boolean; var width, height, w, size: Integer; wBytes, dhBytes, shBytes: Integer; dstScan0, srcScan0: Pointer; dstOffset, srcOffset: Integer; argCount: Integer; Proc: Pointer; asm push esi push edi push ebx mov Proc, ecx mov ecx, [eax].TImageData.Width cmp ecx, [edx].TImageData.Width jbe @@1 mov ecx, [edx].TImageData.Width @@1: mov width, ecx // width = Min(source.width, dest.width) mov ecx, [eax].TImageData.Height cmp ecx, [edx].TImageData.Height jbe @@2 mov ecx, [edx].TImageData.Height @@2: mov height, ecx // height = Min(source.height, dest.height) mov ecx, AbortSubSize mov size, ecx // size = @AbortSubSize shl ecx, 2 mov wBytes, ecx // wBytes = size * 4 mov ebx, [edx].TImageData.Scan0 mov srcScan0, ebx // srcScan0 = source.Scan0 mov ebx, [edx].TImageData.Stride mov edi, size imul edi, ebx mov shBytes, edi // shBytes = size * source.Stride sub ebx, ecx mov srcOffset, ebx // srcOffset = source.Stride - wBytes mov edi, [eax].TImageData.Scan0// dstScan0 = dest.Scan0 mov ebx, [eax].TImageData.Stride mov esi, size imul esi, ebx // dhBytes = size * dest.Stride sub ebx, ecx // dstOffset = dest.Stride - wBytes mov cl, [eax].TImageData.InvertLine cmp cl, [edx].TImageData.InvertLine je @@3 mov ecx, [eax].TImageData.Stride mov eax, [eax].TImageData.Height dec eax // if (dest.InvertLine != source.InvertLine) imul eax, ecx // { add edi, eax // dstScan0 += ((height - 1) * dest.Stride shl ecx, 1 // dstOffset -= (dest.Stride * 2) sub ebx, ecx // dhBytes = -dhBytes neg esi // } @@3: mov dstScan0, edi // edi = dstScan0 mov dstOffset, ebx mov dhBytes, esi mov esi, srcScan0 // esi = srcScan0 mov eax, Args-4 inc eax mov argCount, eax @@hLoop: mov edx, size // for (; height > 0; height -= size) cmp edx, height // { jbe @@4 // edx = size (sub height) mov edx, height // if (edx > height) edx = height @@4: mov eax, width // for (w = width; w > 0; w -= size) mov w, eax // { @@wLoop: push edx push esi push edi mov ecx, argCount jecxz @@5 mov ebx, Args // param = Args @@paramLoop: push dword ptr [ebx] // for (i = args; i > 0; i --, param -= 4) add ebx, 8 // push(param) loop @@paramLoop @@5: mov ecx, size // ecx = size (sub width) mov ebx, dstOffset // ebx = dstOffset mov eax, srcOffset // eax = srcOffset cmp ecx, w jbe @@6 sub ecx, w // if (ecx > w) shl ecx, 2 // { add ebx, ecx // ebx += ((ecx - w) * 4) add eax, ecx // eax += ((ecx - w) * 4) mov ecx, w // ecx = w @@6: // } call Proc // AbotrProc(...) push CallbackData call Callback // if (callback(callbackData)) test eax, eax // return jz @@7 add esp, 12 jmp @@Abort @@7: pop edi pop esi pop edx add esi, wBytes // esi += wBytes add edi, wBytes // edi += wBytes mov eax, w sub eax, size mov w, eax jg @@wLoop // } mov eax, height sub eax, size jle @@Exit mov height, eax mov esi, srcScan0 // srcScan0 += shBytes add esi, shBytes // esi = srcScan0 mov srcScan0, esi mov edi, dstScan0 // dstScan0 += dhBytes add edi, dhBytes // edi = dstScan0 mov dstScan0, edi jmp @@hLoop // } @@Abort: xor eax, eax jmp @@End @@Exit: mov eax, True @@End: pop ebx pop edi pop esi end;
八、内部使用参数表及初始化过程(这些过程是在单元最后部分调用的,因此原样复制于此):
type TInterpolateProc = procedure; PARGBQuadW = ^TARGBQuadW; TARGBQuadW = packed record wBlue: Word; wGreen: Word; wRed: Word; wAlpha: Word; end; var BicubicTable: Pointer; BicubicSlope: Single; BilinearTable: Pointer; ArgbTable: array[0..255] of TARGBQuadW; PSTable: Pointer; AbortSubSize: Integer = 256; ...... ...... procedure InitPSTable; begin PSTable := GlobalAllocPtr(GMEM_MOVEABLE, (128 + 1) * 8); asm push edi mov edi, PSTable xor eax, eax xor ecx, ecx @@Loop: stosw stosw stosw stosw inc ecx mov eax, 16384 cdq div ecx sub eax, 128 cmp ecx, 128 jle @@Loop pop edi end; end; procedure InitArgbTable; asm push edi lea edi, ArgbTable xor eax, eax @@Loop: stosw stosw stosw stosw inc eax cmp eax, 256 jl @@Loop pop edi end; procedure InitBilinearTable; begin BilinearTable := GlobalAllocPtr(GMEM_MOVEABLE, 256 * 2 * 8); asm push esi push edi mov esi, BilinearTable mov edi, 256*8 xor ecx, ecx @@sumLoop: mov edx, ecx and edx, 255 // u(v) = x & 0xff mov eax, 100h sub eax, edx jnz @@1 dec edx jmp @@2 @@1: test edx, edx jnz @@2 dec eax @@2: shl edx, 16 or edx, eax // edx = 00 u(v) 00 ff-u(v) movd mm0, edx movd mm1, edx punpcklwd mm0, mm0 // mm0 = 00 u 00 u 00 ff-u 00 ff-u punpckldq mm1, mm1 // mm1 = 00 v 00 ff-v 00 v 00 ff-v movq [esi], mm0 movq [esi+edi], mm1 add esi, 8 inc ecx cmp ecx, 256 jl @@sumLoop pop edi pop esi emms end; end; procedure SetBicubicSlope(const Value: Single); type TBicArray = array[0..3] of Smallint; PBicArray = ^TBicArray; var I: Integer; P: PBicArray; function BicubicFunc(x : double): double; var x2, x3: double; begin x2 := x * x; x3 := x2 * x; if x <= 1.0 then Result := (Value + 2.0) * x3 - (Value + 3.0) * x2 + 1.0 else if x <= 2.0 then Result := Value * x3 - (5.0 * Value) * x2 + (8.0 * Value) * x - (4.0 * Value) else Result := 0.0; end; begin if (BicubicSlope <> Value) and (Value < 0.0) {and (Value >= -2.0)} then begin BicubicSlope := Value; P := BicubicTable; for I := 0 to 512 do begin P^[0] := Round(16384 * BicubicFunc(I * (1.0 / 256))); P^[1] := P^[0]; P^[2] := P^[0]; P^[3] := P^[0]; Inc(P); end; end; end; initialization begin InitArgbTable; InitPSTable; InitBilinearTable; BicubicTable := GlobalAllocPtr(GMEM_MOVEABLE, (512 + 1) * 8); SetBicubicSlope(-0.75); end; finalization begin GlobalFreePtr(BicubicTable); GlobalFreePtr(BilinearTable); GlobalFreePtr(PSTable); end;
关于图像数据的转换就介绍到这里。
文章中使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。
文章中所用数据类型及一些内部过程见《Delphi图像处理 -- 数据类型及内部过程》。
尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
补充:本文于2010.5.20重新修订