图像的饱和度调整有很多方法,最简单的就是判断每个象素的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++算法(只是随手写的算法,不是真正的运行代码):
inlinevoidSwapRGB(int&a,int&b)
{
a+=b;
b=a-b;
a-=b;
}
//利用HSL模式求得颜色的S和L
doublergbMax=R/255;
doublergbMin=G/255;
doublergbC=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;
doublevalue=rgbMax+rgbMin;
doubleS, 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+调用的饱和度调整过程:
-
type
-
-
TImageData=packedrecord
-
Width:LongWord;
-
Height:LongWord;
-
Stride:LongWord;
-
PixelFormat:LongWord;
-
Scan0:Pointer;
-
Reserved:LongWord;
-
end;
-
PImageData=^TImageData;
-
-
-
functionGetImageData(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.Widthshl2;
-
-
end;
-
-
procedureSaturation(Data:TImageData;Value:Integer);
-
asm
- pushebp
- pushesi
- pushedi
- pushebx
-
movedi,[eax+16]
-
movecx,[eax+4]
- imulecx,[eax]
- movebp,edx
- cld
-
@PixelLoop:
-
dececx
-
js@end
-
movzxeax,[edi+2]
-
movzxebx,[edi+1]
- movzxesi,[edi]
- cmpesi,ebx
-
jge@@1
- xchgesi,ebx
-
@@1:
- cmpesi,eax
-
jge@@2
- xchgesi,eax
-
@@2:
- cmpebx,eax
-
jle@@3
- movebx,eax
-
@@3:
-
moveax,esi
-
subeax,ebx
-
jnz@@4
-
addedi,4
-
jmp@PixelLoop
-
@@4:
- addesi,ebx
-
movebx,esi
-
shresi,1
-
cmpesi,128
-
jl@@5
-
negebx
-
addebx,510
-
@@5:
-
imuleax,255
- cdq
-
divebx
-
movebx,ebp
-
testebx,ebx
-
js@@10
- addbl,al
-
jnc@@6
-
movebx,eax
-
jmp@@7
-
@@6:
-
movebx,255
-
subebx,ebp
-
@@7:
-
moveax,65025
- cdq
-
divebx
-
subeax,255
-
movebx,eax
-
@@10:
- pushebp
-
movebp,255
- pushecx
-
movecx,3
-
@RGBLoop:
-
movzxeax,[edi]
- pusheax
-
subeax,esi
- imuleax,ebx
- cdq
- idivebp
- popedx
- addeax,edx
-
jns@@11
-
xoreax,eax
-
jmp@@12
-
@@11:
-
cmpeax,255
-
jle@@12
-
moveax,255
-
@@12:
-
stosb
-
loop@RGBLoop
- popecx
- popebp
-
incedi
-
jmp@PixelLoop
-
@end:
- popebx
- popedi
- popesi
- popebp
-
end;
-
procedureGdipSaturation(Bmp:TGpBitmap;Value:Integer);
-
var
- Data:TBitmapData;
-
begin
-
ifValue=0thenExit;
-
Data:=Bmp.LockBits(GpRect(0,0,Bmp.Width,Bmp.Height),[imRead,imWrite],pf32bppARGB);
-
try
- Saturation(TImageData(Data),Value);
-
finally
-
Bmp.UnlockBits(Data);
-
end;
-
end;
-
procedureBitmapSaturation(Bmp:TBitmap;Value:Integer);
-
begin
-
ifValue<>0then
- Saturation(GetImageData(Bmp),Value);
-
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
#defineRgbHsbH
#include<windows.h>
#include<algorithm>
usingstd::min;
usingstd::max;
#include<gdiplus.h>
usingnamespaceGdiplus;
voidSetRgbHsb(unsignedchar&R,unsignedchar&G,unsignedchar&B,
inthValue,intsValue,intbValue);
voidGdipHSBAdjustment(Bitmap*Bmp,inthValue,intsValue,intbValue);
//---------------------------------------------------------------------------
#endif
//rgbhsb.cpp
#pragmahdrstop
#include"RgbHsb.h"
//---------------------------------------------------------------------------
inlinevoidSwapRGB(int&a,int&b)
{
a+=b;
b=a-b;
a-=b;
}
inlinevoidCheckRGB(int&Value)
{
if(Value<0)Value=0;
elseif(Value>255)Value=255;
}
inlinevoidAssignRGB(unsignedchar&R,unsignedchar&G,unsignedchar&B,intrv,intgv,intbv)
{
R=rv;
G=gv;
B=bv;
}
voidSetRgbHsb(unsignedchar&R,unsignedchar&G,unsignedchar&B,inthValue,intsValue,intbValue)
{
intrgbMax=R;
intrgbMin=G;
intrgbC=B;
if(rgbMax<rgbC)
SwapRGB(rgbMax,rgbC);
if(rgbMax<rgbMin)
SwapRGB(rgbMax,rgbMin);
if(rgbMin>rgbC)
SwapRGB(rgbMin,rgbC);
intvalue=rgbMax+rgbMin;
intL=(value+1)>>1;
intH,S;
intdelta=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;
elseif(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;
elseif(H>360)H-=360;
intm=H%60;
H/=60;
if(H&1)m=60-m;
rgbC=(m*255+30)/60;
rgbC=rgbC-(rgbC-128)*(255-S)/255;
intLum=L-128;
if(Lum>0)
rgbC=rgbC+((255-rgbC)*Lum+64)/128;
elseif(Lum<0)
rgbC=rgbC+rgbC*Lum/128;
}
elseH/=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;
}
elseif(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)
{
case1:AssignRGB(R,G,B,rgbC,rgbMax,rgbMin);
break;
case2:AssignRGB(R,G,B,rgbMin,rgbMax,rgbC);
break;
case3:AssignRGB(R,G,B,rgbMin,rgbC,rgbMax);
break;
case4:AssignRGB(R,G,B,rgbC,rgbMin,rgbMax);
break;
case5:AssignRGB(R,G,B,rgbMax,rgbMin,rgbC);
break;
default:AssignRGB(R,G,B,rgbMax,rgbC,rgbMin);
}
}
//elseAssignRGB(R,G,B,rgbMax,rgbMin,rgbC);
}
voidGdipHSBAdjustment(Bitmap*Bmp,inthValue,intsValue,intbValue)
{
sValue=sValue*255/100;
bValue=bValue*255/100;
BitmapDatadata;
Rectr(0,0,Bmp->GetWidth(),Bmp->GetHeight());
Bmp->LockBits(&r,ImageLockModeRead|ImageLockModeWrite,PixelFormat24bppRGB,&data);
try
{
intoffset=data.Stride-data.Width*3;
unsignedchar*p=(unsignedchar*)data.Scan0;
for(inty=0;y<data.Height;y++,p+=offset)
for(intx=0;x<data.Width;x++,p+=3)
SetRgbHsb(p[2],p[1],*p,hValue,sValue,bValue);
}
__finally
{
Bmp->UnlockBits(&data);
}
}
#pragmapackage(smart_init)
//main.h
#ifndefmainH
#definemainH
//---------------------------------------------------------------------------
#include<Classes.hpp>
#include<Controls.hpp>
#include<StdCtrls.hpp>
#include<Forms.hpp>
#include<ComCtrls.hpp>
#include<ExtCtrls.hpp>
#include"RgbHsb.h"
//---------------------------------------------------------------------------
classTForm1:publicTForm
{
__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://Userdeclarations
public://Userdeclarations
__fastcallTForm1(TComponent*Owner);
__fastcall~TForm1(void);
};
//---------------------------------------------------------------------------
externPACKAGETForm1*Form1;
//---------------------------------------------------------------------------
#endif
//main.cpp
#include<vcl.h>
#pragmahdrstop
#include"main.h"
#include<stdlib.h>
//---------------------------------------------------------------------------
#pragmapackage(smart_init)
#pragmaresource"*.dfm"
TForm1*Form1;
ULONGgdiplusToken;
Bitmap*Bmp,*tmpBmp;
Gdiplus::Rectr;
boollock;
//---------------------------------------------------------------------------
__fastcallTForm1::TForm1(TComponent*Owner)
:TForm(Owner)
{
Gdiplus::GdiplusStartupInputgdiplusStartupInput;
GdiplusStartup(&gdiplusToken,&gdiplusStartupInput,NULL);
Bmp=newBitmap(WideString("100_0349.jpg"/*"d:/100_1.jpg"*/));
r=Gdiplus::Rect(0,0,Bmp->GetWidth(),Bmp->GetHeight());
tmpBmp=Bmp->Clone(r,PixelFormat24bppRGB);
DoubleBuffered=true;
lock=false;
}
__fastcallTForm1::~TForm1(void)
{
deletetmpBmp;
deleteBmp;
GdiplusShutdown(gdiplusToken);
}
//---------------------------------------------------------------------------
void__fastcallTForm1::PaintBox1Paint(TObject*Sender)
{
Gdiplus::Graphicsg(PaintBox1->Canvas->Handle);
g.DrawImage(tmpBmp,r);
g.TranslateTransform(0,r.Height);
g.DrawImage(Bmp,r);
}
//---------------------------------------------------------------------------
void__fastcallTForm1::HEditKeyPress(TObject*Sender,char&Key)
{
if(Key>=32&&(Key<48||Key>57))
Key=0;
}
//---------------------------------------------------------------------------
void__fastcallTForm1::HEditChange(TObject*Sender)
{
lock=true;
if(((TEdit*)Sender)->Text.Length()==0)
((TEdit*)Sender)->Text="0";
switch(((TEdit*)Sender)->Tag)
{
case0:HBar->Position=HEdit->Text.ToInt();
break;
case1:SBar->Position=SEdit->Text.ToInt();
break;
case2:BBar->Position=BEdit->Text.ToInt();
break;
}
lock=false;
deletetmpBmp;
tmpBmp=Bmp->Clone(r,PixelFormat24bppRGB);
if(HBar->Position||SBar->Position||BBar->Position)
GdipHSBAdjustment(tmpBmp,HBar->Position,SBar->Position,BBar->Position);
PaintBox1->Invalidate();
}
//---------------------------------------------------------------------------
void__fastcallTForm1::HBarChange(TObject*Sender)
{
if(!lock)
HEdit->Text=HBar->Position;
}
//---------------------------------------------------------------------------
void__fastcallTForm1::SBarChange(TObject*Sender)
{
if(!lock)
SEdit->Text=SBar->Position;
}
//---------------------------------------------------------------------------
void__fastcallTForm1::BBarChange(TObject*Sender)
{
if(!lock)
BEdit->Text=BBar->Position;
}
由于本人文化水平太差,虽摸索出这些算法,但却没法把它进一步说透,只好说些“独立”,“补丁”的字眼,让各位见笑了。如有错误请指正,有建议也请来信:maozefa@hotmail.com
后记:在GDI+下,32位PNG图像经转换为24位图像格式处理还原后,原有的透明色在转换过程中损失,故将本文对24位图像处理代码改为了32位图像处理代码。(2007.12.12)
说明:为了统一《GDI+ 在Delphi程序的应用》系列文章所用数据类型和图像处理格式,本文代码已作了修订,代码中所用Gdiplus单元下载地址及BUG更正见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。(2008.8.18记)
分享到:
相关推荐
GDI+入门指导书------经典 非常适合于GDI+初学者
windwos C++ gdi++实现jpg图像压缩-图像裁剪和缩放-图像格式转换-图像dpi修改
——在GDI+ Painter应用程序中添加颜色、钢笔和画笔 总结 第5章 颜色、字体和文本 5.1 访问Graphics对象 5.2 使用颜色 5.3 使用字体 5.4 使用文本和字符串 5.5 渲染文本的质量和性能 5.6 高级版式 5.7 一个...
DELPHI版的GDI++库,内有Demos.
使用GDI+进行图形缩放、拖动,多种图片格式支持,仅简单示例。 问题源贴:http://bbs.csdn.net/topics/390638094
使用Delphi+GDI实现图片的镜像翻转,有需要的可以试试。
WINDOWS GDI和GDI+编程实例剖析. - READ
——在GDI+ Painter应用程序中添加颜色、钢笔和画笔 总结 第5章 颜色、字体和文本 5.1 访问Graphics对象 5.2 使用颜色 5.3 使用字体 5.4 使用文本和字符串 5.5 渲染文本的质量和性能 5.6 高级版式 5.7 一个...
GDI+程序设计.pdf,书籍和随书源码。
GDI+程序设计 GDI+程序设计 GDI+程序设计 GDI+程序设计
教你在VC下如何使用GDI+进行 图像程序设计,是整理的比较全的资料。
一本为C#开发人员准备的图形图像处理技术的书籍
在Delphi中使用GDI+,范例中包含GDI+各种效果的测试。
其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus:: GdiplusShutdown来关闭GDI+。 (2)在应用类中添加...
Delphi GDI+
一本不可多得的GDI+绘图书籍,网上GDI+介绍其实挺少的,研究GDI+绘图时,浪费了不少时间,找了很久,发现了一本好书,在万分激动的心情下,将此书分享给大家,希望在GDI+绘图上给您一些指导。
GDI+对话框的显示,里面有5个特效和打开文件的代码