![]() |
|
||||||||||||||
| | 网站首页 | 数据库教程 | web编程 | 服务器 | 程序设计 | | ||
|
||
|
||||||
| GDI+ 在Delphi程序的应用 -- 线性调整图像亮度 | ||||||
作者:佚名 文章来源:不详 点击数: 更新时间:2007-9-12 ![]() |
||||||
|
我曾写过2篇关于GDI+图像亮度调整的文章:《GDI+ 在Delphi程序的应用 -- 调整图像亮度》和《GDI+ 在Delphi程序的应用 -- ColorMatrix与图像亮度》,前者采用GDI+的Bitmap扫描线逐点增加或减少图像像素RGB的值,后者则通过设置GDI+的ColorMatrix进行调整,但是这两种方法都属于非线性的亮度调整,优点是代码简单、速度快,缺点是在调整亮度的同时,也损失了图像的色彩的纯度。
利用HSL颜色空间,通过只对其L(亮度)部分调整,可达到图像亮度的线性调整。但是,RGB和HSL颜色空间的转换很繁琐,一般还需要浮点数的运算,不仅增加了代码的复杂度,更重要的是要逐点将RGB转换为HSL,然后确定新的L值,再将HSL转换为RGB,运行速度可想而知是很慢的。要想提高图像亮度线性调整的速度,应该从三方面考虑,一是变浮点运算为整数运算,二是只提取HSL的L部分进行调整,三是采用汇编代码,在Delphi中,当然是BASM。下面是按照这三方面考虑写的图像亮度线性调整代码: procedure GetHSL_L; asm movzx eax, [esi] movzx ebx, [esi + 1] movzx ecx, [esi + 2] cmp ebx, ecx // L = (Max(R, Max(G, B)) + Min(R, Min(G, B))) >> 1; jg @@1 xchg ebx, ecx @@1: cmp ebx, eax jg @@2 xchg ebx, eax @@2: cmp ecx, eax jl @@3 xchg ecx, eax @@3: add ebx, ecx shr ebx, 1 // ebx = L end; procedure SetHSL_L; asm xor ebp, ebp sub edi, 128 // edi = newL cmp ebx, 128 jle @@1 mov ebp, 256 // if (L > 128) sub ebp, ebx // { xchg ebp, ebx // ebx = 256 - L sub ebp, 128 // ebp = (L - 128) * 256 shl ebp, 8 // } @@1: // else {ebp = 0; ebx = L} mov ecx, 3 // for (ecx = 3; ecx > 0; ecx --, esi ++) @vLoop: // { movzx eax, [esi] // rgbToHS = (*esi * 128 - ebp) / ebx shl eax, 7 sub eax, ebp cdq div ebx test edi, edi js @@2 mov edx, 256 // if (edi >= 0) sub edx, eax // rgb = rgbToHS + (256 - rgbToHS) * edi / 128 imul edx, edi add edx, 127 // 由于2次舍弃对高亮度部分造成误差,+127作补偿 shr edx, 7 jmp @@3 @@2: mov edx, eax // else imul eax, edi // rgb = rgbToHS + rgbToHS * edi / 128 neg eax shr eax, 7 neg eax @@3: add eax, edx // rgb = Max(0, Min(255, rgb)) jns @@4 xor eax, eax jmp @@5 @@4: cmp eax, 255 jle @@5 mov eax, 255 @@5: mov [esi], al // *esi ++ = rgb inc esi loop @vLoop // } end; procedure HSLBrightness(Data: TBitmapData; Value: Integer); asm push ebp push esi push edi push ebx mov ebp, eax mov esi, [ebp + 16] mov eax, [ebp] shl eax, 1 add eax, [ebp] mov ebx, [ebp + 8] sub ebx, eax mov ecx, [ebp + 4] mov [ebp + 20], edx @yLoop: push ecx mov ecx, [ebp] @xLoop: push ecx push ebx call GetHSL_L mov edi, ebx add edi, [ebp + 20] push ebp call SetHSL_L pop ebp pop ebx pop ecx loop @xLoop add esi, ebx pop ecx loop @yLoop pop ebx pop edi pop esi pop ebp end; 其中的GetHSL_L和SetHSL_L过程是2个内部过程(不能直接调用的),为了方便调试和测试用的,可以将它们的代码直接移入到HSLBrightness过程中,分别替换call GetHSL_L和call SetHSL_L,速度还可得到小小的提升。 GetHSL_L是用来提取像素HSL颜色的L部分,比较简单,用Pascal语法就一句话: L := (Max(R, Max(G, B)) + Min(R, Min(G, B))) div 2; L没有采用通常的百分比表示,而是取值0 - 255,这样就不必要采用浮点数运算了。 SetHSL_L相对要复杂多了,也是代码的核心部分,主要完成2个功能,一是用以前的L值(GetHSL_L中得到的)分别RGB求出其HSL的HS部分,其公式用Pascal表示为: if L > 128 then 二是用新的L值(老的L值加需要调整的亮度值(0 - 255))和上面求出的HS值计算出新的RGB值,计算方法为: 如此,一个像素点的线性亮度调整就基本完成了,这也是比非线性亮度调整多出来的代码,运行速度肯定比非线性亮度调整慢,但完全可以满足要求:在我的机器上测试(P464 2.8G, DDR2 667 1GB),一幅1000W像素的数码照片用非线性调整为750毫秒,而线性调整过程为1300毫秒,其中,TGpBitmap的Lock过程就占了600多毫秒,实际非线性调整约150毫秒,线性亮度调整大约为750毫秒左右,是非线性调整耗时的5倍!当然,对于小的照片这些完全可以忽略。至于HSLBrightness过程在这里只是个循环处理框架罢了。 至于上述代码计算是否正确,我做过随机单个像素提取和还原的测试,也就是随机给出R、G、B,提取其L值,然后不改变L值,提取HS值后还原为新的R、G、B值,其准确度差不多为99%以上,我也用《Delphi数字图像处理及高级应用》一书中的HSL与RGB转换代码测试过,本过程比书上的过程还原准确度高多了!其原因是我只提取了HSL的L,虽然也求了HS部分,但并没有将其拆开,这就减少了运算精度损失;另外,该书的L值以100为单位,而本过程用255为单位,虽然是整数运算,但只要在合适的地方给与适度补偿,其运算精度比书上的浮点数运算还高。我把测试GetHSL_L和SetHSL_L的代码附在下面,有兴趣的朋友可以测试一下: procedure LTest(R, G, B: Integer; var L, RI, GI, BI: Integer); var v: array[0..3] of Integer; asm push esi push edi push ebx lea esi, v mov eax, R mov [esi], al mov eax, G mov [esi + 1], al mov eax, B mov [esi + 2], al call GetHSL_L mov eax, L mov [eax], ebx mov edi, ebx push ebp call SetHSL_L pop ebp sub esi, 3 movzx eax, [esi] mov ebx, RI mov [ebx], eax movzx eax, [esi + 1] mov ebx, GI mov [ebx], eax movzx eax, [esi + 2] mov ebx, BI mov [ebx], eax pop ebx pop edi pop esi end; procedure TForm1.Button1Click(Sender: TObject); const s = #13 + #10; var R, G, B, RT, GT, BT, L: Integer; begin Randomize; R := Random(255); G := Random(255); B := Random(255); LTest(R, G, B, L, RT, GT, BT); ShowMessage(Format(' R: %d, G: %d, B: %d%sRI: %d, GI: %d, BI: %d, L:%d', [R, G, B, s, RT, GT, BT, L])); end; 好了,下面可以给出图像亮度线性调整过程的测试代码了: procedure TForm1.Button2Click(Sender: TObject); var bmp1, bmp2: TGpBitmap; data1, data2: TBitmapData; g: TGpGraphics; r: TGpRect; value: Integer; begin value := 20; bmp1 := TGpBitmap.Create('d:\001-1.jpg'); r := GpRect(0, 0, bmp1.Width, bmp1.Height); bmp2 := bmp1.Clone(r, pf24bppRGB); g := TGpGraphics.Create(Handle, False); try g.DrawImage(bmp1, r); data1 := bmp1.LockBits(r, [imRead, imWrite], pf24bppRGB); data2 := bmp2.LockBits(r, [imRead, imWrite], pf24bppRGB); try Brightness(data1, value); HSLBrightness(data2, value); finally bmp2.UnlockBits(data2); bmp1.UnlockBits(data1); end; g.TranslateTransform(195, 0); g.DrawImage(bmp1, r); g.TranslateTransform(195, 0); g.DrawImage(bmp2, r); finally g.Free; bmp2.Free; bmp1.Free; end; end; function GetImageData(Bmp: TBitmap): TBitmapData; begin Bmp.PixelFormat := pf24bit; Result.Width := Bmp.Width; Result.Height := Bmp.Height; Result.Scan0 := Bmp.ScanLine[Result.Height - 1]; Result.Stride := (((24 * Bmp.Width) + 31) and $ffffffe0) shr 3; end; procedure TForm1.Button3Click(Sender: TObject); var bmp1, bmp2: TBitmap; data1, data2: TBitmapData; value: Integer; begin bmp1 := TBitmap.Create; bmp2 := TBitmap.Create; value := 100; try bmp1.LoadFromFile('d:\001-1.bmp'); bmp2.Assign(bmp1); Canvas.Draw(0, 0, bmp1); data1 := GetImageData(bmp1); Brightness(data1, value); Canvas.Draw(195, 0, bmp1); data2 := GetImageData(bmp2); HSLBrightness(data2, value); Canvas.Draw(390, 0, bmp2); finally bmp2.Free; bmp1.Free; end; end; 测试代码有2个,一是使用GDI+的TGpBitmap,一是使用Delphi的TBitmap,测试结果是相同的。有人可能会问,GDI+的图像亮度调整过程能否调整TBitmap?自己测试一下不就明白了,其原理可以参见我的文章《GDI+ 在Delphi程序的应用 -- 图像卷积操作及高斯模糊》;其中,每个测试都使用了非线性调整和线性调整过程,其中的非线性调整过程可参见文章开头提到的2篇文章(好像做广告了,呵呵)。运行结果如下: 先给出测试图片原图: 再给出运行结果图和Photoshop处理的图片合成图(便于比较,免得贴太多的图):
通过上面的比较图,不难发现Photoshop的亮度处理也是非线性的,因为其处理结果与我的非线性RGB亮度调整过程的处理结果完全一样!我记得好像有网友在CSDN论坛上非要寻求Photoshop的亮度调整原理,看来大可不必了,它使用的也是一种最简单的调整方法,只不过Photoshop作了-100 - +100的范围控制而已。 而线性亮度调整和非线性亮度调整结果比较,很显然,线性调整后的颜色深度和层次性要好多了,在亮度值20的时候,效果比非线性亮度调整好多了,至于亮度值100和-100的调整,表面看,似乎线性调整不如非线性调整好看:非线性调整比较平淡、均匀,所以看上去比较“顺眼”;而线性调整由于调整后的颜色的纯度没什么损失,随着图像亮度大幅度的线性增减,其色彩层次空间也相应变化很大,使部分像素超出0 - 255范围,所以显得“难看”。但是仔细观察一下,亮度值为100的图像,虽然面部因亮度太强而损失了明暗,但背景的颜色层次却凸现出来了(如左下角的蓝色背景),即使是难看的-100亮度调整后的图片,也显示出很深的色彩层次空间感,而非线性亮度调整后的图片,除了平淡,还是平淡。 老生常谈:1、本人用GDI+过程与网上有区别;2、如有错误请指正,建议也请来信:maozefa@hotmail.com 本文来源:http://blog.csdn.net/maozefa/archive/2007/08/25/1758962.aspx
|
||||||
| 文章录入:admin 责任编辑:admin | ||||||
| 【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口】 | ||||||
| 网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!) |
| | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告 | 网站地图 | 管理登录 | | |||
|