GDI+ 在Delphi程序的应用 -- 图像饱和度调整

图像的饱和度调整有很多方法,最简单的就是判断每个象素的R、G、B值是否大于或小于128,大于加上调整值,小于则减去调整值;也可将象素RGB转换为HSV或者HSL,然后调整其S部分,从而达到线性调整图象饱和度的目的。这几种方法我都测试过,效果均不太好,简单的就不说了,利用HSV和HSL调整饱和度,其调节范围很窄,饱和度没达到,难看的色斑却出现了。而Photoshop的饱和度调整调节范围大多了,效果也好多了,请看下面25%饱和度调整时几种方法的效果对比图:

可以看出,都是25%的饱和度调整,Photoshop的调节幅度显得小一些(平坦些),效果也好多了,而HSV和HSL均出现了色斑,某些颜色也严重失真,尤其是HSV方式。

据网上和书上的介绍,Photoshop的是利用所谓HSB颜色模式实现色相/饱和度调节的,可是就是没有看到其算法,我只得自己进行琢磨,首先发现Photoshop色相/饱和度命令中的明度调节好象是“独立”的,也就是它不需要转换为所谓的HSB模式,直接靠白色和黑色遮照层进行调节,具体原理和代码可看我写的《GDI+ 在Delphi程序的应用 -- 仿Photoshop的明度调整》一文。后来,却又发现Photoshop的饱和度调节好象是“半独立的”,什么意思呢?就是说Photoshop的色相/饱和度的调整还是转换为HSL颜色模式进行的,只是饱和度的增减调节却是“独立”于SHL模式的另外一套算法,如果不是需要HSL的S和L部分进行饱和度的上下限控制,它也和明度调整一样,可以独立进行!下面是我写的C++算法(只是随手写的算法,不是真正的运行代码):


inline void SwapRGB( int & a, int & b)
{
a
+= b;
b
= a - b;
a
-= b;
}

// 利用HSL模式求得颜色的S和L
double rgbMax = R / 255 ;
double rgbMin = G / 255 ;
double rgbC = B / 255 ;
if (rgbMax < rgbC)
SwapRGB(rgbMax,rgbC);
if (rgbMax < rgbMin)
SwapRGB(rgbMax,rgbMin);
if (rgbMin > rgbC)
SwapRGB(rgbMin,rgbC);
doubledelta=rgbMax-rgbMin;
// 如果delta=0,S=0,所以不能调整饱和度
if(delta==0) return;

double value = rgbMax + rgbMin;
double S, L = value / 2 ;
if (L < 0.5 )
S
= delta / value;
else
S
= delta / ( 2 - value);
// 具体的饱和度调整,sValue为饱和度增减量
// 如果增减量>0,饱和度呈级数增强,否则线性衰减
if (sValue > 0 )
{
// 如果增减量+S>1,用S代替增减量,以控制饱和度的上限
// 否则取增减量的补数
sValue = sValue + S >= 1 ? S: 1 - sValue;
// 求倒数-1,实现级数增强
sValue = 1 / sValue - 1 ;
}
// L在此作饱和度下限控制
R = R + (R - L * 255 ) * sValue;
G
= G + (G - L * 255 ) * sValue;
B
= B + (B - L * 255 ) * sValue;

从上面的算法代码中可以看到,Photoshop的饱和度调整没有像HSV和HSL的饱和度调整那样,将S加上增减量重新计算,并将HSL转换回RGB,而只是取得了颜色的S、L作为上下限控制,对原有的RGB进行了“补丁”式的调节。

