GDI+ 在Delphi程序的应用 -- 仿Photoshop的明度调整
来源:互联网 发布:淘宝划线价格怎么设置 编辑:程序博客网 时间:2024/04/29 23:52
这几天研究了一下Photoshop的色相/饱和度命令,也就是所谓的HSB颜色模式,没完全搞明白,网上搜索也没一点结果,看了一些介绍HSB算法的文章,其实讲的就是HSV或者HSL的算法。
关于PS色相/饱和度中的色相,就不用研究了,原理和HSV或者HSL的H都是一样的。
而饱和度在-100,0,+100这三点上的效果与HSL完全一样,其它范围就有区别了,特别是在0 -- +100范围,调整时比HSL的H调整要平坦,所以有效调整幅度较大,有些图片调整到+50%以上还不觉很大失真(这里的“失真”是针对颜色中难看的斑点来说的,并不是说整个图片不觉失真),而HSL的H的正向调整10%以上就很难看了;与HSV则没一点是相同的,可见PS的色相/饱和度算法应该是在HSL基础上改进的。
最令人困惑的是PS的明度调整,好像是“独立”于色相饱和度的。我们知道,要在程序中利用HSV或HSL模式调整V或者L,往往要先将RGB转换为HSV或HSL,或者至少要在其中将V或者L部分分离出来,修改后再转换为RGB模式(可参见我的文章《GDI+ 在Delphi程序的应用 -- 线性调整图像亮度 》分离HSL的L部分调整亮度),而PS的明度调整则不一样,完全不用转换RGB到所谓的HSB进行调整,直接写个函数就可以了,请看下面的Delphi过程及测试代码(分别用GDI+的TGpBitmap和Delphi的TBitmap测试),用于模仿PS明度调整(严格的说不叫模仿,而是实实在在的PS明度调整过程):
说明:为了统一《GDI+ 在Delphi程序的应用》系列文章所用数据类型和图像处理格式,本文代码已作了修订,代码中所用Gdiplus单元下载地址及BUG更正见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。(2008.8.18记)
- type
- // 与GDI+ TBitmapData结构兼容的图像数据结构
- TImageData = packed record
- Width: LongWord; // 图像宽度
- Height: LongWord; // 图像高度
- Stride: LongWord; // 图像扫描线字节长度
- PixelFormat: LongWord; // 未使用
- Scan0: Pointer; // 图像数据地址
- Reserved: LongWord; // 保留
- end;
- PImageData = ^TImageData;
- // 获取TBitmap图像的TImageData数据结构,便于处理TBitmap图像
- function GetImageData(Bmp: TBitmap): TImageData;
- begin
- Bmp.PixelFormat := pf32bit;
- Result.Width := Bmp.Width;
- Result.Height := Bmp.Height;
- Result.Scan0 := Bmp.ScanLine[Bmp.Height - 1];
- Result.Stride := Result.Width shl 2;
- // Result.Stride := (((32 * Bmp.Width) + 31) and $ffffffe0) shr 3;
- end;
过程代码:
- // 调整图象明度
- procedure PSBrightness(Data: TImageData; Value: Integer);
- asm
- push ebp
- push esi
- push edi
- push ebx
- mov edi, [eax + 16] // edi = Data.Scan0
- mov ebp, [eax + 4] // edp = Data.Height * Data.Width
- imul ebp, [eax]
- mov esi, edx // esi = Value
- mov ebx, 255 // ebx = 255
- cld
- @PixelLoop: // for (i = ebp; i > 0; i --)
- mov ecx, 3 // {
- @vLoop: // for (j = 3; j > 0; j --)
- movzx eax, [edi] // {
- push eax
- test esi, esi
- js @@1
- neg eax // if (Value > 0)
- add eax, ebx // rgb = rgb + (255 - rgb) * Value / 255
- @@1:
- imul eax, esi // else
- cdq // rgb = rgb + rgb * Value / 255
- idiv ebx
- pop edx
- add eax, edx
- jns @@2 // rgb = max(0, min(255, rgb))
- xor eax, eax
- jmp @@3
- @@2:
- cmp eax, ebx
- jle @@3
- mov eax, ebx
- @@3:
- stosb // *edi ++ = rgb
- loop @vLoop // }
- inc edi // edi ++
- dec ebp
- jnz @PixelLoop // }
- pop ebx
- pop edi
- pop esi
- pop ebp
- end;
- // 调整GDI+图象明度
- procedure GdipPSBrightness(Bmp: TGpBitmap; Value: Integer);
- var
- Data: TBitmapData;
- begin
- if Value = 0 then Exit;
- Data := Bmp.LockBits(GpRect(0, 0, Bmp.Width, Bmp.Height), [imRead, imWrite], pf32bppARGB);
- try
- PSBrightness(TImageData(Data), Value);
- finally
- Bmp.UnlockBits(Data);
- end;
- end;
- // 调整TBitmap图象明度
- procedure BitmapPSBrightness(Bmp: TBitmap; Value: Integer);
- begin
- if Value <> 0 then
- HSLBrightness(GetImageData(Bmp), Value);
- end;
可以看出,上面的PSBrightness过程没有依赖任何颜色模式转换,而是采用了下面这个伪代码公式:
RGB = RGB + (255 - RGB) * value / 255;
else
RGB = RGB + RGB * value / 255;
其中RGB分别表示颜色的R、G、B,value为明度值。那么这个公式的含义是什么的,其实就是HSL转换为RGB的L部分的公式变形,我在《GDI+ 在Delphi程序的应用 -- 线性调整图像亮度 》中采用的公式和它形式是一样的,只是计算基数不同 :
if (L >= 0)
RGB = RGB + (255 - RGB) * L / 128;
else
RGB = RGB + RGB * L / 128;
前者使用的是value的全范围255或者100%(公式后的/255改为/100),而后者采用的是L的1/2,也就是128或者50%(就这点区别,效果可就大相径庭了),而且要利用L调整亮度,必须从HSL空间中获取L再加上调整值,而PS明度调整则不需要从HSB的得到原来的B,这就意味着B在HSB中始终为“0”!虽然PS的明度调整是和色相/饱和度调整放在一起的,但完全不依赖于色相/饱和度,这就是我感觉其好像是“独立”的原因,也是研究PS饱和度算法不果的重要原因(一个B=0的HSB模式,光靠HS部分怎样正确转换为R、G、B?要知道HSV的V和HSL中的L在正确转换为RGB模式中至关重要!)。
假如我的感觉是正确的,那么PS“独立”的明度调整又是什么原理呢?我们知道,一般的非线性RGB亮度调整只是在原有R、G、B值基础上增加和减少一定量来实现的,而PS的明度调整原理还得从前面那个公式上去找。我们将正向明度调整公式:RGB = RGB + (255 - RGB) * value / 255转换为RGB = (RGB * (255 - value) + 255 * value) / 255,如果value用1表示最大值255,则为RGB = RGB * (1 - value) + 255 * value,可以看出什么呢?凡是知道图像合成的人都知道这个公式,其实PS的明度调整是采用Alpha合成方式,这里的value就是Alpha,公式前面部分RGB * (1 - value)的是图像部分,后面的255 * value部分则是一个白色遮照层,明度越大,遮照层的Alpha越大,图像就越谈,反之亦然。而明度的负调整则是以一个黑色遮照层来完成的。负100%就全黑了。只有遮照层Alpha=0,也就是明度值为0时,才是完完全全的图片显示。要验证上面的说法很简单,一是运行我的测试代码,而是在PS中,用一个全白或全黑图层覆盖在一张图片上,调整这个层的不透明度,可以看出和明度调整效果完全一样!
其实,我只是对PS的饱和度调整感兴趣,原因前面已经说了,比HSV和HSL的饱和度调整效果要好,范围要大,饱和度算法没研究出来,到搞了个明度调整过程。希望知道PS饱和度算法的朋友不吝赐教,本人不甚感激!
测试代码:
- procedure TForm1.Button1Click(Sender: TObject);
- var
- Image: TGpBitmap;
- g: TGpGraphics;
- begin
- Image := TGpBitmap.Create('D:/VclLib/GdiplusDemo/Media/20041001.jpg');
- g := TGpGraphics.Create(Handle, False);
- g.DrawImage(Image, 10, 10);
- GdipPSBrightness(Image, 30);
- g.DrawImage(Image, 10, 220);
- Image.Free;
- g.Free;
- end;
- procedure TForm1.Button2Click(Sender: TObject);
- var
- Image: TBitmap;
- begin
- Image := TBitmap.Create;
- Image.LoadFromFile('D:/VclLib/GdiplusDemo/Media/20041001.bmp');
- Canvas.Draw(10, 10, Image);
- BitmapPSBrightness(Image, -30);
- Canvas.Draw(10, 220, Image);
- Image.Free;
- end;
如有错误请来信指正:maozefa@hotmail.com
- GDI+ 在Delphi程序的应用 -- 仿Photoshop的明度调整
- GDI+在Delphi程序的应用 – Photoshop色相/饱和度/明度功能
- GDI+ 在Delphi程序的应用 -- 调整图像亮度
- GDI+ 在Delphi程序的应用 -- 线性调整图像亮度
- GDI+ 在Delphi程序的应用 -- 图像饱和度调整
- GDI+ 在Delphi程序的应用 -- FontCollection
- GDI+ 在Delphi程序的应用 -- Matrix应用心得
- GDI+ 在Delphi程序的应用 -- GDI+图像与GDI位图的相互转换
- GDI+ 在Delphi程序的应用 -- GDI+图像与GDI位图的相互转换
- GDI+ 在Delphi程序的应用 -- 图像二值化
- GDI+ 在Delphi程序的应用 -- ColorMatrix与图像亮度
- GDI+ 在Delphi、C++Builder程序的应用 -- IStream
- GDI+ 在Delphi程序的应用 -- 图像二值化
- GDI+ 在Delphi程序的应用 -- 制作水印效果图片
- GDI+ 在Delphi程序的应用 -- 制作水印效果图片
- GDI+ 在Delphi程序的应用 -- 图像的透明显示技巧
- GDI+ 在Delphi程序的应用 -- 可调节的文字阴影特效
- GDI+ 在Delphi程序的应用 -- 多帧(页)图像的分解与合成
- JavaScript——世界上误解最深的程序设计语言
- MANIFEST.MF 文件内容详解
- oracle故障解决日志
- 利用IHttpHandler计算页面的执行时间
- 微软1.3万员工使用Facebook 盖茨身影浮现
- GDI+ 在Delphi程序的应用 -- 仿Photoshop的明度调整
- Silverlight:你需要知道的十件事情
- MFC教程(1)--MFC概述
- 强化培训9.4——核心网
- 昨晚喝醉了
- 2007.09.4 晴
- 2007-9-4高赔付
- concat
- the differences of DataRelation class between 1.1 and 2.0