前言
尽三年来,本人陆续写了六十多篇BOLO文章,其中绝大部分是有关图像处理的,因此,有人戏称我为图像处理专家(或GDI+专家),此戏称当然是对我的赞扬,但我自己却觉得略含一丝丝贬义,即除了图像处理,别的方面(如网络、数据库等)都不咋的,事实也确实如此。
我是一名机关工作人员,主业是统计,职称是统计师,但是因我对计算机很有兴趣,所以,在1989年,领导干脆安排我管理机关的计算机,在此期间,我自学了Basic、Pascal、C、C++及ASM等多种语言,最“精通”的是C(对当时热门的大众语言dBase反倒不热心,因为我认为那只是一个关系数据库软件包,充其量也就算个解释语言,研究它没意思)。也写过一些系统内部自用的程序,并获得过省、部门的有关奖项,在当时来说,我也算得上是一个较优秀的程序员,即使不是专业的。十一年前,机关公务员改革,为了给年轻人让道,我们这批老家伙被下岗了(这是真正意义上的下岗,全薪退养,当时我45岁),后来又被返聘到政府办公室信息中心干了2年,以后被儿子接到大连养老至今。因闲来无事,在上网、游戏之余,偶尔也摆弄几下C/C++、Delphi等,又因只有图形图像这块不需要与具体的事务联系,所以摆弄图像处理是顺理成章的事了。
由于是随意摆弄的,我的图像处理文章之间缺乏一些必要的联系,显得很零碎,因此最近我花了些时间,重新整理、规范了一下代码,有Delphi的,也有C/C++的,所以我打算各图像处理文章也整理一遍,纯GDI+的还是保留为《GDI+在Delphi程序的应用》系列,其余的则划为《Delphi图像处理》和《C/C++图像处理》2个系列。
本篇为《Delphi图像处理》系列的基础代码,即数据类型定义和一些内部使用过程。
虽然Delphi似乎没落了,使用的人也越来越少,但Delphi本身的魅力依然存在,特别是在图像处理方面,其BASM的效率和灵活性堪比真正的汇编,即使C/C++也没得比(以后的《C/C++图像处理》文章中我会证明的)。说到这里,特别提一下,有的朋友看了我的文章,对其中大量的BASM代码有些不满意,在这里我表示歉意,并不是我非要“卖弄”代码,而是要想进行高效、实用性的处理图像,不得不借助它。除了教科书和没法使用插入汇编的语言外,绝大部分实用性的图像处理核心代码都是借助(插入)汇编的。
下面是图像处理数据定义和部分内部处理过程代码,我把它们独立为一个单元,真正应用性的调用图像处理过程时,可不必包含本单元:
unit ImageData; (***************************************************************************** * * * 本单元定义了图像数据处理基本结构及内部处理过程。 * * 如果不是添加图像处理过程,一般不用直接引用本单元。 * * * * 编制人: 湖北省公安县统计局 毛泽发 2008.10 * * * *****************************************************************************) interface uses Windows, SysUtils, Classes, Graphics; const EIDErrorSourceMust = 'Source image data must be %d-bit pixel format.'; EIDErrorDestMust = 'The target image data must be %d-bit pixel format.'; EIDErrorColorTable = 'The lack of %d-bit image data corresponding color table.'; EIDErrorOutOfMemory = 'Scan line image memory allocation failed.'; EIDErrorNotSupport = 'Does not support the pixel format images.'; EIDErrorEmptyData = 'Empty image data structure.'; EIDErrorParam = 'Wrong image data structure or parameter.'; type EImageDataError = Exception; // 256色灰度统计数组,每个元素表示该下标对应的颜色个数 TGrayArray = array[0..255] of LongWord; // 灰度统计结构 TGrayStatData = packed record Grays: TGrayArray; // 灰度数组 Total: int64; // 总的灰度值 Count: LongWord; // 总的像素点数 MaxGray: LongWord; // 像素点最多的灰度值 MinGray: LongWord; // 像素点最少的灰度值 Average: LongWord; // 平均灰度值(Total / Count) end; PGrayStatData = ^TGrayStatData; // 与GDI+ TBitmapData兼容的图像数据结构 TImageData = packed record Width: Integer; // 像素宽度 Height: Integer; // 像素高度 Stride: Integer; // 扫描宽度 PixelFormat: LongWord; // GDI+像素格式 Scan0: Pointer; // 扫描行首地址 case Integer of 0: (LockMode: byte; // GDI+锁数据方式 notuse: Byte; InvertLine: Boolean; // 是否倒置的扫描线 AllocScan: Boolean); // 是否分配图像数据内存 1: (Reserved: UINT); // 保留 end; PImageData = ^TImageData; PARGBQuad = ^TARGBQuad; TARGBQuad = packed record Blue: Byte; Green: Byte; Red: Byte; Alpha: Byte; end; // 获取图像数据结构。InvertLine图像是否倒置的扫描线 function GetImageData(Width, Height, Stride: Integer; Scan0: Pointer; format: TPixelFormat = pf32bit; InvertLine: Boolean = False): TImageData; overload; // 获取新的图像数据结构,必须用FreeImageData释放 function GetImageData(Width, Height: Integer; format: TPixelFormat = pf32bit): TImageData; overload; // 获取TBitmap类型的图像数据结构,像素格式范围pf1Bit - pf32Bit function GetBitmapData(Bitmap: TBitmap): TImageData; {$IF RTLVersion >= 17.00}inline;{$IFEND} // 获取Hbitmap的图像数据结构,PixelBits像素位数。用FreeImageData释放。 // function GetHBitmapData(DC: HDC; Bitmap: HBitmap; PixelBits: Integer = 32): TImageData; // 如果Data分配了扫描线内存,释放扫描线内存 procedure FreeImageData(var Data: TImageData); // 根据图像数据结构Data填充并返回Windows位图信息的文件头 function GetBitmapInfoHeader(const Data: TImageData): TBitmapInfoHeader; // 获取DC裁剪矩形ClipRect,并根据dstRect调整ClipRect和dstRect,ClipRect非空返回True. // 说明:dstRect和ClipRect的Right和Bottom分别为宽度和高度 function GetDCClipBox(DC: HDC; var dstRect, ClipRect: TRect): Boolean; // 按Data大小和pbi信息获取DC从x,y坐标处的图像数据到Data.Scan0(内部使用) procedure GetDCImageData(DC: HDC; x, y: Integer; var Data: TImageData; pbi: TBitmapInfo); // 将Data图像数据按pbi信息转换后传输到DC的x, y坐标处(内部使用) procedure BitBltImageData(DC: HDC; x, y: Integer; const Data: TImageData; pbi: TBitmapInfo); // 交换Colors数组内的Red与Blue procedure SwapColors(Colors: PRGBQuad; Count: Integer); // 为BASM拷贝过程设置寄存器(内部使用) // in eax = Dest, edx = Source // out esi = source.Scan0, eax = source.Offset // edi = dest.Scan0, ebx = dest.Offset, // ecx = min Width, edx = min Height procedure SetCopyRegs(Dest, Source: TImageData); // --> st floay value // <-- eax int value function _Infinity: Integer; function GetInfinity(X: double): Integer; implementation function GetImageData(Width, Height, Stride: Integer; Scan0: Pointer; format: TPixelFormat; InvertLine: Boolean): TImageData; const BitCounts: array [pf1bit..pf32bit] of Byte = (1, 4, 8, 16, 16, 24, 32); begin if (format < pf1bit) or (format > pf32bit) then raise EImageDataError.Create(EIDErrorNotSupport); Result.Width := Width; Result.Height := Height; Result.Scan0 := Scan0; Result.Reserved := 0; Result.InvertLine := InvertLine; Result.PixelFormat := BitCounts[format]; if Stride = 0 then Result.Stride := ((Width * Result.PixelFormat + 31) and $ffffffe0) shr 3 else Result.Stride := Stride; Result.PixelFormat := Result.PixelFormat shl 8; if format = pf15bit then Result.PixelFormat := Result.PixelFormat or 5; end; function GetImageData(Width, Height: Integer; format: TPixelFormat): TImageData; begin Result := GetImageData(Width, Height, 0, nil, format); Result.Scan0 := GlobalAllocPtr(GHND, Height * Result.Stride); if Result.Scan0 = nil then raise EOutOfMemory.Create(EIDErrorOutOfMemory); Result.AllocScan := True; end; function GetBitmapData(Bitmap: TBitmap): TImageData; begin with Bitmap do Result := GetImageData(Width, Height, 0, ScanLine[Height - 1], PixelFormat, True); end; procedure FreeImageData(var Data: TImageData); begin if Data.AllocScan and (Data.Scan0 <> nil) then GlobalFreePtr(Data.Scan0); Data.Scan0 := nil; Data.Reserved := Data.Reserved and $ff; end; function GetBitmapInfoHeader(const Data: TImageData): TBitmapInfoHeader; begin Result.biSize := Sizeof(TBitmapInfoHeader); Result.biWidth := Data.Width; Result.biHeight := Data.Height; Result.biPlanes := 1; Result.biBitCount := (Data.PixelFormat shr 8) and $ff; Result.biCompression := BI_RGB; end; procedure GetDCImageData(DC: HDC; x, y: Integer; var Data: TImageData; pbi: TBitmapInfo); var saveBitmap, Bitmap: HBITMAP; memDC: HDC; begin Bitmap := CreateCompatibleBitmap(DC, Data.Width, Data.Height); try memDC := CreateCompatibleDC(DC); saveBitmap := SelectObject(memDC, Bitmap); try BitBlt(memDC, 0, 0, Data.Width, Data.Height, DC, x, y, SRCCOPY); finally SelectObject(memDC, saveBitmap); DeleteDC(memDC); end; GetDIBits(DC, bitmap, 0, Data.Height, Data.Scan0, pbi, DIB_RGB_COLORS); finally DeleteObject(Bitmap); end; end; procedure BitBltImageData(DC: HDC; x, y: Integer; const Data: TImageData; pbi: TBitmapInfo); var saveBitmap, Bitmap: HBITMAP; memDC: HDC; begin Bitmap := CreateDIBItmap(DC, pbi.bmiHeader, CBM_INIT, Data.Scan0, pbi, DIB_RGB_COLORS); memDC := CreateCompatibleDC(DC); saveBitmap := SelectObject(memDC, Bitmap); try BitBlt(DC, x, y, Data.Width, Data.Height, memDC, 0, 0, SRCCOPY); finally SelectObject(memDC, saveBitmap); DeleteDC(memDC); DeleteObject(Bitmap); end; end; function GetDCClipBox(DC: HDC; var dstRect, ClipRect: TRect): Boolean; var w, h: Integer; begin Result := False; if GetClipBox(DC, ClipRect) <= NULLREGION then Exit; w := dstRect.Right; if dstRect.Left < 0 then begin Inc(w, dstRect.Left); ClipRect.Left := 0; end else begin ClipRect.Left := dstRect.Left; dstRect.Left := 0; end; if w + ClipRect.Left > ClipRect.Right then w := ClipRect.Right - ClipRect.Left; if w <= 0 then Exit; h := dstRect.Bottom; if dstRect.Top < 0 then begin Inc(h, dstRect.Top); ClipRect.Top := 0; end else begin ClipRect.Top := dstRect.Top; dstRect.Top := 0; end; if h + ClipRect.Top > ClipRect.Bottom then h := ClipRect.Bottom - ClipRect.Top; if h <= 0 then Exit; ClipRect.Right := w; ClipRect.Bottom := h; Result := True; end; procedure SetCopyRegs(Dest, Source: TImageData); asm mov edi, [edx].TImageData.Width cmp edi, [eax].TImageData.Width jbe @@1 mov edi, [eax].TImageData.Width @@1: push edi // ecx = width = min(source.Width, dest.Width) movzx ecx, byte ptr [edx].TImageData.PixelFormat[1] imul ecx, edi add ecx, 7 shr ecx, 3 mov ebx, [edx].TImageData.Stride sub ebx, ecx push ebx // eax = srcOffset = source.Stride - ((PixelBits * width + 7) >> 3) mov esi, [edx].TImageData.Scan0 // esi = source.Scan0 movzx ecx, byte ptr [eax].TImageData.PixelFormat[1] imul ecx, edi add ecx, 7 shr ecx, 3 mov ebx, [eax].TImageData.Stride sub ebx, ecx // ebx = dstOffset = dest.Stride - ((PixelBits * width + 7) >> 3) 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// if (dest.InvertLine != source.InvertLine) dec eax // { imul eax, ecx // edi += ((dest.Height - 1) * dest.Stride) add edi, eax shl ecx, 1 // ebx -= (dest.Stride * 2) sub ebx, ecx // } @@3: pop eax pop ecx end; procedure SwapColors(Colors: PRGBQuad; Count: Integer); asm mov ecx, eax @@Loop: dec edx js @@Exit mov eax, [ecx+edx*4] bswap eax shr eax, 8 mov [ecx+edx*4], eax jmp @@Loop @@Exit: end; function _Infinity: Integer; asm 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 GetInfinity(X: double): Integer; asm fld qword ptr X call _Infinity end; end.
尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
说明:本文于2010.5.20重新修订过。