阅读提示:
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元
这个填充浮雕效果过程代码已经完成好几天了,但是一直没敢在BLOG上发表,因为这是我在研究Photoshop浮雕效果做实验时,无意中写的一段代码,当时就感觉这个效果虽然不是我想要的Photoshop浮雕效果,但比Photoshop浮雕效果的应用价值应该不会差,可以说是各有特色。只是当时感觉处理速度较慢,同时顾虑该浮雕效果是我妙想天开,发表后会不会有引起嘲笑。经过了几天的改进和原理论证,我觉得该浮雕效果从原理上是说得通的,而改进后的速度一般情况下比彩色浮雕处理快,在浮雕深度较小(< 8)的时候,比灰色浮雕处理还快,所以还是决定发表在这里,供大家讨论其实用性,或给出改进意见。至于这种浮雕效果的名称,刚开始时因其效果类似石雕,准备取名石雕效果,经改进后,可用任何颜色或者图案进行填充浮雕画面,所以取名填充浮雕效果。
下面先给出浮雕过程处理代码:
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;
// 实填充浮雕。Data 图像数据结构, Angle 角度, Size 长度, Color 填充色
procedure ImageSolidSculpture(var Data: TImageData; Angle: Single;
Size: LongWord; Color: TARGB);
var
x, y, Radius: Integer;
xDelta, yDelta: Integer;
width, height: Integer;
dstOffset, divSize: Integer;
src: TImageData;
begin
if Size > 128 then Size := 128;
divSize := DivTab[Size];
Radius := (Size + 1) shr 1; // 图像边框扩展半径
Angle := PI * Angle / 180;
xDelta := Round(Cos(Angle) * 4096);
yDelta := Round(Sin(Angle) * 4096);
x := (Radius shl 12) - xDelta * Size div 2;
y := (Radius shl 12) - yDelta * Size div 2;
width := x + (Data.Width shl 12);
height := Data.Height;
if Data.AlphaFlag then
ArgbConvertPArgb(Data);
src := _GetExpandData(Data, Radius);
asm
push esi
push edi
push ebx
mov eax, Data
lea edx, src
call _SetCopyRegs
mov dstOffset, ebx
mov eax, Size // mm3 = word * 4 = Size div to mul
movq mm3, qword ptr MMDivTab[eax*8]
pxor xmm7, xmm7
pxor mm7, mm7 // xmm7 = mm7 = 0
movd mm2, Color // mm2 = word Color
punpcklbw mm2, mm7
mov ebx, src.Stride
mov ecx, y // for (; y < Height; y += 4096)
@@yLoop: // {
mov edx, x // for (; x < Width; x += 4096)
@@xLoop: // {
push edx
push ecx
push edi // x1 = x, y1 = y
pxor mm1, mm1
mov edi, Size // for (i = Size - 1; Size > 0; i --)
@@addLoop: // {
call GetSrcColor // mm1 += GetSrcColor(x1, y1)
paddw mm1, mm0
add ecx, yDelta // y1 += yDelta
add edx, xDelta // x1 += xDelta
dec edi
jnz @@addLoop // }
call GetSrcColor // mm0 = GetSrcColor(x1, y1)
pmulhuw mm1, mm3 // mm1 /= Size
psubw mm1, mm0 // mm1 -= mm0
paddw mm1, mm2 // mm1 += Color
packuswb mm1, mm7
pop edi
pop ecx
pop edx
mov al, [edi].TARGBQuad.Alpha
movd [edi], mm1 // *edi = mm1
mov [edi].TARGBQuad.Alpha, al
add edi, 4 // edi ++
add edx, 1000h
cmp edx, width
jl @@xLoop // }
add edi, dstOffset
add ecx, 1000h
dec height
jnz @@yLoop
emms
pop ebx
pop edi
pop esi
end;
FreeImageData(src);
if Data.AlphaFlag then
PArgbConvertArgb(Data);
end;
// 图案填充浮雕。Data 图像数据结构, Angle 角度, Size 长度, FillData 填充图像数据结构
procedure ImageTextureSculpt(var Data: TImageData; Angle: Single;
Size: LongWord; const FillData: TImageData);
// 调整fillData宽度和高度为其最大2的幂,并拷贝到Result
function AdjustFillData: TImageData;
asm
push esi
push edi
push ebx
push eax
push eax
push eax // NewImageData Result param
mov edi, fillData
bsr ecx, [edi].TImageData.Width
xor eax, eax // mov eax, 1
bts eax, ecx // shl eax, cl (width max bit)
bsr ecx, [edi].TImageData.Height
xor edx, edx // mov edx, 1
bts edx, ecx // shl edx, cl (height max bit)
mov ecx, pf32bit
call NewImageData // Result = NewImageData(width, height, pf32bit)
pop eax
mov edx, edi
call _SetCopyRegs
@@cpyLoop:
push ecx
rep movsd
pop ecx
add esi, eax
dec edx
jnz @@cpyLoop
pop eax
// (Result.Width - 1) * 4: 宽度的最大余数*4,作为x*4的掩码
mov edx, [eax].TImageData.Width
dec edx
shl edx, 2
mov [eax].TImageData.Width, edx
// Result.Height - 1: 高度的最大余数作为y的掩码
dec [eax].TImageData.Height
pop ebx
pop edi
pop esi
end;
var
x, y, Radius: Integer;
xDelta, yDelta: Integer;
width, height: Integer;
dstOffset: Integer;
src, back: TImageData;
begin
if Size > 128 then Size := 128;
Radius := (Size + 1) shr 1; // 图像边框扩展半径
Angle := PI * Angle / 180;
xDelta := Round(Cos(Angle) * 4096);
yDelta := Round(Sin(Angle) * 4096);
x := (Radius shl 12) - xDelta * Size div 2;
y := (Radius shl 12) - yDelta * Size div 2;
width := x + (Data.Width shl 12);
height := Data.Height;
if Data.AlphaFlag then
ArgbConvertPArgb(Data);
src := _GetExpandData(Data, Radius);
back := AdjustFillData;
asm
push esi
push edi
push ebx
mov eax, Data
lea edx, src
call _SetCopyRegs
mov dstOffset, ebx
mov eax, Size // mm3 = word * 4 = Size div to mul
movq mm3, qword ptr MMDivTab[eax*8]
pxor xmm7, xmm7 // xmm7 = mm7 = 0
pxor mm7, mm7
mov ebx, src.Stride
mov ecx, y // for (; y < Height; y += 4096)
@@yLoop: // {
mov edx, x // for (; x < Width; x += 4096)
@@xLoop: // {
push edx
push ecx
push edi
push edx
push ecx // x1 = x, y1 = y
mov edi, Size // for (i = Size - 1; Size > 0; i --)
pxor mm1, mm1 // {
@@addLoop:
call GetSrcColor // mm1 += GetSrcColor(x1, y1)
paddw mm1, mm0
add ecx, yDelta // y1 += yDelta
add edx, xDelta // x1 += xDelta
dec edi
jnz @@addLoop // }
@@1:
call GetSrcColor // mm0 = GetSrcColor(x1, y1)
pmulhuw mm1, mm3 // mm1 /= Size
psubw mm1, mm0 // mm1 -= mm0
pop eax
pop edx
shr eax, 12 // fy = (y / 4096) & back.Height
// shr edx, 12 // fx = (x / 4096) & back.Width
shr edx, 10
and eax, back.TImageData.Height // height is y mask
and edx, back.TImageData.Width // width is x*4 mask
bsr ecx, back.TImageData.Stride
shl eax, cl
// shl edx, 2
add eax, edx
add eax, back.TImageData.Scan0
movd mm0, [eax] // mm0 = FillData.Scan0[fy * back.Stride + fx * 4]
punpcklbw mm0, mm7
paddw mm0, mm1 // mm0 += mm1
packuswb mm0, mm7
pop edi
pop ecx
pop edx
mov al, [edi].TARGBQuad.Alpha
movd [edi], mm0 // *edi = mm0
mov [edi].TARGBQuad.Alpha, al
add edi, 4 // edi ++
add edx, 1000h
cmp edx, width
jl @@xLoop // }
add edi, dstOffset
add ecx, 1000h
dec height
jnz @@yLoop
emms
pop ebx
pop edi
pop esi
end;
FreeImageData(back);
FreeImageData(src);
if Data.AlphaFlag then
PArgbConvertArgb(Data);
end;
从处理流程看,填充浮雕和彩色浮雕、灰色浮雕是一样的,但在像素处理上是不相同的。下面是用一个用45度角,2像素深度浮雕差值计算矩阵图来说明几种浮雕效果像素处理的差异
各矩阵中,中间的点可以看作为要处理的像素。
灰度浮雕固定地取左上角点和右下角的差值加上128背景值 (无论深度多大,都是如此,只是矩阵中间的0多少的问题),如此一来,差异小的像素趋向于背景色,差异大的像素形成“黑白分明”的阴线和阳线,这就成了灰色的浮雕效果,浮雕深度越大,“黑白分明”的效果就越明显,当深度达到一个比较大的值时,画面上会形成加强了的正、负片以及图像原色彩3层画面的共存状态;
彩色浮雕也是固定的取邻近3个点的值减去2倍右下角点的值,由于被减的点数大于减的点数,在雕刻阴影形成时保留了很大的亮度,这就相当于给各像素加了一个不固定的背景值,所以形成彩色浮雕效果;
而填充浮雕的则是取主对角线除右下角外的各点之和的平均值,减去右下角点的值,再加上填充背景色,在背景色固定的前提下(假定128),会形成类似灰度浮雕的效果,但是由于是取主对角线除右下角外的各点之和的平均值,就相当于先对像素点做了一定的表面模糊后再减去右下角点的值,所以,当雕刻深度增大时,不会形成明显的“黑白分明”效应,而是有一定的模糊过渡带,即类似阴影的半影调。
下面是以45度角,深度为10,背景色128的灰度浮雕和填充浮雕效果比较图:
左上角是原图,左下角是灰度浮雕效果图,右上角是填充浮雕效果图,因为填充浮雕在浮雕效果处理前对原图作了灰度处理,为便于比较,所以右下角是灰度化后的灰色浮雕效果图。
从比较图上可以看出,当浮雕深度较大时,无论是否去色,灰度浮雕效果的阳线带和阴线带都很明显,而填充浮雕则存在过渡半影调,所以,看起来有石雕的效果。另外,从灰色背景上看,填充浮雕效果明显的平坦于灰色浮雕效果,这是因为填充浮雕处理有一定的表面模糊作用的缘故。
填充浮雕可以以任意颜色和图案作为背景填充,这就使得填充浮雕过程能产生各种效果的浮雕图,为了便于使用,我把填充浮雕过程写成了2个过程,分别用来处理实色填充和图案填充。
下面先对实色填充作几个图片处理测试:
TGpBitmap测试代码(仿玉石浮雕效果):
procedure TForm1.Button3Click(Sender: TObject);
var
bmp: TGpBitmap;
g: TGpGraphics;
data: TImageData;
begin
bmp := TGpBitmap.Create('..\media\graysource.bmp');
g := TGpGraphics.Create(Canvas.Handle);
g.DrawImage(bmp, 0, 0);
data := LockGpBitmap(bmp);
ImageSolidSculpture(data, 45, 10, $AEC0C8);
UnlockGpBitmap(bmp, data);
g.DrawImage(bmp, 0, data.Height);
g.Free;
bmp.Free;
end;
效果图如下,上面是原图,下面左边是45度,深度为5的浮雕效果,下面右边是45度,深度为10的浮雕效果。
原图:
你是否觉得象玉石浮雕?特别是右边图中那些重叠着的叶片, 真有着一种晶莹剔透的感觉!
下面是以角度30,深度8,填充颜色为$A5140A的仿玛瑙色浮雕效果图,测试代码就不贴了:
TBitmap填充图案测试代码:
procedure TForm1.Button4Click(Sender: TObject);
var
bmp, fbmp: TBitmap;
data, fdata: TImageData;
begin
bmp := TBitmap.Create;
fbmp := TBitmap.Create;
bmp.LoadFromFile('..\media\graysource.bmp');
fbmp.LoadFromFile('..\media\back-2.bmp');
data := GetBitmapData(bmp);
fData := GetBitmapData(fbmp);
ImageTextureSculpt(data, 45, 5, fData);
Canvas.Draw(0, 0, bmp);
fbmp.Free;
bmp.free;
end;
下面是使用4种不同填充图案调用ImageTextureSculpture过程分别形成的浮雕效果图(原图在文章前面),角度50,浮雕深度从左上角开始,依次是8,6,5,7:
填充浮雕前也可以不进行灰度处理,有时还能产生意想不到的效果,下面是使用图案填充的2幅效果:
填充背景图案:
通过几天的研究和测试,发现浮雕角度、深度和填充色(图案)的选择,应根据图片的实际情况确定,如上图中,左上角的填充图案颜色较深、较杂,这就需要把浮雕深度用大一点,反之,如左下角就要相应小一点;图像较平淡的,不宜用很深的浮雕;浮雕角度与图像光照方向基本对应;填充颜色(图案)也应按图像具体选用,如前面的玉雕效果图,如果换成其它图片,效果不一定那么好,反过来也可以说,那张图用玛瑙色效果就差多了。
关于填充效果图的介绍就到此为止,欢迎朋友们提出改进意见。
最后重申一下,该效果确是本人无意中搞出来的,如果已经有类似的方法,纯属巧合,本人不和你争“专利权”了。
《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《Delphi图像处理 -- 文章索引》。