阅读提示:
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
尽可能保持二者内容一致,可相互对照。
《Delphi图像处理》系列修改说明(2012.1.9)
在整理、规划《Delphi图像处理》系列时,我确确实实是把每篇文章都作为这个系列的一部分来安排的,但文章陆续发表后,却反而没有以前零散文章的效果好,不少读者反映有些函数或者变量不知道什么地方查找,究其原因,主要是系列文章之间联系太紧了(本就是一个单元分割的),虽然我在文章中作了说明,但因文章太多,过程也太多,不容易找到所需要的东西。
趁春节前后,我准备对《Delphi图像处理》系列作重新规划和全面修改,尽可能减少文章之间的偶合度,除了极少数公用代码和一些代码太长的函数,宁愿代码重复粘贴;同时,将原来图像处理过程中对图像数据结构的范围检查、异常处理等代码全部删除;将原图像处理过程中的拷贝形式过程全部删除,总之,只保留图像处理的精华部分,当然肯定是完整的。
这次对本文原有的数据类型、公用过程和变量作了大量修改和删减,系列的每篇文章都必须包含本文的ImageData.pas单元。
《Delphi图像处理》系列前言(2009.10.7)
尽三年来,本人陆续写了六十多篇BOLO文章,其中绝大部分是有关图像处理的,因此,有人戏称我为图像处理专家(或GDI+专家),此戏称当然是对我的赞扬,但我自己却觉得略含一丝丝贬义,即除了图像处理,别的方面(如网络、数据库等)都不咋的,事实也确实如此。
我是一名机关工作人员,主业是统计,职称是统计师,但是因我对计算机很有兴趣,所以,在1989年,领导干脆安排我管理机关的计算机,在此期间,我自学了Basic、Pascal、C、C++及ASM等多种语言,最“精通”的是C(对当时热门的大众语言dBase反倒不热心,因为我认为那只是一个关系数据库软件包,充其量也就算个解释语言,研究它没意思)。也写过一些系统内部自用的程序,并获得过省、部门的有关奖项,在当时来说,我也算得上是一个较优秀的程序员,即使不是专业的。十一年前,机关公务员改革,为了给年轻人让道,我们这批老家伙被下岗了(这是真正意义上的下岗,全薪退养,当时我45岁),后来又被返聘到政府办公室信息中心干了2年,以后被儿子接到大连养老至今。因闲来无事,在上网、游戏之余,偶尔也摆弄几下C/C++、Delphi等,又因只有图形图像这块不需要与具体的事务联系,所以摆弄图像处理是顺理成章的事了。
由于是随意摆弄的,我的图像处理文章之间缺乏一些必要的联系,显得很零碎,因此最近我花了些时间,重新整理、规范了一下代码,有Delphi的,也有C/C++的,所以我打算各图像处理文章也整理一遍,纯GDI+的还是保留为《GDI+在Delphi程序的应用》系列,其余的则划为《Delphi图像处理》和《C++图像处理》2个系列。
本篇为《Delphi图像处理》系列的基础代码,即数据类型定义和一些内部使用过程。
虽然Delphi似乎没落了,使用的人也越来越少,但Delphi本身的魅力依然存在,特别是在图像处理方面,其BASM的效率和灵活性堪比真正的汇编,即使C/C++也没得比。说到这里,特别提一下,有的朋友看了我的文章,对其中大量的BASM代码有些不满意,在这里我表示歉意,并不是我非要“卖弄”代码,而是要想进行高效、实用性的处理图像,不得不借助它。除了教科书和没法使用插入汇编的语言外,绝大部分实用性的图像处理核心代码都是借助(插入)汇编的。
下面是图像处理数据定义和部分内部处理过程代码,我把它们独立为一个单元,系列的每篇文章都必须包含本单元:
unit ImageData; interface uses Windows, SysUtils, Classes, Graphics, Gdiplus; type // 插值方式: 缺省(线性插值),临近,线性,双立方 TInterpolateMode = (imDefault, imNear, imBilinear, imBicubic); // 与GDI+ TBitmapData兼容的图像数据结构 PImageData = ^TImageData; TImageData = packed record Width: Integer; // 像素宽度 Height: Integer; // 像素高度 Stride: Integer; // 扫描宽度 PixelFormat: LongWord; // GDI+像素格式 Scan0: Pointer; // 扫描行首地址 case Integer of 0: (LockMode: byte; // GDI+锁数据方式 AllocScan: Boolean; // 是否分配图像数据内存 AlphaFlag: Boolean; // 是否含Alpha IpMode: TInterpolateMode);// 插值方式 1: (Reserved: UINT); // 保留 end; PARGBQuad = ^TARGBQuad; TARGBQuad = packed record case Integer of 0: (Blue, Green, Red, Alpha: Byte); 1: (Color: TARGB); end; PGrayTable = ^TGrayTable; TGrayTable = array[0..255] of Byte; PMMType = ^TMMType; TMMType = array[0..3] of Word; // 锁定GDI+32位位图扫描线并返回图形数据结构 function LockGpBitmap(Bmp: TGpBitmap): TImageData; // GDI+位图扫描线解锁 procedure UnlockGpBitmap(Bmp: TGpBitmap; var Data: TImageData); // 获取图像数据结构。参数;宽度,高度,扫描线宽度,扫描线地址,像素格式,alpha标记 function GetImageData(Width, Height, Stride: Integer; Scan0: Pointer; format: Graphics.TPixelFormat; IsAlpha: Boolean): TImageData; // 获取新的图像据结构。参数;宽度,高度,像素格式。必须用FreeImageData释放 function NewImageData(Width, Height: Integer; format: Graphics.TPixelFormat = pf32bit): TImageData; // 获取TBitmap的图像数据结构。 // 如果IsTo32bit=False,按Bmp像素格式,否则按32位格式获取 function GetBitmapData(Bmp: TBitmap; IsTo32bit: Boolean = True): TImageData; // 获取并返回Data的子图像数据结构(只支持32位)。Scan0=nil失败 function GetSubImageData(const Data: TImageData; x, y, Width, Height: Integer): TImageData; overload; // 如果Data分配了扫描线内存,释放扫描线内存 procedure FreeImageData(var Data: TImageData); // 设置插值方式。返回设置前的值 function SetInterpolateMode(var Data: TImageData; const Value: TInterpolateMode): TInterpolateMode; // 设置图像数据操作寄存器(汇编码用) procedure _SetDataRegs(const Data: TImageData); // 设置图像数据拷贝寄存器(汇编码用) procedure _SetCopyRegs(const Dest, Source: TImageData); // 翻转图像数据扫描线 function _InvertScan0(var Data: TImageData): PImageData; // 转换浮点数为整数,按无穷大方式保留浮点数小数 function _Infinity(Value: Single): Integer; // 获取并返回Data的边框扩展图像数据结构。Radius:扩展半径 function _GetExpandData(const Data: TImageData; Radius: Integer): TImageData; // PARGB格式转换成ARGB格式 procedure PArgbConvertArgb(var Data: TImageData); // ARGB格式转换成PARGB格式 procedure ArgbConvertPArgb(var Data: TImageData); var DivTab: array[0..1024] of LongWord; // 汇编用除法转乘法表(被除数0 - 1024) {$ALIGN 8} ArgbTab: array[0..256] of TMMType; // mmx ARGB 灰度表(0 - 256) MMDivTab: array[0..255] of TMMType; // mmx 用除法转乘法表(被除数0 - 255) {$ALIGN OFF} implementation uses GdipExport; type TGpObj = class(TGdiplusBase) end; // 返回GDI+图像原像素格式(TGpImage.PixelFormat是VCL风格枚举,不再含像素信息) function GetGpPixelFormat(Bmp: TGpBitmap): Integer; begin GdipGetImagePixelFormat(TGpObj(Bmp).Native, Result); end; function LockGpBitmap(Bmp: TGpBitmap): TImageData; var Format: Integer; begin Format := GetGpPixelFormat(Bmp); TBitmapData(Result) := Bmp.LockBits(GpRect(0, 0, Bmp.Width, Bmp.Height), [imRead, imWrite], pf32bppARGB); Result.AlphaFlag := (format and $00040000) <> 0; end; procedure UnlockGpBitmap(Bmp: TGpBitmap; var Data: TImageData); begin Data.Reserved := Data.Reserved and $ff; Bmp.UnlockBits(TBitmapData(Data)); end; function GetImageData(Width, Height, Stride: Integer; Scan0: Pointer; format: Graphics.TPixelFormat; IsAlpha: Boolean): TImageData; const Bits: array [pf1bit..pf32bit] of LongWord = ($100, $400, $800, $1005, $1000, $1800, $2000); begin if (format < pf1bit) or (format > pf32bit) then raise Exception.Create('Does not support the pixel format images.'); Result.Width := Width; Result.Height := Height; Result.Scan0 := Scan0; Result.PixelFormat := Bits[format]; if Stride = 0 then Result.Stride := ((Width * (Result.PixelFormat shr 8) + 31) and not 31) shr 3 else Result.Stride := Stride; Result.Reserved := DWORD(IsAlpha) shl 16; end; function NewImageData(Width, Height: Integer; format: Graphics.TPixelFormat): TImageData; begin Result := GetImageData(Width, Height, 0, nil, format, format = pf32bit); Result.Scan0 := GlobalAllocPtr(GHND, Height * Result.Stride); if Result.Scan0 = nil then raise EOutOfMemory.Create('Scan line image memory allocation failed.'); Result.AllocScan := True; end; function GetBitmapData(Bmp: TBitmap; IsTo32bit: Boolean): TImageData; procedure FillAlpha; asm mov eax, Result mov edx, [eax].TImageData.Scan0 mov ecx, [eax].TImageData.Width imul ecx, [eax].TImageData.Height mov eax, 0ff000000h @@Loop: or [edx], eax add edx, 4 loop @@Loop end; var OldFormat: Graphics.TPixelFormat; begin with Bmp do begin OldFormat := PixelFormat; if IsTo32bit then PixelFormat := pf32bit; Result := GetImageData(Width, Height, 0, ScanLine[Height - 1], PixelFormat, OldFormat = pf32bit); if (OldFormat <> pf32bit) and IsTo32bit then FillAlpha; end; _InvertScan0(Result); // Windows bitmap end; function GetSubImageData(const Data: TImageData; x, y, Width, Height: Integer): TImageData; asm push esi push edi mov esi, Width add esi, edx jle @@err cmp esi, [eax].TImageData.Width cmova esi, [eax].TImageData.Width mov edi, [eax].TImageData.Scan0 test edx, edx jle @@1 sub esi, edx jle @@err shl edx, 2 add edi, edx @@1: mov edx, Height add edx, ecx jle @@err cmp edx, [eax].TImageData.Height cmova edx, [eax].TImageData.Height test ecx, ecx jle @@2 sub edx, ecx jle @@err imul ecx, [eax].TImageData.Stride add edi, ecx @@2: mov ecx, Result mov [ecx].TImageData.Width, esi mov [ecx].TImageData.Height, edx mov [ecx].TImageData.Scan0, edi mov edx, [eax].TImageData.PixelFormat mov [ecx].TImageData.PixelFormat, edx mov edx, [eax].TImageData.Stride mov [ecx].TImageData.Stride, edx mov edx, [eax].TImageData.Reserved mov [ecx].TImageData.Reserved, edx mov [ecx].TImageData.AllocScan, False clc jmp @@Exit @@err: mov [ecx].TImageData.Scan0, 0 stc @@Exit: pop edi pop esi end; procedure FreeImageData(var Data: TImageData); begin if Data.AllocScan and (Data.Scan0 <> nil) then begin if Data.Stride < 0 then _InvertScan0(Data); GlobalFreePtr(Data.Scan0); Data.Reserved := 0; end; end; function SetInterpolateMode(var Data: TImageData; const Value: TInterpolateMode): TInterpolateMode; begin Result := Data.IpMode; Data.IpMode := Value; end; function _InvertScan0(var Data: TImageData): PImageData; asm push edx mov edx, [eax].TImageData.Height dec edx imul edx, [eax].TImageData.Stride add [eax].TImageData.Scan0, edx neg [eax].TImageData.Stride pop edx end; // <-- edi Scan0 // <-- ebx ScanOffset // <-- ecx Width // <-- edx Height procedure _SetDataRegs(const Data: TImageData); asm mov edi, [eax].TImageData.Scan0 mov ecx, [eax].TImageData.Width movzx edx, byte ptr[eax].TImageData.PixelFormat[1] imul edx, ecx add edx, 7 shr edx, 3 mov ebx, [eax].TImageData.Stride sub ebx, edx mov edx, [eax].TImageData.Height end; // <-- edi dest Scan0 // <-- ebx dest ScanOffset // <-- esi source Scan0 // <-- eax source ScanOffset // <-- ecx width // <-- edx height procedure _SetCopyRegs(const Dest, Source: TImageData); asm mov ecx, [edx].TImageData.Width // ecx = min(source.Width, dest.Width) cmp ecx, [eax].TImageData.Width cmova ecx, [eax].TImageData.Width movzx esi, byte ptr[edx].TImageData.PixelFormat[1] imul esi, ecx add esi, 7 shr esi, 3 mov ebx, [edx].TImageData.Stride sub ebx, esi push ebx // eax = source.Stride - (PixelBits * width + 7) / 8 movzx esi, byte ptr[eax].TImageData.PixelFormat[1] imul esi, ecx add esi, 7 shr esi, 3 mov ebx, [eax].TImageData.Stride sub ebx, esi // ebx = dest.Stride - (PixelBits * width + 7) / 8 mov esi, [edx].TImageData.Scan0 // esi = source.Scan0 mov edi, [eax].TImageData.Scan0 // edi = dest.Scan0 mov edx, [edx].TImageData.Height// edx = min(source.Height, dest.Height) cmp edx, [eax].TImageData.Height cmova edx, [eax].TImageData.Height pop eax end; function _Infinity(Value: Single): Integer; asm fld Value sub esp, 8 fstcw word ptr [esp] fstcw word ptr [esp+2] fwait or word ptr [esp+2], 0b00h fldcw word ptr [esp+2] fistp dword ptr [esp+4] fwait fldcw word ptr [esp] pop eax pop eax end; function _GetExpandData(const Data: TImageData; Radius: Integer): TImageData; var Width, SrcOffset: Integer; asm push esi push edi push ebx push ecx push ecx // NewImageData param: Result mov edi, eax mov ebx, edx shl edx, 1 // Size = Radius * 2 mov eax, [edi].TImageData.Width add eax, edx add edx, [edi].TImageData.Height mov ecx, pf32bit call NewImageData// Result = NewImageData(Data.Width + Size, Data.Height + Size, pf32bit) mov eax, [edi].TImageData.Stride mov ecx, [edi].TImageData.Width mov edx, [edi].TImageData.Height mov esi, [edi].TImageData.Scan0 mov Width, ecx shl ecx, 2 sub eax, ecx mov SrcOffset, eax pop eax // eax = Result mov cx, word ptr[edi].TImageData.AlphaFlag mov word ptr[eax].TImageData.AlphaFlag, cx mov edi, [eax].TImageData.Stride imul edi, ebx add edi, [eax].TImageData.Scan0 push [eax].TImageData.Scan0 push edi push eax @@cLoop: mov eax, [esi] mov ecx, ebx rep stosd mov ecx, Width rep movsd mov eax, [esi-4] mov ecx, ebx rep stosd add esi, SrcOffset dec edx jnz @@cLoop pop eax // eax = Result mov esi, edi sub esi, [eax].TImageData.Stride mov edx, [eax].TImageData.Width push ebx @@bLoop: push esi mov ecx, edx rep movsd pop esi dec ebx jnz @@bLoop pop ebx pop esi pop edi @@tLoop: push esi mov ecx, edx rep movsd pop esi dec ebx jnz @@tLoop pop ebx pop edi pop esi @@Exit: end; procedure PArgbConvertArgb(var Data: TImageData); asm push edi push ebx call _SetDataRegs mov eax, 255 cvtsi2ss xmm6, eax pshufd xmm6, xmm6, 0 pxor xmm7, xmm7 @@yLoop: push ecx @@xLoop: movd xmm0, [edi] punpcklbw xmm0, xmm7 punpcklwd xmm0, xmm7 cvtdq2ps xmm0, xmm0 pshufd xmm1, xmm0, 255 mulps xmm0, xmm6 divps xmm0, xmm1 cvtps2dq xmm0, xmm0 packssdw xmm0, xmm7 packuswb xmm0, xmm7 mov al, [edi].TARGBQuad.Alpha movd [edi], xmm0 mov [edi].TARGBQuad.Alpha, al add edi, 4 loop @@xLoop add edi, ebx pop ecx dec edx jnz @@yLoop pop ebx pop edi end; procedure ArgbConvertPArgb(var Data: TImageData); asm push edi push ebx call _SetDataRegs mov eax, 255 cvtsi2ss xmm6, eax pshufd xmm6, xmm6, 0 pxor xmm7, xmm7 @@yLoop: push ecx @@xLoop: movd xmm0, [edi] punpcklbw xmm0, xmm7 punpcklwd xmm0, xmm7 cvtdq2ps xmm0, xmm0 pshufd xmm1, xmm0, 255 mulps xmm0, xmm1 divps xmm0, xmm6 cvtps2dq xmm0, xmm0 packssdw xmm0, xmm7 packuswb xmm0, xmm7 mov al, [edi].TARGBQuad.Alpha movd [edi], xmm0 mov [edi].TARGBQuad.Alpha, al add edi, 4 loop @@xLoop add edi, ebx pop ecx dec edx jnz @@yLoop pop ebx pop edi end; procedure InitArgbTable; asm push edi lea edi, ArgbTab xor eax, eax @@Loop: stosw stosw stosw stosw inc eax cmp eax, 256 jle @@Loop pop edi end; procedure InitDivTable; asm push edi lea edi, DivTab mov eax, -1 stosd stosd mov ecx, 2 @@Loop: mov eax, ecx dec eax mov edx, 1 div ecx stosd inc ecx cmp ecx, 1024 jle @@Loop lea edi, MMDivTab mov eax, -1 stosd stosd stosd stosd mov ecx, 2 @@Loop2: mov eax, ecx dec eax or eax, 10000h xor edx, edx div ecx stosw stosw stosw stosw inc ecx cmp ecx, 256 jl @@Loop2 pop edi end; initialization begin InitArgbTable; InitDivTable; end; end.
图像处理采用了与GDI+位图数据类型TBitmapData兼容的TGpImageData类型,图像处理例子采用GDI+位图TGpBitmap对象和VCL位图TBitmap对象,因此ImageData.pas单元提供了这2种类型的转换过程,而且只提供了锁定(绑定)形式的转换。以前提供的拷贝形式过程和其它类型本次修改中删除。
《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《Delphi图像处理 -- 文章索引》。