`
sogotobj
  • 浏览: 639201 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

GDI+ 在Delphi程序的应用 -- 线性调整图像亮度

阅读更多
我曾写过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。下面是按照这三方面考虑写的图像亮度线性调整代码:

说明:为了统一《GDI+ 在Delphi程序的应用》系列文章所用数据类型和图像处理格式,本文代码已作了修订,代码中所用Gdiplus单元下载地址及BUG更正见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。(2008.8.18记)
数据类型:
  1. type
  2. //与GDI+TBitmapData结构兼容的图像数据结构
  3. TImageData=packedrecord
  4. Width:LongWord;//图像宽度
  5. Height:LongWord;//图像高度
  6. Stride:LongWord;//图像扫描线字节长度
  7. PixelFormat:LongWord;//未使用
  8. Scan0:Pointer;//图像数据地址
  9. Reserved:LongWord;//保留
  10. end;
  11. PImageData=^TImageData;
  12. //获取TBitmap图像的TImageData数据结构,便于处理TBitmap图像
  13. functionGetImageData(Bmp:TBitmap):TImageData;
  14. begin
  15. Bmp.PixelFormat:=pf32bit;
  16. Result.Width:=Bmp.Width;
  17. Result.Height:=Bmp.Height;
  18. Result.Scan0:=Bmp.ScanLine[Bmp.Height-1];
  19. Result.Stride:=Result.Widthshl2;
  20. //Result.Stride:=(((32*Bmp.Width)+31)and$ffffffe0)shr3;
  21. end;

过程代码:

  1. procedureGetHSL_L;
  2. asm
  3. movzxeax,[esi]
  4. movzxebx,[esi+1]
  5. movzxecx,[esi+2]
  6. cmpebx,ecx//L=(Max(R,Max(G,B))+Min(R,Min(G,B)))>>1;
  7. jge@@1
  8. xchgebx,ecx
  9. @@1:
  10. cmpebx,eax
  11. jge@@2
  12. xchgebx,eax
  13. @@2:
  14. cmpecx,eax
  15. jle@@3
  16. xchgecx,eax
  17. @@3:
  18. addebx,ecx
  19. shrebx,1//ebx=L
  20. end;
  21. procedureSetHSL_L;
  22. asm
  23. subedi,128//edi=newL
  24. xorebp,ebp//if(L<=128){
  25. cmpebx,128//ebp=0;ebx=L
  26. jle@@0//}
  27. movebp,256//else
  28. subebp,ebx//{
  29. xchgebp,ebx//ebx=256-L
  30. subebp,128//ebp=(L-128)*256
  31. shlebp,8//}
  32. @@0:
  33. movecx,3//for(ecx=3;ecx>0;ecx--)
  34. @RGBLoop://{
  35. movzxeax,[esi]//if(ebx==0)
  36. testebx,ebx//rgbToHS=*esi
  37. jz@@1
  38. shleax,7//else
  39. subeax,ebp//rgbToHS=(*esi*128-ebp)/ebx
  40. cdq
  41. divebx
  42. @@1:
  43. testedi,edi
  44. js@@2
  45. movedx,256//if(edi>=0)
  46. subedx,eax//rgb=rgbToHS+(256-rgbToHS)*edi/128
  47. imuledx,edi
  48. addedx,127//由于2次舍弃对高亮度部分造成误差,+127作补偿
  49. shredx,7
  50. jmp@@3
  51. @@2:
  52. movedx,eax//else
  53. imuleax,edi//rgb=rgbToHS+rgbToHS*edi/128
  54. negeax
  55. shreax,7
  56. negeax
  57. @@3:
  58. addeax,edx//rgb=Max(0,Min(255,rgb))
  59. jns@@4
  60. xoreax,eax
  61. jmp@@5
  62. @@4:
  63. cmpeax,255
  64. jle@@5
  65. moveax,255
  66. @@5:
  67. mov[esi],al//*esi++=rgb
  68. incesi
  69. loop@RGBLoop//}
  70. incesi//esi++
  71. end;
  72. //利用HSL线性调整图像亮度,Value亮度值
  73. procedureHSLBrightness(Data:TImageData;Value:Integer);
  74. asm
  75. pushebp
  76. pushesi
  77. pushedi
  78. pushebx
  79. movesi,[eax+16]
  80. movecx,[eax+4]
  81. imulecx,[eax]
  82. movebp,edx
  83. @PixelLoop:
  84. pushecx
  85. callGetHSL_L
  86. movedi,ebx
  87. addedi,ebp
  88. pushebp
  89. callSetHSL_L
  90. popebp
  91. popecx
  92. loop@PixelLoop
  93. popebx
  94. popedi
  95. popesi
  96. popebp
  97. end;
  98. //利用HSL线性调整GDI+图像亮度,Value亮度值
  99. procedureGdipHSLBrightness(Bmp:TGpBitmap;Value:Integer);
  100. var
  101. Data:TBitmapData;
  102. begin
  103. ifValue=0thenExit;
  104. Data:=Bmp.LockBits(GpRect(0,0,Bmp.Width,Bmp.Height),[imRead,imWrite],pf32bppARGB);
  105. try
  106. HSLBrightness(TImageData(Data),Value);
  107. finally
  108. Bmp.UnlockBits(Data);
  109. end;
  110. end;
  111. //利用HSL线性调整TBitmap图像亮度,Value亮度值
  112. procedureBitmapHSLBrightness(Bmp:TBitmap;Value:Integer);
  113. begin
  114. ifValue<>0then
  115. HSLBrightness(GetImageData(Bmp),Value);
  116. 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
begin
rHS := (R * 128 - (L - 128) * 256) div (256 - L);
gHS := (G * 128 - (L - 128) * 256) div (256 - L);
bHS := (B * 128 - (L - 128) * 256) div (256 - L);
end else
begin
rHS := R * 128 div L;
gHS := G * 128 div L;
bHS := B * 128 div L;
end;

二是用新的L值(老的L值加需要调整的亮度值(0 - 255))和上面求出的HS值计算出新的RGB值,计算方法为:
newL := L + Value - 128;
if newL > 0 then
begin
newR := rHS + (256 - rHS) * newL div 128;
newG := gHS + (256 - gHS) * newL div 128;
newB := bHS + (256 - bHS) * newL div 128;
else begin
newR := rHS + rHS * newL div 128;
newG := gHS + gHS * newL div 128;
newB := bHS + bHS * newL div 128;
end;

如此,一个像素点的线性亮度调整就基本完成了,这也是比非线性亮度调整多出来的代码,运行速度肯定比非线性亮度调整慢,但完全可以满足要求:在我的机器上测试(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的代码附在下面,有兴趣的朋友可以测试一下:

procedureLTest(R,G,B:Integer;varL,RI,GI,BI:Integer);
var
v:array[
0..3]ofInteger;
asm
pushesi
pushedi
pushebx

leaesi,v
moveax,R
mov[esi],al
moveax,G
mov[esi
+1],al
moveax,B
mov[esi
+2],al
callGetHSL_L
moveax,L
mov[eax],ebx
movedi,ebx
pushebp
callSetHSL_L
popebp
subesi,4

movzxeax,[esi]
movebx,RI
mov[ebx],eax

movzxeax,[esi
+1]
movebx,GI
mov[ebx],eax

movzxeax,[esi
+2]
movebx,BI
mov[ebx],eax

popebx
popedi
popesi
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;

好了,下面可以给出图像亮度线性调整过程的测试代码了:

procedureTForm1.Button2Click(Sender:TObject);
var
bmp1,bmp2:TGpBitmap;
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
GdipBrightness(bmp1value);
GdipHSLBrightness(
bmp2, value);
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;
procedureTForm1.Button3Click(Sender:TObject);
var
bmp1,bmp2:TBitmap;

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);
BitmapBrightness(bmp1,value);
Canvas.Draw(
195,0,bmp1);
BitmapHSLBrightness(bmp2,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

后记:今天修改了SetHSL_L过程中当L(ebx)=0时,作除数的错误,这只有RGB全为0时会出现。另外发现HSL线性亮度调整的一个特性:当RGB全部=0或者全部=255,也就是像素为黑或白的时候,亮度值增量在-128 -- + 128(如果以100%表示的HSL空间,是-50% -- + 50%)范围内是不会做任何改变的。而一般的图片是不会采用这么大的亮度增量的,那将会使图片严重失真!当然,一幅图片中全黑和全白的像素也不会太多。除非是二值图。(可能有人会说这就是线性调整的主要特征,有啥奇怪的。不过我文化低,不大懂理论,发现这一点就记载这里,作为备忘)。

后记:在GDI+下,32位PNG图像经转换为24位图像格式处理还原后,原有的透明色在转换过程中损失,故将本文对24位图像处理代码改为了32位图像处理代码。另外,测试代码中的一般图像亮度调整过程代码在文章《GDI+ 在Delphi程序的应用 -- 调整图像亮度》中。(2007.12.12)

分享到:
评论

相关推荐

    GDI+入门指导书------经典

    GDI+入门指导书------经典 非常适合于GDI+初学者

    GDI++ for DELPHI

    GDI++ for DELPHI是一个专为DELPHI开发者设计的图形设备接口(GDI)扩展库,它提供了丰富的图形绘制功能,使开发者能够利用更高级的图形处理技术来增强应用程序的界面美观度和交互性。GDI++是基于Windows API的GDI...

    gdi++实现图像压缩-图像裁剪和缩放-图像格式转换-图像dpi修改.rar

    在Windows平台上,C++开发图形应用时,GDI++(也称为GDI+)是一个强大的图形库,它提供了丰富的功能,包括图像处理、绘制、文本渲染等。本压缩包文件包含的是利用GDI++实现的一些关键图像操作,如图像压缩、图像裁剪...

    GDI+_for_VCL基础

    GDI+ for VCL基础是关于在Delphi和C++Builder中使用GDI+图形库进行图形绘制和图像处理的专题。GDI+是微软在Windows XP系统中引入的一个增强图形设备接口,它扩展了传统的GDI(Graphics Device Interface),提供了更...

    GDI+图像程序设计(PDF & 源码 -电子工业出版社)

    ——在GDI+ Painter应用程序中添加颜色、钢笔和画笔 总结 第5章 颜色、字体和文本 5.1 访问Graphics对象 5.2 使用颜色 5.3 使用字体 5.4 使用文本和字符串 5.5 渲染文本的质量和性能 5.6 高级版式 5.7 一个...

    Delphi使用GDI+制作任意图片形状窗口

    为了实现上述功能,我们需要在Delphi中创建一个新的VCL Forms应用程序,然后导入GDI+的相关单元。在窗体的`OnCreate`事件中初始化GDI+,在`OnPaint`事件中绘制图片和处理透明度,在`OnNCCalcSize`事件中计算并设置...

    GDI+图形程序设计.zip

    GDI+提供了丰富的绘图功能,包括二维几何图形、曲线、文本、图像以及颜色管理等,使得开发者能够创建出具有高质量视觉效果的应用程序。 这本书《GDI+程序设计》显然是一个深入探讨GDI+技术的教程,它可能包含了GDI+...

    在Delphi XE10.2版本可用的GDI+库

    在Delphi XE10.2版本中,开发者可以利用GDI+库来增强图形处理功能,提升应用程序的视觉效果。GDI+是Microsoft提供的一个图形设备接口,它基于GDI(Graphics Device Interface),但提供了更多的图形绘制和图像处理...

    GDI+程序设计_GDI+程序设计_

    通过GDI+,你可以创建自定义的控件,实现复杂的用户界面,或者在应用程序中添加图形输出功能。 1. **Graphics类**:这是GDI+的核心类,用于实际的绘图操作。你可以从Graphics对象上绘制线条、曲线、矩形、椭圆、...

    Delphi GDI+

    Delphi GDI+是Delphi开发者用于构建高质量图形用户界面的强大工具集,它通过提供矢量图形、位图处理、文字渲染、颜色管理和图形变换等功能,极大地扩展了原生GDI的功能,帮助开发者创建出更为美观且功能丰富的应用...

    GDI+程序设计 GDI+程序设计 GDI+程序设计 GDI+程序设计

    GDI+(Graphics Device Interface Plus)是微软Windows操作系统中用于图形设备接口的增强版本,它为开发者提供了一套丰富的绘图工具,使得在Windows应用程序中创建、管理和显示图形变得更加便捷和高效。GDI+不仅继承...

    delphi gdi+遮罩动画字

    Delphi是一款强大的Windows应用程序开发工具,而GDI+(Graphics Device Interface Plus)是微软提供的图形处理库,提供了丰富的图形绘制和图像处理功能。 首先,我们需要了解遮罩动画的基本原理。遮罩动画通常涉及...

    GDI+程序设计(清华大学)

    书中不仅介绍了GDI+的基础知识,还着重讲解了如何将GDI+技术应用于实际开发中,尤其适合那些希望在应用程序中实现高级图形处理和自定义控件开发的中高级程序员。 另外,书中提到了.NET Framework对编程的变革作用,...

    ( Delphi 7 )使用GDI+进行图形缩放、拖动,多种图片格式支持,仅简单示例

    使用GDI+进行图形缩放、拖动,多种图片格式支持,仅简单示例。 问题源贴:http://bbs.csdn.net/topics/390638094

    GDI+游戏GDI+游戏GDI+游戏GDI+游戏GDI+游戏GDI+游戏

    GDI+(Graphics Device Interface Plus)是Windows操作系统中用于图形绘制和图像处理的一个高级API,它是.NET Framework的一部分,尤其在C#编程中被广泛应用于创建图形界面和游戏开发。在这个"飞机大战"的小游戏中,...

    Delphi使用GDI实现图像镜像翻转

    Delphi作为流行的Windows应用程序开发工具,其VCL库与GDI接口紧密结合,使得开发者能够方便地利用GDI的功能。 首先,我们要理解图像翻转的原理。图像的镜像翻转主要有两种类型:水平翻转(也称为左右翻转)和垂直...

    GDI+调整图像亮度示例 (delphi)

    在本示例中,我们将探讨如何使用Delphi和GDI+来调整图像的亮度。 首先,理解图像亮度的基本概念至关重要。亮度是人眼感知图像亮暗程度的度量,通常通过调整像素的RGB值来改变。在GDI+中,可以通过修改每个像素的...

    GDI+图形程序设计

    通过阅读本书,读者不仅可以掌握GDI+的基本概念,还能学会如何在实际项目中应用这些技术,提升Windows应用程序的图形表现力。 《GDI+图形程序设计》的PDF文件包含了详细的章节结构,可能包括以下内容:GDI+概述、...

    VC下如何使用GDI+进行 图像程序设计

    在Microsoft Visual C++ (VC++)环境下,利用GDI+进行图像程序设计是一项常见的任务,GDI+是Windows操作系统提供的一种图形设备接口,它扩展了传统的GDI(Graphics Device Interface),增加了许多新的特性和功能,...

    GDI+特效---对话框

    GDI+,全称Graphics Device Interface Plus,是微软推出的一种图形设备接口的增强版本,用于Windows应用程序中的图形绘制。在本主题中,我们将探讨如何利用GDI+来创建具有特效的对话框,以及如何实现文件打开功能。 ...

Global site tag (gtag.js) - Google Analytics