Delphi图像处理 -- 数据类型及公用过程

 阅读提示

    《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》。

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《Delphi图像处理 -- 文章索引》。

 

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
Delphi11 是最新的 Delphi 版本,而 `delphi-opencv-master` 是一个开源的 Delphi 与 OpenCV 集成的项目,它提供了一组 Delphi 封装的 OpenCV 函数和类,方便 Delphi 开发者使用 OpenCV 进行图像处理和计算机视觉操作。 在 Delphi11 中,你可以通过以下步骤来使用 `delphi-opencv-master`: 1. 首先,你需要下载 `delphi-opencv-master` 项目的源代码,可以从 GitHub 上下载:https://github.com/Laex/Delphi-OpenCV 2. 解压缩下载的源代码,并将其中的 `OpenCV` 文件夹复制到 Delphi11 项目的根目录下。 3. 在 Delphi11 中打开你的项目,然后在菜单栏中选择 `Project` -> `Options` -> `Delphi Compiler` -> `Search Path`,将 OpenCV 文件夹所在的路径添加到搜索路径中。 4. 在 Delphi11 中使用 `uses` 语句引入 `OpenCV_Image` 单元,并调用其中的函数和类来进行图像处理和计算机视觉操作。 例如,以下代码演示了如何使用 `delphi-opencv-master` 进行图像读取和灰度化: ``` uses OpenCV_Image; var img: IplImage; begin // 读取图像 img := LoadImage('lena.jpg'); // 将图像转换为灰度图像 cvCvtColor(img, img, CV_RGB2GRAY); // 显示图像 ShowImage('My Image', img); // 等待用户按下任意键 WaitKey; // 释放图像内存 img.Release; end; ``` 这段代码使用了 `OpenCV_Image` 单元中提供的 `LoadImage` 函数和 `ShowImage` 函数来读取和显示图像,使用了 OpenCV 库中的 `cvCvtColor` 函数将图像转换为灰度图像。你可以根据自己的需要,使用 `delphi-opencv-master` 中提供的其他函数和类来进行更加复杂的图像处理和计算机视觉操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值