在Photoshop中,图像色阶调整应用很广泛,本文介绍的图像色阶调整过程与Photoshop处理效果基本一致。
Photoshop的色阶调整分输入色阶调整和输出色阶调整,其中输入色阶调整有3个调整点,即通常所说的黑场、白场及灰场调整。
输入色阶调整的基本算法并不复杂,首先计算出白场与黑场的离差Diff,然后计算出像素各份量值与黑场的离差rgbDiff,如果rgbDiff<=0,像素各份量值等于0,否则,计算以rgbDiff与Diff的比值为底的灰场倒数的幂。用公式表示:
Diff = Highlight -Shadow
rgbDiff = RGB - Shadow
clRGB = Power(rgbDiff / Diff, 1 / Midtones)
其中Shadow为输入色阶低端数据(黑场),Highlight为输入色阶高端数据(白场), Midtones为输入色阶中间数据(灰场),Diff为二者的离差(必须大于1),RGB为调整前的像素分量值,clRGB为调整输入色阶后的像素分量值。
输出色阶调整更简单,首先计算输出色阶白场与黑场的离差与255的比值系数,然后用输入色阶调整后的像素分量值乘上这个系数,再加上输出黑场值即可。用公式表示:
outClRGB = clRGB * (outHighlight - outShadow) / 255 + outShadow
其中,outShadow为输出黑场,outHighlight为输出白场,outClRGB为全部色阶调整后的像素分量值。
前面已经提到输入色阶黑白场的离差必须大于1,而输入色阶并没有这个限制,输出黑白场的离差可以为负数,当输出黑场与白场完全颠倒时,输出色阶调整后的图片为原图片的负片。
色阶调整涉及四个通道,即R、G、B各分量通道及整体颜色通道,如果每个通道单独调整,将是比较麻烦和耗时的,本文采用色阶表替换法,可一次性完成所有四个通道的色阶调整。
下面是图像色阶调整的代码:
1 // 色阶项结构
2 typedef struct
3 {
4 UINT Shadow;
5 FLOAT Midtones;
6 UINT Highlight;
7 UINT OutShadow;
8 UINT OutHighlight;
9 }ColorLevelItem, *PColorLevelItem;
10
11 typedef struct
12 {
13 ColorLevelItem Blue;
14 ColorLevelItem Green;
15 ColorLevelItem Red;
16 ColorLevelItem RGB;
17 }ColorLevelData, *PColorLevelData;
18
19 VOID InitColorLevelData(PColorLevelData clData)
20 {
21 PColorLevelItem item = &clData->Blue;
22 for (INT i = 0; i < 4; i ++, item ++)
23 {
24 item->Shadow = item->OutShadow = 0;
25 item->Highlight = item->OutHighlight = 255;
26 item->Midtones = 1.0;
27 }
28 }
29
30 BOOL GetColorLevelTable(PColorLevelItem item, LPBYTE clTable)
31 {
32 INT diff = (INT)(item->Highlight - item->Shadow);
33 INT outDiff = (INT)(item->OutHighlight - item->OutShadow);
34
35 if (!((item->Highlight <= 255 && diff < 255 && diff >= 2) ||
36 (item->OutShadow <= 255 && item->OutHighlight <= 255 && outDiff < 255) ||
37 (!(item->Midtones > 9.99 && item->Midtones > 0.1) && item->Midtones != 1.0)))
38 return FALSE;
39
40 DOUBLE coef = 255.0 / diff;
41 DOUBLE outCoef = outDiff / 255.0;
42 DOUBLE exponent = 1.0 / item->Midtones;
43
44 for (INT i = 0; i < 256; i ++)
45 {
46 INT v;
47 // 计算输入色阶黑白场
48 if (clTable[i] <= (BYTE)item->Shadow)
49 v = 0;
50 else
51 {
52 v = (INT)((clTable[i] - item->Shadow) * coef + 0.5);
53 if (v > 255)
54 v = 255;
55 }
56 // 计算输入色阶灰场
57 v = (INT)(pow(v / 255.0, exponent) * 255.0 + 0.5);
58 // 计算输出色阶
59 clTable[i] = (BYTE)(v * outCoef + item->OutShadow + 0.5);
60 }
61 return TRUE;
62 }
63
64 BOOL CheckColorLevelData(PColorLevelData clData, BYTE clTables[][256])
65 {
66 BOOL result = FALSE;
67 INT i, j;
68 for (i = 0; i < 3; i ++)
69 {
70 for (j = 0; j < 256; j ++)
71 clTables[i][j] = (BYTE)j;
72 }
73 PColorLevelItem item = &clData->Blue;
74 for (i = 0; i < 3; i ++, item ++)
75 {
76 if (GetColorLevelTable(item, clTables[i]))
77 result = TRUE;
78 }
79 for (i = 0; i < 3; i ++)
80 {
81 if (!GetColorLevelTable(item, clTables[i]))
82 break;
83 result = TRUE;
84 }
85 return result;
86 }
87
88 // 图像数据色阶调整
89 VOID ImageColorLevel(BitmapData *dest, BitmapData *source, PColorLevelData clData)
90 {
91 PARGBQuad pd, ps;
92 UINT width, height;
93 INT dstOffset, srcOffset;
94 GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);
95
96 BYTE clTables[3][256];
97 if (CheckColorLevelData(clData, clTables))
98 {
99 for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
100 {
101 for (UINT x = 0; x < width; x ++, ps ++, pd ++)
102 {
103 pd->Blue = clTables[0][ps->Blue];
104 pd->Green = clTables[1][ps->Green];
105 pd->Red = clTables[2][ps->Red];
106 pd->Alpha = ps->Alpha;
107 }
108 }
109 }
110 else if (dest != source)
111 {
112 for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
113 {
114 for (UINT x = 0; x < width; x ++, ps ++, pd ++)
115 {
116 pd->Color = ps->Color;
117 }
118 }
119 }
120 }
下面给一个简单的图像色阶调整函数调用例子:
1 void __fastcall TForm1::Button1Click(TObject *Sender)
2 {
3 BitmapData dest, source;
4
5 Bitmap *sBmp = new Bitmap(L"..\\..\\media\\source1.jpg");
6 LockBitmap(sBmp, &source);
7
8 Bitmap *dBmp = new Bitmap(source.Width, source.Height, PixelFormat32bppARGB);
9 LockBitmap(dBmp, &dest);
10
11 ColorLevelData clData;
12 InitColorLevelData(&clData);
13
14 clData.RGB.Shadow = 10;
15 clData.RGB.Midtones = 1.2;
16 clData.RGB.Highlight = 240;
17 clData.RGB.OutShadow = 50;
18 clData.RGB.OutHighlight = 200;
19
20 /*
21 clData.RGB.OutShadow = 255;
22 clData.RGB.OutHighlight = 0;
23 */
24 ImageColorLevel(&dest, &source, &clData);
25
26 UnlockBitmap(dBmp, &dest);
27 UnlockBitmap(sBmp, &source);
28
29 Gdiplus::Graphics g(Canvas->Handle);
30 g.DrawImage(sBmp, 0, 0);
31 g.DrawImage(dBmp, source.Width, 0);
32
33 delete dBmp;
34 delete sBmp;
35 }
第一张效果图绿色通道色阶调整,第二张效果图是RGB输出色阶调整到完全颠倒时的负片图。
本文代码系用BCB XE7编辑和编译。