Delphi影像處理 — 平面幾何變換(上)

來源:互聯網
上載者:User

閱讀提示:

    《Delphi影像處理》系列以效率為側重點,一般代碼為PASCAL,核心代碼採用BASM。

    《C++影像處理》系列以代碼清晰,可讀性為主,全部使用C++代碼。

    儘可能保持二者內容一致,可相互對照。

    本文代碼必須包括文章《Delphi影像處理 -- 資料類型及公用過程》中的ImageData.pas單元和《Delphi影像處理 -- 平面幾何變換類》TransformMatrix.pas單元。

 

    在《Delphi影像處理 -- 平面幾何變換類》一文中,介紹了映像平面幾何變換類TTransformMatrix,並寫了一個簡單的臨近插值法映像幾何變換函數Transform,用於測試。很顯然,Transform函數產生的變換映像不僅品質較差,而且也不具備通用性,只能作為一個實現映像幾何變換的架構。

    本文擬採用臨近插值法、雙線性插值法和雙立方插值法等三種插值方式,來實現較完整、通用的圖形映像平面幾何變換。三種插值過程程式碼封裝含在《Delphi影像處理 -- 平面幾何變換類》一文中,本文代碼不包括TTransformMatrix類和三種插值過程。

procedure SetMixerMM;asm    pxor      mm7, mm7    mov       eax, 1011h    movd      mm6, eax    pshufw    mm6, mm6, 0    mov       eax, 8    movd      mm5, eax    pshufw    mm5, mm5, 0end;procedure MixerColor;asm    cmp       eax, 255    jb        @@ArgbMix    movd      [edi], xmm0    ret@@ArgbMix:    movdq2q   mm0, xmm0    movd      mm1, [edi]    punpcklbw mm0, mm7    punpcklbw mm1, mm7    pshufw    mm2, mm0, 255    movzx     eax, [edi].TARGBQuad.Alpha    cmp       eax, 255    jne       @@PArgbMix    // dest.argb = dest.argb + source.argb - dest.argb * source.alpha / 255    paddw     mm0, mm1    pmullw    mm1, mm2    pmulhuw   mm1, mm6    paddusw   mm1, mm5    psrlw     mm1, 4    psubsw    mm0, mm1    packuswb  mm0, mm0    movd      [edi], mm0    ret@@PArgbMix:    // dest.rgb = dest.rgb * dest.alpha / 255    pshufw    mm3, mm1, 255    pmullw    mm1, mm3    pmulhuw   mm1, mm6    paddusw   mm1, mm5    psrlw     mm1, 4    pinsrw    mm1, eax, 3    // dest.argb = dest.argb + source.argb - dest.argb * sourec.alpha / 255    paddw     mm0, mm1    pmullw    mm1, mm2    pmulhuw   mm1, mm6    paddusw   mm1, mm5    psrlw     mm1, 4    psubsw    mm0, mm1    pextrw    eax, mm0, 3    // dest.rgb = dest.rgb * 255 / dest.alpha    movq      mm1, mm0    psllw     mm0, 8    psubw     mm0, mm1    pmulhuw   mm0, qword ptr MMDivTab[eax*8]    packuswb  mm0, mm7    movd      [edi], mm0    mov       [edi].TARGBQuad.Alpha, alend;procedure CopyInterpolateData(var Deat: TImageData; const Source: TImageData; Alpha: Integer);asm    push      esi    push      edi    push      ebx    cmp       ecx, 256    jne       @@CvtPArgb    cmp       [edx].TImageData.AlphaFlag, True    je        @@CvtPArgb    call      _SetCopyRegs@@CpyLoop:    push      ecx    rep       movsd    pop       ecx    add       esi, eax    add       edi, ebx    dec       edx    jne       @@CpyLoop    jmp       @@Exit@@CvtPArgb:    push      ebp    push      ecx    call      _SetCopyRegs    mov       ebp, eax    call      SetMixerMM    pop       eax    movd      mm2, eax    pshufw    mm2, mm2, 0@@yLoop:    push      ecx@@xLoop:    movd      mm0, [esi]    punpcklbw mm0, mm7    pshufw    mm1, mm0, 255    pmullw    mm1, mm2    psrlw     mm1, 8      // alpha0 = Source.Alpha * Alpha / 256    pmullw    mm0, mm1    // Source.RGB = (Source.RGB * alpha0 + 127) / 255    pmulhuw   mm0, mm6    paddusw   mm0, mm5    psrlw     mm0, 4    packuswb  mm0, mm7    movd      eax, mm1    movd      [edi], mm0    mov       [edi].TARGBQuad.Alpha, al    add       esi, 4    add       edi, 4    loop      @@xLoop    add       esi, ebp    add       edi, ebx    pop       ecx    dec       edx    jnz       @@yLoop    pop       ebp    emms@@Exit:    pop       ebx    pop       edi    pop       esiend;procedure FillBorder(var Data: TImageData; Radius: Integer; FillFlag: Integer);asm    push    ebp    push    esi    push    edi    push    ebx    test    ecx, 0fffh    jne     @@yFill    push    eax    push    ecx    mov     edi, [eax].TImageData.Stride    imul    edi, edx    add  edi, [eax].TImageData.Scan0    mov     ebp, [eax].TImageData.Width    mov     ebx, [eax].TImageData.Height    mov     esi, edx    shl     esi, 1    sub     ebx, esi    sub     ebp, esi    shl     ebp, 2    shl     esi, 1@@cLoop:    mov     eax, [edi+esi]    mov  ecx, edx    rep     stosd    add     edi, ebp    mov     eax, [edi-4]    mov     ecx, edx    rep  stosd    dec     ebx    jnz     @@cLoop    pop     ecx    pop     eax@@yFill:    test    ecx, 0fff0000h    jne     @@Exit    mov     ebp, [eax].TImageData.Width    mov  edi, [eax].TImageData.Scan0    mov     esi, [eax].TImageData.Stride    imul    esi, edx    add     esi, edi    push    edx@@tLoop:    push    esi    mov     ecx, ebp    rep     movsd    pop     esi    dec     edx    jnz     @@tLoop    pop     edx    mov     ebx, [eax].TImageData.Height    sub     ebx, edx    sub     ebx, edx    imul    ebx, [eax].TImageData.Stride    add     edi, ebx    mov     esi, edi    sub     esi, [eax].TImageData.Stride@@bLoop:    mov     ecx, ebp    rep     movsd    dec     edx    jnz     @@bLoop@@Exit:    pop     ebx    pop     edi    pop     esi    pop     ebpend;type  TInterpolateProc = procedure;  TElementsI = array[0..5] of Integer;procedure _DoTransform(var Dest: TImageData; const Soutce: TImageData;  const Elements: TElementsI; Radius: Integer; ipProc: TInterpolateProc);var  im22, im21, im12, im11: Integer;  up, xDown, yDown: Integer;  width, height, dstOffset: Integer;  scan0: Pointer;asm    push    esi    push    edi    push    ebx    mov     ebx, [ecx]    mov     im11, ebx    mov     ebx, [ecx+4]    mov     im12, ebx    mov     ebx, [ecx+8]    mov     im21, ebx    mov     ebx, [ecx+12]    mov     im22, ebx    push    dword ptr[ecx+16]    push    dword ptr[ecx+20]    mov     esi, Radius    mov     ebx, [edx].TImageData.Width    mov     edi, [edx].TImageData.Height    sub     ebx, esi    sub     edi, esi    shl     ebx, 12    shl     edi, 12    mov     xDown, ebx          // xDown = (Source.Width - radius) * 4096    mov     yDown, edi          // yDown = (Source.Height - radius) * 4096    mov     ebx, [eax].TImageData.Height    mov     height, ebx         // height = dest.Height    mov     ebx, [eax].TImageData.Width    mov     width, ebx          // width = dest.Width    shl     ebx, 2    neg     ebx    add     ebx, [eax].TImageData.Stride    mov     dstOffset, ebx      // dstOffset = dest.Stride - dest.Width * 4    mov     edi, [eax].TImageData.Scan0    mov     ebx, [edx].TImageData.Scan0    mov     scan0, ebx    mov     ebx, [edx].TImageData.Stride    pop     ecx                 // ys    pop     edx                 // xs    shl     esi, 11    mov     up, esi             // up = radius * 2048    add     esi, 800h    add     edx, esi            // xs += (up + 2048)    add     ecx, esi            // ys += (up + 2048)    pxor    xmm7, xmm7    mov     eax, 40004h    movd    xmm6, eax    pshufd  xmm6, xmm6, 0    call    SetMixerMM@@yLoop:    push    ecx                 // for (pd = (ARGB*)Dest.Scan0; height > 0; height --)    push    edx                 // {    push    width               //   x = xs@@xLoop:                        //   y = ys    cmp     ecx, up             //   for (w = width; w > 0; w --)    jl      @@Next              //   {    cmp     ecx, yDown    jge     @@Next                  cmp     edx, up    jl      @@Next    cmp     edx, xDown          //     if (y >= up && y < yDown && x >= up && x < xDown)    jge     @@Next              //     {    mov     esi, ecx            //       y0 = y / 4096    mov     eax, edx            //       x0 = x / 4096    sar     esi, 12    sar     eax, 12    imul    esi, ebx    shl     eax, 2    add     esi, eax    add     esi, scan0          //       esi = Source.Scan0 + x0 * 4 + y0 * Source.Stride    call    ipProc    movd    eax, xmm0           //       xmm0 = eax = ipProc(src, x, y, esi)    shr     eax, 24    jz      @@Next    call    MixerColor          //       if (eax >> 24) MixerColor(pd, mm0, eax)@@Next:    add     edi, 4              //       pd ++    add     edx, im11           //       x += im11    add     ecx, im12           //       y += im12    dec     width               //     }    jnz     @@xLoop    pop     width    pop     edx    pop     ecx    add     edi, dstOffset      //   (LPBYTE)pd += dstOffset    add     edx, im21           //   xs += im21    add     ecx, im22           //   ys += im22    dec     height              // }    jnz     @@yLoop    emms@@Exit:    pop     ebx    pop     edi    pop     esiend;// 擷取插值過程和擴充半徑function GetInterpolateProc(const Data: TImageData; var Proc: TInterpolateProc): Integer;const  procs: array[TInterpolateMode] of TInterpolateProc =    (_GetBilinearColor, _GetNearColor, _GetBilinearColor, _GetBicubicColor);  radius: array[TInterpolateMode] of Integer = (2, 1, 2, 4);begin  Proc := procs[Data.IpMode];  Result := radius[Data.IpMode];end;// 按Matrix計算目標和源映像幾何變換的裁剪矩形function _GetTransformParams(dstWidth, dstHeight, srcWidth, srcHeight: Integer;  var Matrix: TTransformMatrix; var dst, src: TRect): Boolean;  function CalcRectI(Width, height: Integer; var r: TRect): Boolean;  begin    Inc(r.Right, r.Left);    Inc(r.Bottom, r.Top);    if r.Right > Width then r.Right := Width;    if r.Bottom > Height then r.Bottom := Height;    if r.Left > 0 then Dec(r.Right, r.Left) else r.Left := 0;    if r.Top > 0 then Dec(r.Bottom, r.Top) else r.Top := 0;    Result := (r.Right > 0) and (r.Bottom > 0);  end;var  fx, fy, fwidth, fheight: Single;begin  Matrix.GetTransformSize(srcWidth, srcHeight, fx, fy, fwidth, fheight);  Matrix.Invert;  dst := Rect(Trunc(fx), Trunc(fy), _Infinity(fwidth), _Infinity(fheight));  Result := CalcRectI(dstWidth, dstHeight, dst);  if not Result then Exit;  if (fx > 0) or (fy > 0) then  begin    if fx < 0 then fx := 0    else if (fy < 0) then fy := 0;    Matrix.Translate(fx, fy);  end;  Matrix.GetTransformSize(dst.Right, dst.Bottom, fx, fy, fwidth, fheight);  src := Rect(Trunc(fx), Trunc(fy), _Infinity(fwidth), _Infinity(fheight));  Result := CalcRectI(srcWidth, srcHeight, src);  if not Result then Exit;  if fx > 0 then Matrix.OffsetX := Matrix.OffsetX - fx;  if fy > 0 then Matrix.OffsetY := Matrix.OffsetY - fy;end;procedure ImageTransform(var Dest: TImageData; x, y: Integer;  const Source: TImageData; const Matrix: TTransformMatrix; Alpha: Single = 1);var  m: TTransformMatrix;  e: TMatrixElements;  eI: TElementsI;  dstR, srcR: TRect;  i, alphaI, radius: Integer;  proc: TInterpolateProc;  dst, src, tmp, sub: TImageData;begin  alphaI := Round(Alpha * 256);  if alphaI <= 0 then Exit;  if alphaI > 256 then alphaI := 256;  m := TTransformMatrix.Create(Matrix);  try    m.OffsetX := m.OffsetX + x;    m.OffsetY := m.OffsetY + y;    if not _GetTransformParams(Dest.Width, Dest.Height,      Source.Width, Source.Height, m, dstR, srcR) then  Exit;    e := m.Elements;    for i := 0 to 5 do      eI[i] := Round(e.Elements[i] * 4096);    radius := GetInterpolateProc(Source, proc);    dst := GetSubImageData(Dest, dstR.Left, dstR.Top, dstR.Right, dstR.Bottom);    tmp := GetSubImageData(Source, srcR.Left, srcR.Top, srcR.Right, srcR.Bottom);    src := NewImageData(tmp.Width + radius * 2, tmp.Height + radius * 2);    try      src.AlphaFlag := Source.AlphaFlag;      sub := GetSubImageData(src, radius, radius, tmp.Width, tmp.Height);      CopyInterpolateData(sub, tmp, alphaI);      FillBorder(src, radius, (eI[1] shl 16) or (eI[2] and $ffff));      _DoTransform(dst, src, eI, radius, proc);      if (eI[1] = 0) and (eI[2] = 0) and (alphaI = 256) and not Source.AlphaFlag        and (Dest.Width = dst.Width) and (Dest.Height = dst.Height) then        Dest.AlphaFlag := False;    finally      FreeImageData(src);    end;  finally    m.Free;  end;end;

    代碼很長,但核心代碼還是_DoTransform過程和《Delphi影像處理 -- 平面幾何變換類》中的三個插值過程。幾何變換過程原理亦可參見該文。

    有關插值邊界的處理,一般有2種辦法,一是在插值過程中進行判斷座標是否超界而作相應的處理,二是捨棄邊界部分,對於後者我是不主張的,因為那樣是不完整的處理。我採用了擴充邊框的辦法進行邊框插值處理,這樣一來,雖然多了一道拷貝過程,卻少了具體插值過程的座標判斷,二者抵消,插值速度應該是差不多的(據我測試,擴充邊框辦法在映像放大和旋轉處理中速度還是略快一些),但是簡化了插值代碼。

    下面是映像幾何變換的例子: 

