Delphi图像处理 -- 图像合成

阅读提示:

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    尽可能保持二者内容一致,可相互对照。

   本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元

 

    在图像处理过程中,图像的合成操作是使用频率最高的,如图像显示、图像拷贝、图像拼接以及的图层拼合叠加等。

    图像合成,其实也就是图像像素颜色的混合,在Photoshop中,颜色混合是个很复杂的东西,不同的混合模式,将产生不同的合成效果,如果将之全部研究透彻,估计就得写一本书。因此,本文只谈谈最基本的图像合成,也就是Photoshop中的正常混合模式。

    只要接触过图像处理的,都知道有个图像像素混合公式:

    1)dstRGB = srcRGB * alpha + dstRGB * (1 - alpha)

    其中,dstRGB为目标图像素值;srcRGB为源图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。

    其实,这个像素混合公式有很大局限性,只适合不含Alpha信息的图像。

    要处理包括带Alpha通道图像(层)的混合,其完整的公式应该是:

    2-1)srcRGB = srcRGB * srcAlpha * alpha / 255      (源图像素预乘转换为PARGB)

    2-2)dstRGB = dstRGB * dstAlpha / 255    (目标图像素预乘转换为PARGB)

    2-3)dstRGB = dstRGB + srcRGB - dstRGB * srcAlpha * alpha / 255    (源图像素值与目标图像素值混合)

    2-4)dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255    (混合后的目标图Alpha通道值)

    2-5)dstRGB = dstRGB * 255 / dstAlpha    (混合后的目标图像素转换为ARGB)

    其中,dstRGB为目标图像素值;srcRGB为源图像素值;dstAlpha为目标图Alpha通道值;srcAlpha为源图Alpha通道值;dstARGB为含Alpha目标图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。

    将公式2中的2-1式代入2-3式,简化可得:

    3-1)dstRGB = dstRGB * dstAlpha / 255

    3-2)dstRGB = dstRGB +  (srcRGB - dstRGB) * srcAlpha * alpha / 255

    3-3)dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255

    3-4)dstRGB = dstRGB * 255 / dstAlpha

    当dstAlpha=srcAlpha=255时,公式3中3-1式、3-3式和3-4式没有意义,3-2式也变化为:

   4)dstRGB = dstRGB +  (srcRGB - dstRGB) * alpha

    不难看出,公式4是公式1的变形。因此,公式1只是公式3(或者公式2)在目标图和源图都不含Alpha信息(或者Alpha=255)情况下的一个特例而已。

    当公式4中的alpha=1时,目标图像素等于源图像素,所以,本文前面说图像拷贝其实也是图像合成的范畴。

    通过上面较详细的分析,可以看出,即使是最基本正常图像混合模式也是很复杂的。其实,上面还不是完整的分析,因为按照目标图Alpha信息、源图Alpha信息以及源图合成比例等三个要素的完全的排列组合,最多可以派生8个公式。

    下面就按正常混合模式的全部8种情况(有2项重合,实际为7种情况)来分别进行代码实现,也可完善和补充上面的文字叙述:

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses Gdiplus, ImageData, Jpeg;

{$R *.dfm}

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, 0
end;

// --> mm7 4 * word = 0
// --> mm6 4 * word = 0x1101
// --> mm5 4 * word = 4
// --> eax source alpha
// --> esi source pixel (ARGB)
// --> edi dest pixel (ARGB)
// <-- eax dest alpha !!!
// <-- mm0 dest pixel
procedure _PARGBMixer(srcAlpha: Integer);
asm
    push      edx
    movd      mm0, [esi]
    movd      mm1, [edi]
    punpcklbw mm0, mm7
    punpcklbw mm1, mm7

    // dest.rgb = dest.rgb * dest.alpha / 255
    movzx     edx, [edi].TARGBQuad.Alpha
    pmullw    mm1, qword ptr ArgbTab[edx*8]
    pmulhuw   mm1, mm6
    paddusw   mm1, mm5
    psrlw     mm1, 4

    // dest.rgb = (dest.rgb * 255 + (source.rgb - dest.rgb) * sourec.alpha) / 255
    psubw     mm0, mm1
    pmullw    mm0, qword ptr ArgbTab[eax*8]
    pmullw    mm1, qword ptr ArgbTab[255*8]
    paddw     mm0, mm1
    pmulhuw   mm0, mm6
    paddusw   mm0, mm5
    psrlw     mm0, 4

    // dest.alpha += (source.alpha - (dest.alpha * source.alpha + 127) / 255)
    push      eax
    add       [esp], edx
    imul      eax, edx
    add       eax, 127
    mul       dword ptr DivTab[255*4]
    pop       eax
    sub       eax, edx

    // 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
    pop       edx
