Delphi图像处理 -- 图像黑白调整

阅读提示

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

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

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

    本文代码必须包括《C++图像处理 -- 数据类型及公用函数》文章中的BmpData.h头文件。

 

    Photoshop CS的图像黑白调整功能,是通过对红、黄、绿、青、蓝和洋红等6种颜色的比例调节来完成的。能更精细地将彩色图片转换为高质量的黑白照片。

    Photoshop CS图像黑白调整功能的计算公式为:

    gray = (max - mid) * ratio_max + (mid - min) * ratio_max_mid + min

    公式中:gray为像素灰度值,max、mid和min分别为图像像素R、G、B分量颜色的最大值、中间值和最小值,ratio_max为max所代表的分量颜色(单色)比率,ratio_max_mid则为max与mid两种分量颜色所形成的复色比率。

    用上面公式计算的灰度值,与我们通常所用的灰度计算方法有很大不同,通常所用的灰度公式为,是直接将颜色各分量乘以相应的比率相加而成,如:gray = 0.3R + 0.59G + 0.11B,而上面公式则是在最小值代表的颜色分量基础上,用最大值与最小值之差表示单色部分(红、绿、蓝),用中间值与最小值之差表示复色部分(黄、青、洋红),将单色和复色部分分别乘以与之对应的比率后相加,再加上最小值而得到灰度值。对于每个单独的像素来说,计算灰度值只需要用到上述6种颜色比率中的2种即可。在计算过程中可根据像素RGB相互关系选择对应的单色和复色比率,如像素RGB的大小关系为R>G>B,单色比率选最大值R红色,复色比率则为最大值R与中间值G所形成的复色黄色。

    用程序代码实现上面的灰度计算公式并不复杂,难点还是前面所说的根据像素RGB相互关系选择对应的单色和复色比率

    Photoshop图像黑白调整功能中还有一个附加的着色功能,该功能实际上是利用图层颜色混合模式原理实现的。有关图层颜色混合模式的原理和实现在C++图像处理中有多篇文章进行了介绍,在《Delphi图像处理 -- 图像颜色混合》中也有实现代码,可以参考这些文章。

    下面是用Delphi实现图像黑白调整的BASM代码,包括灰度图象着色功能代码:

type
  TBWParams = array[0..5] of Single;

// eax,edx,ecx=r,g,b esi,sdi,ebx=rIndex,gIndex,bIndex
procedure CompareRgb;
asm
    cmp     eax, ecx
    jae     @@1
    xchg    eax, ecx
    xchg    esi, ebx
@@1:
    cmp     eax, edx
    jae     @@2
    xchg    eax, edx
    xchg    esi, edi
@@2:
    cmp     ecx, edx
    jbe     @@3
    xchg    ecx, edx
    xchg    ebx, edi
@@3:
end;

// in: esi=srcPixel,edi=dstPixel,eax=gray
// out: [edi]=mixerColor
procedure ColorMix;
const GrayConst: array[0..2] of Integer = (113, 604, 307);
var
  gray, max_min: LongWord;
asm
    push    esi
    push    edi
    mov     gray, eax
    movzx   ecx, [esi].TARGBQuad.Blue
    movzx   edx, [esi].TARGBQuad.Green
    movzx   eax, [esi].TARGBQuad.Red
    xor     ebx, ebx            // blue  index
    mov     edi, 1              // green index
    mov     esi, 2              // red   index
    call    CompareRgb          // CompareRgb(red, green, blue)
    sub     eax, ecx            // max - min
    jnz     @@4
    pop     edi
    mov     eax, gray
    mov     [edi].TARGBQuad.Blue, al
    mov     [edi].TARGBQuad.Green, al
    mov     [edi].TARGBQuad.Red, al
    jmp     @@Exit
@@4:
    sub     edx, ecx            // mid - min
    mov     max_min, eax

    mov     ecx, eax
    sub     eax, edx
    imul    eax, GrayConst[edi*4].Integer
    imul    ecx, GrayConst[ebx*4].Integer
    add     eax, ecx
    add     eax, 512            // nMax = gray +
    shr     eax, 10             //   (max_min - mid_min) * grayConst[midIndex] +
    add     eax, gray           //   max_min * grayConst[minIndex]
    cmp     eax, 255
    ja      @@5
    mov     ecx, eax
    sub     ecx, max_min        // nMin = nMax - max_min
    js      @@6
    add     edx, ecx            // nMid = nMin + mid_min
    jmp     @@8

