16位图像Alpha混合的实现(用汇编写的,比MMX还要快)

Alpha 混合的算法很简单,基于下面的公式就可以实现:

D := A * (S - D) / 255 + D

D 是目标图像的像素,

S 是源图像的像素

A 是 Alpha 值, 0 为全透明, 255 为不透明。

 

下面是 16 位 565 格式的混合算法的实现,首先用最简单的方式实现,即逐个像素的处理:

 

// 一次处理一个像素,比较简单,但速度较慢

procedure AlphaBlend656(BmpDst, BmpSrc: TBitmap; Alpha: Byte);

var

  i, j, W, H: Integer;

  pSrc, pDst: PWord;

  wSR, wSG, wSB: Word;

  wDR, wDG, wDB: Word;

begin

  // 确定高宽

  if BmpDst.Width > BmpSrc.Width then

    W := BmpSrc.Width

  else

    W := BmpDst.Width;

  if BmpDst.Height > BmpSrc.Height then

    H := BmpSrc.Height

  else

    H := BmpDst.Height;

 

  for i := 0 to H - 1do

  begin

    pSrc := BmpSrc.ScanLine[i];

    pDst := BmpDst.ScanLine[i];

    for j := 0 to W - 1 do

    begin

      // D := A * (S - D) / 255 + D

 

      wSR := (pSrc^ shr 11);

      wSG := (pSrc^ shr 5) and $3F;

      wSB := pSrc^ and $1F;

 

      wDR := (pDst^ shr 11);

      wDG := (pDst^ shr 5) and $3F;

      wDB := pDst^ and $1F;

 

      pDst^ := (((Alpha * (wSR - wDR) shr 8) + wDR) shl 11) or

               (((Alpha * (wSG - wDG) shr 8) + wDG) shl 5) or

               ((Alpha * (wSB - wDB) shr 8) + wDB);

 

      Inc(pSrc);

      Inc(pDst);

    end;

  end;

end;

 

实现起来很简单,但速度比较慢,其实存在着一次处理两个像素的算法,下面是代码:

 

// 一次处理两个像素 , 所以速度是 AlphaBlend656 的 2 倍

procedure AlphaBlend656Fast(BmpDst, BmpSrc: TBitmap; Alpha: Byte);

var

  i, j, W, H: Integer;

  pSrc, pDst: PWord;

  dwSR, dwSG, dwSB: LongWord;

  dwDR, dwDG, dwDB: LongWord;

  dwAdd64 : LongWord;

  dwAlphaOver4 : LongWord;

  odd: Boolean;

begin

    // 确定高宽

  if BmpDst.Width > BmpSrc.Width then

    W := BmpSrc.Width

  else

    W := BmpDst.Width;

  if BmpDst.Height > BmpSrc.Height then

    H := BmpSrc.Height

  else

    H := BmpDst.Height;

 

  dwAdd64 := 64 or ( 64 shl 16 );

  dwAlphaOver4 := ( Alpha shr 2 ) or ( ( Alpha shr 2 ) shl 16 );

 

  if (W and $01) = 1 then

  begin

    odd := True;

    W := (W - 1) shr 1;

  end

  else begin

    odd := False;

    W := W shr 1;

  end;

 

  for i := 0 to H - 1 do

  begin

    pSrc := BmpSrc.ScanLine[i];

    pDst := BmpDst.ScanLine[i];

    for j := 0 to W - 1 do

    begin

      // D := A * (S - D) / 255 + D

 

      dwSR := (PLongWord(pSrc)^ shr 11) and $001F001F;

      dwSG := (PLongWord(pSrc)^ shr 5) and $003F003F;

      dwSB := PLongWord(pSrc)^ and $001F001F;

 

      dwDR := (PLongWord(pDst)^ shr 11) and $001F001F;

      dwDG := (PLongWord(pDst)^ shr 5) and $003F003F;

      dwDB := PLongWord(pDst)^ and $001F001F;

 

 

      PLongWord(pDst)^ := ((((Alpha * (dwSR + dwAdd64 - dwDR)) shr 8) + dwDR - dwAlphaOver4) and $001F001F) shl 11 or

               ((((Alpha * (dwSG + dwAdd64 - dwDG)) shr 8) + dwDG - dwAlphaOver4 ) and $003F003F) shl 5 or

               (((Alpha * (dwSB + dwAdd64 - dwDB)) shr 8) + dwDB - dwAlphaOver4 ) and $001F001F;

 

      Inc(pSrc, 2);

      Inc(pDst, 2);

    end;

 

    if odd then

    begin

      dwSR := (pSrc^ shr 11);

      dwSG := (pSrc^ shr 5) and $3F;

      dwSB := pSrc^ and $1F;

 

      dwDR := (pDst^ shr 11);

      dwDG := (pDst^ shr 5) and $3F;

      dwDB := pDst^ and $1F;

 

      pDst^ := Word((((Alpha * (dwSR - dwDR) shr 8) + dwDR) shl 11) or

               (((Alpha * (dwSG - dwDG) shr 8) + dwDG) shl 5) or

               ((Alpha * (dwSB - dwDB) shr 8) + dwDB));

 

      Inc(pSrc);

      Inc(pDst);

    end; 

  end;

end;

 

不过这还不够快,基本 MMX 指令的实现可以一次处理 4 个像素,下面是代码:

 

// 利用 MMX 优化指令,一次可以处理 4 个像素,因此速度应该是 AlphaBlend656 的 4 倍

procedure AlphaBlend656MMX(BmpDst, BmpSrc: TBitmap; Alpha: Byte);

var

  i, j, W, H, Leave: Integer;

  pSrc, pDst: PWord;

  MaskR, MaskG, MaskB, Alpha64: Int64;

  wSR, wSG, wSB: Word;

  wDR, wDG, wDB: Word;