下面是根据以上算法写的Delphi的BASM代码和GDI+调用的饱和度调整过程:

  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. //图像饱和度调整。Value:(-255-+255,没作范围检查)
  2. procedureSaturation(Data:TImageData;Value:Integer);
  3. asm
  4. pushebp
  5. pushesi
  6. pushedi
  7. pushebx
  8. movedi,[eax+16]//edi=Data.Scan0
  9. movecx,[eax+4]//ecx=Data.Width*Data.Height
  10. imulecx,[eax]
  11. movebp,edx
  12. cld
  13. @PixelLoop://for(i=ecx-1;i>=0;i--)
  14. dececx//{
  15. js@end
  16. movzxeax,[edi+2]
  17. movzxebx,[edi+1]
  18. movzxesi,[edi]
  19. cmpesi,ebx
  20. jge@@1
  21. xchgesi,ebx
  22. @@1:
  23. cmpesi,eax
  24. jge@@2
  25. xchgesi,eax
  26. @@2:
  27. cmpebx,eax
  28. jle@@3
  29. movebx,eax
  30. @@3:
  31. moveax,esi//delta=varMax-varMin
  32. subeax,ebx//if(delta==0)
  33. jnz@@4//{
  34. addedi,4//edi+=4
  35. jmp@PixelLoop//continue
  36. @@4://}
  37. addesi,ebx
  38. movebx,esi//ebx=varMax+varMin
  39. shresi,1//esi=L=(varMax+varMin)/2
  40. cmpesi,128
  41. jl@@5
  42. negebx//if(L>=128)ebx=510-ebx
  43. addebx,510
  44. @@5:
  45. imuleax,255//eax=S=delta*255/ebx
  46. cdq
  47. divebx
  48. movebx,ebp//ebx=value
  49. testebx,ebx//if(ebx>0)
  50. js@@10//{
  51. addbl,al
  52. jnc@@6//if(ebx+S>=255)
  53. movebx,eax//ebx=S
  54. jmp@@7
  55. @@6:
  56. movebx,255
  57. subebx,ebp//elseebx=255-value
  58. @@7:
  59. moveax,65025//ebx=65025/ebx-255
  60. cdq
  61. divebx
  62. subeax,255
  63. movebx,eax//}
  64. @@10:
  65. pushebp
  66. movebp,255
  67. pushecx
  68. movecx,3
  69. @RGBLoop://for(j=3;j>0;j--)
  70. movzxeax,[edi]//{
  71. pusheax
  72. subeax,esi//rgb=rgb+(rgb-L)*ebx/255
  73. imuleax,ebx
  74. cdq
  75. idivebp
  76. popedx
  77. addeax,edx
  78. jns@@11
  79. xoreax,eax//if(rgb<0)rgb=0
  80. jmp@@12
  81. @@11:
  82. cmpeax,255
  83. jle@@12
  84. moveax,255//elseif(rgb>255)rgb=255
  85. @@12:
  86. stosb//*edi++=rgb
  87. loop@RGBLoop//}
  88. popecx
  89. popebp
  90. incedi//edi++
  91. jmp@PixelLoop//}
  92. @end:
  93. popebx
  94. popedi
  95. popesi
  96. popebp
  97. end;
  98. procedureGdipSaturation(Bmp:TGpBitmap;Value:Integer);
  99. var
  100. Data:TBitmapData;
  101. begin
  102. ifValue=0thenExit;
  103. Data:=Bmp.LockBits(GpRect(0,0,Bmp.Width,Bmp.Height),[imRead,imWrite],pf32bppARGB);
  104. try
  105. Saturation(TImageData(Data),Value);
  106. finally
  107. Bmp.UnlockBits(Data);
  108. end;
  109. end;
  110. procedureBitmapSaturation(Bmp:TBitmap;Value:Integer);
  111. begin
  112. ifValue<>0then
  113. Saturation(GetImageData(Bmp),Value);
  114. end;