@@5:// nMax > 255
    shl     edx, 10             // hueCoef = (mid_min << 10) / max_min
    mov     eax, max_min
    xchg    eax, edx
    mul     DivTab[edx*4].Integer
    push    edx
    mov     ecx, GrayConst[edi*4].Integer
    imul    edx, ecx
    shr     edx, 10             // v0 = (ys[mid.index] * hueCoef) >> 10
    add     ecx, GrayConst[ebx*4].Integer
    sub     ecx, edx            // v1 = ys[mid.index] + ys[min.index] - v0
    add     edx, GrayConst[esi*4].Integer
    mov     eax, edx
    shl     edx, 8
    sub     edx, eax
    mov     eax, gray
    shl     eax, 10
    sub     eax, edx
    mov     edx, ecx
    shr     edx, 1
    add     eax, edx
    mul     DivTab[ecx*4].Integer
    mov     ecx, edx            // nMin = ((gray << 10) - (ys[max.index] + v0) *
    pop     eax                 //   255 + (v1 >> 1)) / v1
    xor     edx, 255
    imul    edx, eax
    add     edx, 512
    shr     edx, 10
    add     edx, ecx            // nMid = nMin + (((255 ^ newMin) * hueCoef + 512) >> 10)
    mov     eax, 255            // nMax = 255
    jmp     @@8

@@6:// nMin < 0
    shl     edx, 10             // hueCoef = (mid_min << 10) / max_min
    mov     eax, max_min
    xchg    eax, edx
    mul     DivTab[edx*4].Integer
    push    edx
    imul    edx, GrayConst[edi*4].Integer
    add     edx, 512
    shr     edx, 10             // tmp = ys[max.index] + ((ys[mid.index] * hueCoef + 512) >> 10)
    add     edx, GrayConst[esi*4].Integer
    mov     eax, gray
    shl     eax, 10
    mov     ecx, edx
    shr     edx, 1
    add     eax, edx
    mul     DivTab[ecx*4].Integer
    mov     eax, edx            // nMax = ((gray << 10) + (tmp >> 1)) / tmp
    pop     edx
    imul    edx, eax
    add     edx, 512
    shr     edx, 10             // nMid = (nMax * hueCoef + 512) >> 10
    mov     ecx, 1              // nMin = 1
@@8:
    mov     ah, dl
    pop     edx
    mov     [edx+esi], al
    mov     [edx+edi], ah
    mov     [edx+ebx], cl
    mov     edi, edx
@@Exit:
    pop     esi
end;

// 图像黑白调整。
// 调整参数bwParams为元素数等于6的数组指针,分别为红,黄,绿,青,蓝,洋红
procedure ImageBlackWhite(var Dest: TImageData; const Source: TImageData;
  const bwParams: TBWParams); overload;
var
  i: Integer;
  Width, Height: Integer;
  dstOffset, srcOffset: Integer;
  Params: array[0..5] of Integer;
begin
  for i := 0 to 5 do          // 浮点黑白参数转换为整数并交换青色与洋红色位置
    Params[i] := Round(bwParams[i] * 1024);
  Params[3] := Params[3] xor Params[5];
  Params[5] := Params[5] xor Params[3];
  Params[3] := Params[3] xor Params[5];
  asm
    push    esi
    push    edi
    push    ebx
    mov     eax, Dest
    mov     edx, Source
    call    _SetCopyRegs
    mov     Width, ecx
    mov     Height, edx
    mov     dstOffset, ebx
    mov     srcOffset, eax
@@yLoop:
    push    Width
@@xLoop:
    push    esi
    push    edi
    movzx   ecx, [esi].TARGBQuad.Blue
    movzx   edx, [esi].TARGBQuad.Green
    movzx   eax, [esi].TARGBQuad.Red
    mov     ebx, 4          // blue  index
    mov     edi, 2          // green index
    xor     esi, esi        // red   index
    call    CompareRgb      // CompareRgb(red, green, blue)
    sub     eax, edx        // max - mid
    sub     edx, ecx        // mid - min
    add     edi, esi
    dec     edi
    imul    eax, Params[esi*4].Integer
    imul    edx, Params[edi*4].Integer
    add     eax, edx        // gray = (((max - mid) * params[maxIndex] +
    add     eax, 512        //          (mid - min) * params[maxIndex + midIndex - 1] +
    sar     eax, 10         //           512) >> 10) + min
    add     eax, ecx
    jns     @@1
    xor     eax, eax
    jmp     @@2
@@1:
    cmp     eax, 255
    jb      @@2
    mov     eax, 255
@@2:
    pop     edi
    pop     esi
    mov     [edi].TARGBQuad.Blue, al
    mov     [edi].TARGBQuad.Green, al
    mov     [edi].TARGBQuad.Red, al
    add     edi, 4
    add     esi, 4
    dec     Width
    jne     @@xLoop
    pop     Width
    add     edi, dstOffset
    add     esi, srcOffset
    dec     Height
    jnz     @@yLoop
    pop     ebx
    pop     edi
    pop     esi
  end;
end;