begin

  // 确定高宽

  if BmpDst.Width > BmpSrc.Width then

    W := BmpSrc.Width

    else

    W := BmpDst.Width;

  if BmpDst.Height > BmpSrc.Height then

    H := BmpSrc.Height

  else

    H := BmpDst.Height;

 

  Leave := W and 3;             // 剩余的像素

  W := W shr 2;                 // 一次处理 4 个像素,因此取 W 整除 4 的值

 

  // 提取 RGB 通道的掩码

  MaskR := $f800f800f800f800;

  MaskG := $07e007e007e007e0;

  MaskB := $001f001f001f001f;

 

  // Alpha 值扩展到 64 位

  Alpha64 := Alpha;

  Alpha64 := Alpha64 or (Alpha64 shl 16) or (Alpha64 shl 32) or (Alpha64 shl 48);

 

  for i := 0 to H - 1do

  begin

    pSrc := BmpSrc.ScanLine[i];

    pDst := BmpDst.ScanLine[i];

 

    asm

      push    ecx               // 保存寄存器

      mov     ecx, W            // 设宽度

      cmp     ecx, 0            // 宽度是否为 0

      jz      @@exit565         // 如果宽度为 0 ,结束

 

      push    esi

      push    edi

      mov      esi, pSrc         // 开始处理

      mov     edi, pDst

 

    @@blend565_4:

    { mmx 的作用:

      mm0: red target value

      mm1: red source value

      mm2: green target value

      mm3: green source value

      mm4: blue target value

      mm5: blue source value

      mm6: original target pixel

      mm7: original source pixel

 

      D := A * (S - D) / 255 + D

    }

      movq    mm6, [edi]

      movq    mm7, [esi]

 

      movq    mm0, mm6

      pand    mm0, MaskR        // 提取目标的 R 通道

      movq    mm1, mm7

      pand    mm1, MaskR        // 提取源的 R 通道

      psrlw   mm0, 11           // 右移到最低位,便于接下来的计算

      psrlw   mm1, 11

      psubw   mm1, mm0          // SrcRed := SrcRed - DestRed

      pmullw  mm1, Alpha64      // SrcRed := SrcRed * Alpha

      psraw   mm1, 8            // SrcRed := SrcRed div 8

      paddw   mm1, mm0          // SrcRed := SrcRed + DestRed

      psllw   mm1, 11           // 左移回原来的位置,此已经 R 通道混合已经完毕

 

      movq    mm2, mm6

      pand    mm2, MaskG        // 提取目标的 G 通道

      movq    mm3, mm7

      pand    mm3, MaskG        // 提取源的 G 通道

      psrlw   mm2, 5            // 右移到最低位,便于接下来的计算

      psrlw   mm3, 5

      psubw   mm3, mm2          // SrcGreen := SrcGreen - DestGreen

      pmullw  mm3, Alpha64      // SrcGreen := SrcGreen * Alpha

      psraw   mm3, 8            // SrcGreen := SrcGreen div 8

      paddw   mm3, mm2          // SrcGreen := SrcGreen + DestGreen

      psllw   mm3, 5            // 左移回原来的位置,此已经 G 通道混合已经完毕

 

      movq    mm4, mm6

      pand    mm4, MaskB        // 提取目标的 B 通道

      movq    mm5, mm7

      pand    mm5, MaskB        // 提取源的 B 通道

      psubw   mm5, mm4          // SrcBlue := SrcBlue - DestBlue

      pmullw  mm5, Alpha64      // SrcBlue := SrcBlue * Alpha

      psraw   mm5, 8            // SrcBlue := SrcBlue div 8

      paddw   mm5, mm4          // SrcBlue := SrcBlue + DestBlue ,此已经 B 通道混合已经完毕

 

      por     mm1, mm3          // 合成像素

      por     mm1, mm5

      movq    [edi], mm1        // 赋给目标

 

      add     esi, 8            // 下 4 个像素

      add     edi, 8

      dec     ecx

      jnz      @@blend565_4

 

      mov     pSrc, esi

      mov     pDst, edi

      pop     edi

      pop     esi

      emms

 

    @@exit565:

      pop     ecx

    end;

 

    // 处理剩下的像素

    for j := 0 to Leave - 1 do

    begin

      wSR := (pSrc^ shr 11);

      wSG := (pSrc^ shr 5) and $3F;

      wSB := pSrc^ and $1F;

 

      wDR := (pDst^ shr 11);

      wDG := (pDst^ shr 5) and $3F;

      wDB := pDst^ and $1F;

 

      pDst^ := (((Alpha * (wSR - wDR) shr 8) + wDR) shl 11) or

               (((Alpha * (wSG - wDG) shr 8) + wDG) shl 5) or

               ((Alpha * (wSB - wDB) shr 8) + wDB);

 

      Inc(pSrc);

      Inc(pDst);

    end;

  end;

end;

 

下面是这三个函数的速度比较,目标图像是 600*450 的 16 位位图,源图像是 399*532 的 16 位位图,分别进行了 1000 次混合,结果如下:

AlphaBlend656 :           4516

AlphaBlend656Fast :       2562

AlphaBlend656MMX :        1234

没有意外, MMX 版本比普通的快了近 4 倍

 

对于图像处理的优化有两个比较重要的点:

1、 尽量用位移代替乘除。

2、 一次能够同时处理多个像素,利用 MMX 指令可以做到这一点。

 

最后是代码:

https://files.getdropbox.com/u/524963/AlphaBlend16_565.rar

http://blog.csdn.net/linzhengqun/article/details/4269259

posted @ 2016-03-19 00:02  findumars  Views(1026)  Comments(0Edit  收藏  举报