具体的测试代码就不写了,有兴趣者可参考我的《GDI+ 在Delphi程序的应用 -- 线性调整图像亮度》、《GDI+ 在Delphi程序的应用 -- 图像卷积操作及高斯模糊》和《GDI+ 在Delphi程序的应用 -- 图像亮度/对比度调整》等文章,写出GDI+的TGpBitmap和Delphi的TBitmap的测试代码,其运行结果与Photoshop完全一样。  

  对于色相的调整,HSV、HSL和HSB都是相同的,不同的只是饱和度和亮度(明度)的调整,前天我已经写了《GDI+ 在Delphi程序的应用 -- 仿Photoshop的明度调整》,加上这篇饱和度算法文章,是否意味Photoshop的HSB算法完全破解了呢?不然,Photoshop的饱和度和明度调整独立使用时,确实是我说的那样,与Photoshop效果完全一样,但是放在一起进行调节就有区别了,这里有个谁先谁后的时机问题,和我前天写的《GDI+ 在Delphi程序的应用 -- 图像亮度/对比度调整》中对比度和亮度关系一样,各自独立使用没问题,放在一起调整就麻烦,但是对比度和亮度的关系比较简单,几次测试就清楚了,而饱和度和明度的关系我试验过多次,均与Photoshop有区别(只是有区别而以,其效果不比Photoshop的差多少),所以,要完全破解,还得试验,如果有谁知道,请务必告知,本人在此先谢了。下面干脆把我用BCB6写的试验性代码完整的贴在这,有兴趣的朋友可以帮忙试验,这个试验代码写的很零乱,运行也不快,先调整饱和度,再调整明度,其他方式没成功,所以没保存结果。

// rgbhsb.h

#ifndefRgbHsbH
#define RgbHsbH

#include
< windows.h >
#include
< algorithm >
using std::min;
using std::max;
#include
< gdiplus.h >
using namespace Gdiplus;

void SetRgbHsb(unsigned char & R,unsigned char & G,unsigned char & B,
int hValue, int sValue, int bValue);
void GdipHSBAdjustment(Bitmap * Bmp, int hValue, int sValue, int bValue);

// ---------------------------------------------------------------------------
#endif

// rgbhsb.cpp

#pragma hdrstop

#include
" RgbHsb.h "

// ---------------------------------------------------------------------------

inline
void SwapRGB( int & a, int & b)
{
a
+= b;
b
= a - b;
a
-= b;
}

inline
void CheckRGB( int & Value)
{
if (Value < 0 )Value = 0 ;
else if (Value > 255 )Value = 255 ;
}

inline
void AssignRGB(unsigned char & R,unsigned char & G,unsigned char & B, int rv, int gv, int bv)
{
R
= rv;
G
= gv;
B
= bv;
}