// 图像黑白调整。
// 调整参数bwParams为元素数等于6的数组指针,分别为红,黄,绿,青,蓝,洋红
procedure ImageBlackWhite(var Data: TImageData; const bwParams: TBWParams); overload;
begin
  ImageBlackWhite(Data, Data, bwParams);
end;

// 灰度图像染色。
procedure ImageArgbTint(var Data: TImageData; Argb: LongWord);
var
  Width, Height: Integer;
  dataOffset: Integer;
  mixTable: array[0..255] of TARGBQuad;
asm
    push      esi
    push      edi
    push      ebx
    push      eax
    push      edx
    // 建立灰度染色表mixTable
    shr       edx, 24
    cvtsi2ss  xmm6, edx
    mov       edx, 255
    cvtsi2ss  xmm0, edx
    divss     xmm6, xmm0
    pshufd    xmm6, xmm6, 0 // xmm6 = Argb.Alpha / 255
    mov       edx, 1
    movd      xmm5, edx
    pshufd    xmm5, xmm5, 0 // xmm5 = 1
    pxor      xmm4, xmm4    // xmm4 = 0
    pxor      xmm7, xmm7
    lea       esi, [esp]
    lea       edi, mixTable
    xor       eax, eax
@@CalcMixTable:
    push      eax
    //   mixTable[i].rgb = i + ColorMix(&Argb, i) * Argb.Alpha / 255
    call      ColorMix
    movd      xmm0, [edi]   // xmm0 = ColorMix(&Argb, i)
    punpcklbw xmm0, xmm7
    punpcklwd xmm0, xmm7
    psubd     xmm0, xmm4    // xmm0 -= i
    cvtdq2ps  xmm0, xmm0
    mulps     xmm0, xmm6    // xmm0 = xmm0 * Argb.Alpha / 255
    cvtps2dq  xmm0, xmm0
    paddd     xmm0, xmm4    // xmm0 += i
    paddd     xmm4, xmm5    // i ++
    packssdw  xmm0, xmm7
    packuswb  xmm0, xmm7
    movd      [edi], xmm0   // mixTable[i] = xmm0
    pop       eax
    add       edi, 4
    inc       eax
    cmp       eax, 256
    jb        @@CalcMixTable
    pop       edx
    pop       eax
    call      _SetDataRegs
    lea       eax, mixTable
@@yLoop:
    push      ecx
@@xLoop:
    movzx     esi, [edi].TARGBQuad.Blue
    lea       esi, [eax+esi*4]
    movsw
    movsb                   // pd.RGB = mixTable[pd.Blue]
    inc       edi
    loop      @@xLoop
    pop       ecx
    add       edi, ebx
    dec       edx
    jnz       @@yLoop
    pop       ebx
    pop       edi
    pop       esi
end;


// 灰度图像染色。
procedure ImageColorTint(var Data: TImageData; Color: TColor);
asm
    bswap     edx
    shr       edx, 8
    or        edx, 0ff000000h
    call      ImageArgbTint
end;

    代码中有2个图像黑白调整过程,分别是图像数据拷贝形式和自处理的黑白调整过程。染色过程也有2个,分别对应于ARGB颜色和VCL颜色TColor的,染色过程采用了颜色表查找方式处理,效率很高,即使用纯pascal代码处理速度也相当的快,如果不考虑颜色的不透明度,过程中的SSE代码可以删除。

    下面是个简单处理PNG图片黑白去色后染色的例子:

procedure TForm1.Button1Click(Sender: TObject);
var
  bmp: TGpBitmap;
  g: TGpGraphics;
  data: TImageData;
begin
  bmp := TGpBitmap.Create('..\..\media\xmas_011.png');
  data := LockGpBitmap(bmp);
  ImageBlackWhite(data, BWColors);
  ImageArgbTint(data, $ffff0000);
  UnlockGpBitmap(bmp, data);
  g := TGpGraphics.Create(PaintBox1.Canvas.Handle);
  g.DrawImage(bmp, 0, 0);
  g.Free;
  bmp.Free;
end;

    运行截图如下:

Delphi图像处理 -- 图像黑白调整_第1张图片

    左边是原图,右边是黑白灰度处理后以红色着色。在这里有必要提示一下,染色过程处理的不只是黑白处理后的图像,任何灰度图象都可以的。

    本来写了一个仿Photoshop黑白调整功能的Delphi程序,但代码较长,而且在《C++图像处理 -- 图像黑白调整应用》一文中已经存在一个相同界面的演示程序,其中还有几张运行界面截图,所以这里就不再贴代码了。有兴趣的朋友可以参见该文。

 

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]

    这里可访问《C++图像处理 -- 文章索引

你可能感兴趣的:(Delphi,GDI+,图像黑白调整,BASM,灰度图象着色)