阅读提示:
《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》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《Delphi图像处理 -- 文章索引》。