阅读提示:
《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;
运行截图如下:
左边是原图,右边是黑白灰度处理后以红色着色。在这里有必要提示一下,染色过程处理的不只是黑白处理后的图像,任何灰度图象都可以的。
本来写了一个仿Photoshop黑白调整功能的Delphi程序,但代码较长,而且在《C++图像处理 -- 图像黑白调整应用》一文中已经存在一个相同界面的演示程序,其中还有几张运行界面截图,所以这里就不再贴代码了。有兴趣的朋友可以参见该文。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《C++图像处理 -- 文章索引》。