阅读提示:
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元。
Photoshop提供了丰富的图象图层混合模式,其中的颜色混合模式是用下图层图象的亮度与上图层填充颜色或者图象色彩进行混合,形成的结果既有着上图层的色彩,又保留了下层图象的灰度,基于这种混合特性,颜色混合模式常用来对灰度图象进行着色。
在《C++图像处理 -- 图像颜色混合(上)》、《C++图像处理 -- 图像颜色混合(中)》和《C++图像处理 -- 图像颜色混合(下)》等多篇文章中,介绍了图像颜色混合的原理、算法优化和代码完善方法,因此这里不再介绍,直接贴出Delpha实现代码(BASM)和例子。由于BASM汇编码较长,在C++版的颜色混合文章的图像黑白调整和灰度图象染色功能,准备另文发表。
图像颜色混合实现代码:
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;
const
BWDefault: array[0..5] of Integer = (410, 614, 410, 819, 205, 614);
GrayConst: array[0..2] of Integer = (113, 604, 307);
// 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: edi=pixel
// out: eax=bwGray
procedure GetBWGray;
asm
push esi
push edi
movzx ecx, [edi].TARGBQuad.Blue
movzx edx, [edi].TARGBQuad.Green
movzx eax, [edi].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, BWDefault[esi*4].Integer
imul edx, BWDefault[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
pop edi
pop esi
end;
// in: esi=srcPixel,edi=dstPixel,eax=gray
// out: [edi]=mixerColor
procedure ColorMix;
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;
procedure _DoColorMixer(var Dest: TImageData; const Source: TImageData; Alpha: Integer);
var
width, height, dstOffset, srcOffset: Integer;
dst, src: LongWord;
alphaI: Integer;
asm
push esi
push edi
push ebx
mov alphaI, ecx
mov cl, [eax].TImageData.AlphaFlag
mov ch, [edx].TImageData.AlphaFlag
and [edx].TImageData.AlphaFlag, cl
push ecx
call _SetCopyRegs
mov width, ecx
mov height, edx
mov srcOffset, eax
mov dstOffset, ebx
pop ecx
cmp alphaI, 256
jne @@MixerA
cmp ch, TRUE
je @@MixerA
cmp cl, TRUE
je @@MixerB
@@yLoop:
push width
@@xLoop:
call GetBWGray
call ColorMix
add esi, 4
add edi, 4
dec width
jnz @@xLoop
pop width
add esi, srcOffset
add edi, dstOffset
dec height
jnz @@yLoop
jmp @@End
@@MixerA:
cmp cl, True
je @@MixerB
pxor mm7, mm7
@@yLoopA:
push width
@@xLoopA:
movzx eax, [esi].TARGBQuad.Alpha
mul alphaI
shr eax, 8
jz @@NextA
push esi
push edi
push eax
mov eax, [edi]
mov dst, eax
lea edi, dst
call GetBWGray // get dest BlackWhite gray
call ColorMix // dst = source.HS mixer dest.gray
mov esi, edi
pop eax // mixAlpha = source.Alpha * alpha / 256
pop edi
call _ARGBMixer // dest.rgb = dst.rgb mixer dest.rgb
movd [edi], mm0
mov [edi].TARGBQuad.Alpha, 255
pop esi
@@NextA:
add esi, 4
add edi, 4
dec width
jnz @@xLoopA
pop width
add esi, srcOffset
add edi, dstOffset
dec height
jnz @@yLoopA
emms
jmp @@End
@@MixerB:
call _SetMixerMM
@@yLoopB:
push width
@@xLoopB:
movzx eax, [esi].TARGBQuad.Alpha
mul alphaI
shr eax, 8
jz @@NextB
test [edi].TARGBQuad.Alpha, 0ffh
jnz @@B_1
mov eax, [esi]
mov [edi], eax
jmp @@NextB
@@B_1:
push esi
push edi
push edi
push eax
mov eax, [esi]
mov src, eax
mov eax, [edi]
mov dst, eax
lea edi, dst
call GetBWGray // get dest BlackWhite gray
call ColorMix // dst = source.HS mixer dest.gray
mov esi, edi
pop eax // mixAlpha = source.Alpha * alpha / 256
pop edi
call _PARGBMixer // dst.rgb = dst.rgb mixer dest.rgb
movd [esi], mm0
movzx eax, [edi].TARGBQuad.Alpha// mixAlpha = dest.Alpha
lea edi, src
call _PARGBMixer // dest.rgb = dst.rgb mixer source.rgb
pop edi
movd [edi], mm0
mov [edi].TARGBQuad.Alpha, al
pop esi
@@NextB:
add esi, 4
add edi, 4
dec width
jnz @@xLoopB
pop width
add esi, srcOffset
add edi, dstOffset
dec height
jnz @@yLoopB
emms
@@End:
pop ebx
pop edi
pop esi
end;
// 图像颜色模式混合
procedure ImageColorMixer(var Dest: TImageData; const Source: TImageData; Alpha: Single = 1);
var
alphaI: Integer;
begin
alphaI := Round(Alpha * 256);
if alphaI < 0 then Exit;
if alphaI > 256 then alphaI := 256;
_DoColorMixer(Dest, Source, alphaI);
end;
例子一:
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);
ImageColorMixer(dst, src);
// 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;
例子一运行截图:
例子二:
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);
ImageColorMixer(dst, src);
// ImageMixer(dst, src, 1);
UnlockGpBitmap(source, src);
Canvas.Draw(dst.Width + src.Width, 0, dest);
g.Free;
dest.Free;
source.Free;
end;
例子二截图:
本文只提供了基本的颜色混合代码,要实现颜色混合的几何变换,可参见本系列有关几何变换的文章。
《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《Delphi图像处理 -- 文章索引》。