阅读提示:
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元和《Delphi图像处理 -- 平面几何变换类》TransformMatrix.pas单元。
实现图像浮雕效果的一般原理是,将图像上每个像素点与其对角线的像素点形成差值,使相似颜色值淡化,不同颜色值突出,从而产生纵深感,达到浮雕的效果,具体的做法是用处于对角线的2个像素值相减,再加上一个背景常数,一般为128而成。这种算法的特点是简单快捷,缺点是不能调节图像浮雕效果的角度和深度。
用Photoshop实现的图像浮雕效果,可以任意调节浮雕角度和深度(2个像素点的距离),还可以调整浮雕像素差值的数量。其基本算法原理和一般浮雕效果相同,但是具体做法不一样:对每个要处理的像素点,首先按照浮雕角度和深度计算处2个相应点的位置,然后计算这2个位置的颜色值,并使之形成差值,再乘上浮雕差值数量百分比,最后加上128的背景色。注意,这里计算的2个相应点是逻辑点,而不是实际的像素点,比如实现一个45度角,深度为3的图像浮雕效果,对每个像素点P(x, y),其对应的2个逻辑点的位置分别是P0(x - 3 * 0.7071 / 2, y - 3 * 0.7071 / 2)和P1(x + 3 * 0.7071 / 2, y + 3 * 0.7071 / 2),显然,对于这样的2个逻辑点,是不能直接从图像中找到其对应的像素点的,如果简单地对其四舍五入处理,将会造成大量的,由不同角度和深度而形成的相同的浮雕效果,这可不是我们想要的结果,而且使浮雕角度和深度参数失去了它原本的意义。为此,必须对原始图像按浮雕角度和深度进行缩放后,再对每个像素点进行浮雕效果处理,完毕再缩放回原图的大小,从而完成整个浮雕效果过程。下面是我经过反复试验后,写的Photoshop浮雕效果实现过程代码:
procedure GetSrcColor;
asm
push esi // esi = src.Scan0
mov eax, ecx
sar eax, 12
imul eax, ebx
add esi, eax // esi += (y / 4096 * src.Stride)
mov eax, edx
sar eax, 12
shl eax, 2
add esi, eax // esi += (x / 4096 * 4)
call _GetBilinearColor
movd eax, xmm0
movd mm0, eax
punpcklbw mm0, mm7 // return mm0 = ARGB (word * 4)
pop esi
end;
procedure ImageGraySculpture(var Data: TImageData;
Angle: Single; Size: LongWord; Num: LongWord = 100);
var
x, y, Radius: Integer;
xDelta, yDelta: Integer;
width, height: Integer;
dstOffset: Integer;
src: TImageData;
begin
Radius := (Size + 1) shr 1; // 图像边框扩展半径
Angle := PI * Angle / 180;
Size := Size shl 12; // Size = Size * 256
Num := (Num shl 9) div 100; // Num *= 5.12 转换为2的幂
xDelta := Round(Cos(Angle) * Size);
yDelta := Round(Sin(Angle) * Size);
x := (Radius shl 12) - (xDelta div 2);
y := (Radius shl 12) - (yDelta div 2);
width := x + (Data.Width shl 12);
height := y + (Data.Height shl 12);
if Data.AlphaFlag then
ArgbConvertPArgb(Data);
src := _GetExpandData(Data, Radius);
asm
mov eax, Data
lea edx, src
call _SetCopyRegs
mov dstOffset, ebx
movq mm5, qword ptr ArgbTab[128*8]// mm5 = 00 128 00 128 00 128 00 128
movd mm6, Num // mm6 = 00 Num 00 Num 00 Num 00 Num
punpcklwd mm6, mm6
punpcklwd mm6, mm6
pxor mm7, mm7 // mm7 = 00 00 00 00 00 00 00 00
pxor xmm7, xmm7
mov esi, src.Scan0
mov ebx, src.Stride
mov ecx, y // for (; y < Height; y += 4096)
@@yLoop: // {
mov edx, x // for (; x < Width; x += 4096)
@@xLoop: // {
add ecx, yDelta // y1 = y + yDelta
add edx, xDelta // x1 = x + xDelta
call GetSrcColor //
movq mm1, mm0 // mm1 = 00 A1 00 R1 00 G1 00 B1
sub ecx, yDelta // y0 = y - yDelta
sub edx, xDelta // x0 = x - xDelta
call GetSrcColor // mm0 = 00 A0 00 R0 00 G0 00 B0
psubw mm0, mm1 // mm0 = A0-A1 R0-R1 G0-G1 B0-B1
psllw mm0, 7 // mm0 = mm0 * 128 * Num / 65536
pmulhw mm0, mm6
paddw mm0, mm5 // mm0 = A+128 R+128 G+128 B+128
packuswb mm0, mm7 // mm0 = 00 00 00 00 A R G B
mov al, [edi].TARGBQuad.Alpha
movd [edi], mm0 // *edi = mm0
mov [edi].TARGBQuad.Alpha, al
add edi, 4 // edi += 4
add edx, 1000h
cmp edx, width
jl @@xLoop // }
add ecx, 1000h
add edi, dstOffset
cmp ecx, height
jl @@yLoop // }
emms
end;
FreeImageData(src);
if Data.AlphaFlag then
PArgbConvertArgb(Data);
end;
下面是个演示例子代码:
procedure TForm1.Button3Click(Sender: TObject);
var
bmp: TGpBitmap;
g: TGpGraphics;
data: TImageData;
begin
bmp := TGpBitmap.Create('..\media\20041001.jpg');
g := TGpGraphics.Create(Canvas.Handle);
g.DrawImage(bmp, 0, 0);
data := LockGpBitmap(bmp);
ImageGraySculpture(data, 45, 3, 100);
UnlockGpBitmap(bmp, data);
g.DrawImage(bmp, 0, data.Height);
g.Free;
bmp.Free;
end;
运行效果图:
和Photoshop浮雕效果对比,基本一致。
补记:在文章评论中,有人说灰色浮雕不带彩,其实是不正确的,我测试过网上和书上的很多代码,也测试过Photoshop,都是有可能带彩的,在浮雕深度很小而且原始图像色彩较平淡时也有可能不带彩,可以把我上面的原图裁下来,用Photoshop一试就清楚了。当然,如果你要达到完全不带彩也很简单,在浮雕处理前做一个图像灰度化即可。如果灰度浮雕完全不带彩,程序代码可得到简化,速度至少可再提高60%以上。
《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《Delphi图像处理 -- 文章索引》。