网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 网络学院 >> 程序设计 >> Delphi编程 >> 文章正文
  GDI+ 在Delphi程序的应用 -- 线性调整图像亮度            【字体:
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
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的代码附在下面,有兴趣的朋友可以测试一下:

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(00, 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(
1950);
    g.DrawImage(bmp1, r);

    g.TranslateTransform(
1950);
    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(
00, bmp1);
    data1 :
= GetImageData(bmp1);
    Brightness(data1, value);
    Canvas.Draw(
1950, bmp1);
    data2 :
= GetImageData(bmp2);
    HSLBrightness(data2, value);
    Canvas.Draw(
3900, 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 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
     在delphi中使用xml文档有…
     初探delphi 7 中的插件编…
     delphi 2006(dexter) & …
     获得windows的版本信息。
     “序列号输入助手”源代…
     rs232串口通讯模块
     ado方式下判断数据表是否…
  • Java的网络编程(TCP/IP)

  • Ant入门-配置和使用     选…

  • 浅析Spring框架下PropertyPl…

  • jsp重定向forward和sendRedi…

  • MVP——Model-Viewer-Presen…

  • C++ Object Model

  • Solaris10下,使用SunStudio…

  • 内存管理内幕--Jonathan Bar…

  • constructor and destructor

  • delegate C#关键字 (委托类型…

  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    网络学院©2007 www.23book.net
    为您提供web编程,vb编程,vc编程,服务器架设管理,数据库设计等方面的知识 站长:David