end;

// --> mm7 4 * word = 0
// --> eax source alpha
// --> esi source pixel (ARGB)
// --> edi dest pixel (ARGB)
// <-- mm0 dest pixel (RGB)
procedure _ARGBMixer(srcAlpha: Integer);
asm
    movd      mm0, [esi]
    movd      mm1, [edi]
    punpcklbw mm0, mm7
    punpcklbw mm1, mm7
    psubw     mm0, mm1
    pmullw    mm0, qword ptr ArgbTab[eax*8]
    psllw     mm1, 8
    paddw     mm0, mm1
    psrlw     mm0, 8
    packuswb  mm0, mm0
end;

procedure _DoMixer(var Dest: TImageData; const Source: TImageData; Alpha: Integer);
var
  dstOffset, srcOffset:Integer;
  alphaI: Integer;
  dst: PImageData;
asm
    push      esi
    push      edi
    push      ebx
    mov       dst, eax
    mov       alphaI, ecx
    shr       ecx, 8
    movzx     ebx, [eax].TImageData.AlphaFlag
    shl       ebx, 1
    or        ecx, ebx
    movzx     ebx, [edx].TImageData.AlphaFlag
    shl       ebx, 2
    or        ecx, ebx
    push      ecx
    call      _SetCopyRegs
    mov       srcOffset, eax
    pop       eax
    jmp       dword ptr [@@jmpTable+eax*4]

@@jmpTable:
    dd @@mixer0, @@mixer1, @@mixer2, @@mixer3, @@mixer4, @@mixer5, @@mixer6, @@mixer7

// src = 0, dst = 0, alpha = 0
@@mixer0:
    mov       eax, alphaI
    movq      mm2, qword ptr ArgbTab[eax*8]
    mov       eax, srcOffset
    pxor      mm7, mm7
@@yLoop0:
    push      ecx
@@xLoop0:
    movd      mm0, [esi]
    movd      mm1, [edi]
    punpcklbw mm0, mm7
    punpcklbw mm1, mm7
    psubw     mm0, mm1
    pmullw    mm0, mm2
    psllw     mm1, 8
    paddw     mm0, mm1
    psrlw     mm0, 8
    packuswb  mm0, mm0
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, 255
    add       esi, 4
    add       edi, 4
    loop      @@xLoop0
    add       esi, eax
    add       edi, ebx
    pop       ecx
    dec       edx
    jnz       @@yLoop0
    jmp       @@End

// src = 0, dst = 0, alpha = 1
@@mixer1:
// src = 0, dst = 1, alpha = 1
@@mixer3:
    mov       eax, srcOffset
@@yLoop1:
    push      ecx
    rep       movsd
    pop       ecx
    add       esi, eax
    add       edi, ebx
    dec       edx
    jnz       @@yLoop1
    jmp       @@End
    mov       eax, dst
    mov       [eax].TImageData.AlphaFlag, False
    jmp       @@Exit

// src = 0, dst = 1, alpha = 0
@@mixer2:
    call      _SetMixerMM
    mov       eax, alphaI
    imul      eax, 255
    shr       eax, 8
@@yLoop2:
    push      ecx
@@xLoop2:
    push      eax
    call      _PARGBMixer
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, al
    pop       eax
    add       esi, 4
    add       edi, 4
    loop      @@xLoop2
    add       esi, srcOffset
    add       edi, ebx
    pop       ecx
    dec       edx
    jnz       @@yLoop2
    jmp       @@End

// src = 1, dst = 0, alpha = 0
@@mixer4:
    mov       dstOffset, ebx
    mov       ebx, alphaI
    pxor      mm7, mm7
@@yLoop4:
    push      ecx
@@xLoop4:
    movzx     eax, [esi].TARGBQuad.Alpha
    imul      eax, ebx
    shr       eax, 8
    jz        @@Next4
    call      _ARGBMixer
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, 255
@@Next4:
    add       esi, 4
    add       edi, 4
    loop      @@xLoop4
    add       esi, srcOffset
    add       edi, dstOffset
    pop       ecx
    dec       edx
    jnz       @@yLoop4
    jmp       @@End

// src = 1, dst = 0, alpha = 1
@@mixer5:
    pxor      mm7, mm7
@@yLoop5:
    push      ecx
@@xLoop5:
    movzx     eax, [esi].TARGBQuad.Alpha
    call      _ARGBMixer
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, 255
    add       esi, 4
    add       edi, 4
    loop      @@xLoop5
    add       esi, srcOffset
    add       edi, ebx
    pop       ecx
    dec       edx
    jnz       @@yLoop5
    jmp       @@End