void SetRgbHsb(unsigned char & R,unsigned char & G,unsigned char & B, int hValue, int sValue, int bValue)
{
int rgbMax = R;
int rgbMin = G;
int rgbC = B;
if (rgbMax < rgbC)
SwapRGB(rgbMax,rgbC);
if (rgbMax < rgbMin)
SwapRGB(rgbMax,rgbMin);
if (rgbMin > rgbC)
SwapRGB(rgbMin,rgbC);
int value = rgbMax + rgbMin;
int L = (value + 1 ) >> 1 ;
int H,S;
int delta = rgbMax - rgbMin;
if ( ! delta)
H
= S = 0 ;
else
{
if (L < 128 )
S
= delta * 255 / value;
else
S
= delta * 255 / ( 510 - value);
if (rgbMax == R)
H
= (G - B) * 60 / delta;
else if (rgbMax == G)
H
= (B - R) * 60 / delta + 120 ;
else
H
= (R - G) * 60 / delta + 240 ;
if (H < 0 )H += 360 ;
if (hValue)
{
H
+= hValue;
if (H < 0 )H += 360 ;
else if (H > 360 )H -= 360 ;
int m = H % 60 ;
H
/= 60 ;
if (H & 1 )m = 60 - m;
rgbC
= (m * 255 + 30 ) / 60 ;
rgbC
= rgbC - (rgbC - 128 ) * ( 255 - S) / 255 ;
int Lum = L - 128 ;
if (Lum > 0 )
rgbC
= rgbC + (( 255 - rgbC) * Lum + 64 ) / 128 ;
else if (Lum < 0 )
rgbC
= rgbC + rgbC * Lum / 128 ;
}
else H /= 60 ;
if (sValue)
{
if (sValue > 0 )
{
sValue
= sValue + S >= 255 ? S: 255 - sValue;
sValue
= 65025 / sValue - 255 ;
}
rgbMax
= rgbMax + (rgbMax - L) * sValue / 255 ;
rgbMin
= rgbMin + (rgbMin - L) * sValue / 255 ;
rgbC
= rgbC + (rgbC - L) * sValue / 255 ;
}
}
if (bValue > 0 )
{
rgbMax
= rgbMax + ( 255 - rgbMax) * bValue / 255 ;
rgbMin
= rgbMin + ( 255 - rgbMin) * bValue / 255 ;
rgbC
= rgbC + ( 255 - rgbC) * bValue / 255 ;
}
else if (bValue < 0 )
{
rgbMax
= rgbMax + rgbMax * bValue / 255 ;
rgbMin
= rgbMin + rgbMin * bValue / 255 ;
rgbC
= rgbC + rgbC * bValue / 255 ;
}
CheckRGB(rgbMax);
CheckRGB(rgbMin);
CheckRGB(rgbC);
if (bValue || S)
{
switch (H)
{
case 1 :AssignRGB(R,G,B,rgbC,rgbMax,rgbMin);
break ;
case 2 :AssignRGB(R,G,B,rgbMin,rgbMax,rgbC);
break ;
case 3 :AssignRGB(R,G,B,rgbMin,rgbC,rgbMax);
break ;
case 4 :AssignRGB(R,G,B,rgbC,rgbMin,rgbMax);
break ;
case 5 :AssignRGB(R,G,B,rgbMax,rgbMin,rgbC);
break ;
default :AssignRGB(R,G,B,rgbMax,rgbC,rgbMin);
}
}
// elseAssignRGB(R,G,B,rgbMax,rgbMin,rgbC);
}

void GdipHSBAdjustment(Bitmap * Bmp, int hValue, int sValue, int bValue)
{
sValue
= sValue * 255 / 100 ;
bValue
= bValue * 255 / 100 ;
BitmapDatadata;
Rectr(
0 , 0 ,Bmp -> GetWidth(),Bmp -> GetHeight());
Bmp
-> LockBits( & r,ImageLockModeRead | ImageLockModeWrite,PixelFormat24bppRGB, & data);
try
{
int offset = data.Stride - data.Width * 3 ;
unsigned
char * p = (unsigned char * )data.Scan0;
for ( int y = 0 ;y < data.Height;y ++ ,p += offset)
for ( int x = 0 ;x < data.Width;x ++ ,p += 3 )
SetRgbHsb(p[
2 ],p[ 1 ], * p,hValue,sValue,bValue);
}
__finally
{
Bmp
-> UnlockBits( & data);
}
}

#pragma package(smart_init)

// main.h

#ifndefmainH
#define mainH
// ---------------------------------------------------------------------------
#include < Classes.hpp >
#include
< Controls.hpp >
#include
< StdCtrls.hpp >
#include
< Forms.hpp >
#include
< ComCtrls.hpp >
#include
< ExtCtrls.hpp >
#include
" RgbHsb.h "
// ---------------------------------------------------------------------------
class TForm1: public TForm
{
__published:
// IDE-managedComponents
TButton * Button1;
TLabel
* Label1;
TLabel
* Label2;
TLabel
* Label3;
TTrackBar
* HBar;
TTrackBar
* SBar;
TTrackBar
* BBar;
TEdit
* HEdit;
TEdit
* SEdit;
TEdit
* BEdit;
TPaintBox
* PaintBox1;
void __fastcallPaintBox1Paint(TObject * Sender);
void __fastcallHEditKeyPress(TObject * Sender, char & Key);
void __fastcallHEditChange(TObject * Sender);
void __fastcallHBarChange(TObject * Sender);
void __fastcallSBarChange(TObject * Sender);
void __fastcallBBarChange(TObject * Sender);
private : //
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值