procedure TForm1.Button1Click(Sender: TObject);var  bmp, newBmp: TBitmap;  jpg: TJPEGImage;  matrix: TTransformMatrix;  source, dest: TImageData;  r: TRect;begin  bmp := TBitmap.Create;  matrix := TTransformMatrix.Create;  try    jpg := TJPEGImage.Create;    try      jpg.LoadFromFile('..\..\media\IMG_9440_mf.jpg');      bmp.Assign(jpg);    finally      jpg.Free;    end;    matrix.Scale(1.2, 1.2);    matrix.Shear(0.3, 0.3);    r := matrix.GetTransformRect(bmp.Width, bmp.Height);    if (r.Right <= 0) or (r.Bottom <= 0) then Exit;    source := GetBitmapData(bmp);    newBmp := TBitmap.Create;    try      newBmp.PixelFormat := pf32bit;      newBmp.Width := r.Right;      newBmp.Height := r.Bottom;      dest := GetBitmapData(newBmp);      SetInterpolateMode(source, imBicubic);      ImageTransform(dest, 0, 0, source, matrix);      Canvas.Draw(0, 0, newBmp);    finally      newBmp.Free;    end;  finally    matrix.Free;    bmp.Free;  end;end;

    代碼中的SetInterpolateMode(source, imBicubic);語句為設定雙立方插值方式,該函數也在《Delphi影像處理 -- 資料類型及公用過程》的ImageData.pas單元。

    運行效果如下:

    運行效果還是很好的,特別是邊界效果。

    對於含Alpha資訊的映像,必須作自乘預先處理,這在CopyAlphaData函數中是自動判斷後轉換的。比較下面同一張png圖片2種方式的線性插值幾何變換處理效果,左邊是使用自乘預先處理後的插值處理效果,右邊是沒做自乘預先處理後的插值處理效果:

    《Delphi影像處理》系列使用GDI+單元和說明見文章《GDI+ for VCL基礎 -- GDI+ 與 VCL》。

    因水平有限,錯誤在所難免,歡迎指正和指導。郵箱地址:maozefa@hotmail.com

    這裡可訪問《Delphi影像處理 -- 文章索引》。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.