// src = 1, dst = 1, alpha = 0
@@mixer6:
    mov       dstOffset, ebx
    mov       ebx, alphaI
    call      _SetMixerMM
@@yLoop6:
    push      ecx
@@xLoop6:
    movzx     eax, [esi].TARGBQuad.Alpha
    imul      eax, ebx
    shr       eax, 8
    jz        @@Next6
    call      _PARGBMixer
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, al
@@Next6:
    add       esi, 4
    add       edi, 4
    loop      @@xLoop6
    add       esi, srcOffset
    add       edi, dstOffset
    pop       ecx
    dec       edx
    jnz       @@yLoop6
    jmp       @@End

// src = 1, dst = 1, alpha = 1
@@mixer7:
    call      _SetMixerMM
@@yLoop7:
    push      ecx
@@xLoop7:
    movzx     eax, [esi].TARGBQuad.Alpha
    call      _PARGBMixer
    movd      [edi], mm0
    mov       [edi].TARGBQuad.Alpha, al
    add       esi, 4
    add       edi, 4
    loop      @@xLoop7
    add       esi, srcOffset
    add       edi, ebx
    pop       ecx
    dec       edx
    jnz       @@yLoop7

@@End:
    emms
@@Exit:
    pop       ebx
    pop       edi
    pop       esi
end;

procedure ImageMixer(var Dest: TImageData; const Source: TImageData; Alpha: Single);
var
  alphaI: Integer;
begin
  alphaI := Round(Alpha * 256);
  if alphaI < 0 then Exit;
  if alphaI > 256 then alphaI := 256;
  _DoMixer(Dest, Source, alphaI);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  source, dest: TGpBitmap;
  g: TGpGraphics;
  src, dst: TImageData;
begin
  source := TGpBitmap.Create('..\..\media\Apple.png');
  dest := TGpBitmap.Create('..\..\media\xmas_011.png');
  g := TGpGraphics.Create(Canvas.Handle);
  g.DrawImage(dest, 0, 0);
  g.DrawImage(source, dest.Width, 0);
  src := LockGpBitmap(source);
  dst := LockGpBitmap(dest);
  ImageMixer(dst, src, 0.75);
  UnlockGpBitmap(dest, dst);
  UnlockGpBitmap(source, src);
  g.DrawImage(dest, dst.Width + src.Width, 0);
  g.Free;
  dest.Free;
  source.Free;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  source: TGpBitmap;
  dest: TBitmap;
  tmp: TJpegImage;
  g: TGpGraphics;
  src, dst: TImageData;
begin
  dest := TBitmap.Create;
  tmp := TJpegImage.Create;
  tmp.LoadFromFile('..\..\media\IMG_9440_mf.jpg');
  dest.Assign(tmp);
  tmp.Free;
  source := TGpBitmap.Create('..\..\media\xmas_011.png');
  g := TGpGraphics.Create(Canvas.Handle);
  Canvas.Draw(0, 0, dest);
  g.DrawImage(source, dest.Width, 0);
  dst := GetBitmapData(dest);
  src := LockGpBitmap(source);
  ImageMixer(dst, src, 1);
  UnlockGpBitmap(source, src);
  Canvas.Draw(dst.Width + src.Width, 0, dest);
  g.Free;
  dest.Free;
  source.Free;
end;

end.

    上面是一个完整Delphi程序,在_DoMixer过程中,定义了7个图像合成子过程,以满足全部8种排列组合的图像合并。除了@@Mixer0和@@Mixer1子过程外,其余子过程分别调用了 _ARGBMixer和_PARGBMixer过程,其实@@Mixer0中的处理代码与 _ARGBMixer过程基本是相同的,也就是说,7个子过程分别使用了三种处理方式:

    一是@@Mixer1的完全拷贝方式;

    二是目标图不含Alpha信息,将源图按像素的Alpha信息或者给定的不透明度混合到目标图的_ARGBMixer过程,该过程使用的是前面公式1或公式4的简化公式。

    三是目标图为带Alpha信息的处理形式 _PARGBMixer,该过程使用前面的公式3。

    如果嫌7个子过程太多,也可简化代码,只保留满足上述3种情况的子过程,即@@Mixer1、@@Mixer4和@@Mixer6,对处理效率影响不大。

    上面程序中包含2个例子,前一个是用GDI+位图打开2张png图片后进行处理,这个处理调用的是@@Mixer6。运行效果截图如下:

    后一个例子是对GDI+位图和VCL位图的混合处理,其中VCL位图是JPEG格式文件,这是没有Alpha信息的,这个例子运行效果截图如下:

 

    《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。

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

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

 

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值