阅读提示:
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括《C++图像处理 -- 数据类型及公用函数》文章中的BmpData.h头文件。
在GDI+中,颜色矩阵变换是处理图像颜色的重要手段,包括颜色的缩放、剪切、旋转和平移。在GDI+中,颜色矩阵ColorMatrix定义为5*5的二维浮点数数组,其排列如下图:
按照颜色矩阵变换的功能,下面分别列出缩放、旋转、剪切、平移和全部变换的公式,在公式中,大写ARGB表示颜色各分量现有的值,而小写argb表示运算后得到的新值。
1、颜色缩放:颜色缩放很简单,就是按照给定的比例值,在图像像素现有A、R、G、B各分量数值基础上计算出新的分量值。这个比例值就是ColorMatrix主对角线除去m55外的其它4个值:
r = R * m11
g = G * m22
b = B * m33
a = A * m44
2、颜色剪切:一般说来,图像像素R、G、B各分量按照与另一种颜色分量成比例的量来增加或减少颜色分量就是剪切。其实这种表述并不完全,像素的A分量也是参与其中的!
以红色分量R举例,如果要按绿色分量G进行剪切,那么m21就是剪切比例值,m21 * G就得到了G对R的剪切量。同理,m31 * B、m41 * A可分别得到B和A对R的剪切量,将这些剪切量加起来,就是R总的剪切量。用公式表示为:
r = G * m21 + B * m31 + A * m41
g = R * m12 + B * m32 + A * m42
b = R * m13 + G * m23 + A * m43
a = R * m14 + G * m24 + B * m34
3、颜色旋转:颜色旋转的描述比较复杂,就是在图像像素中,用其中的2个分量按照一定的角度围绕另外1个分量作运算的结果,就是颜色的旋转。以红色分量R和绿色分量G围绕蓝色分量G旋转60度为例:
m11 = cos(60) = 0.5, m12 = sin(60) = 0.866, m21 = -sin(60) = -0.866, m22 = cos(60) = 0.5,那么,R和G 所得到的旋转量分别为:
r = R * m11(0.5) + G * m21(-0.866)
g = R * m12(0.866) + G * m22(0.5)
从上面的公式看,所谓的颜色旋转量,其实就是旋转的2个分量自身的缩放量加上与对方的剪切量而已!就运算角度看,同其它分量没有任何关系。
4、颜色平移:上面的缩放、剪切和旋转属于颜色的线性变换(都是乘法运算的累积和),而平移是颜色的非线性变换,就是对颜色各分量做一个加法而已:图像像素各分量的平移量用所谓的虚拟位,即第5行的各个值来表示,各分量加上所在列的虚拟行的值就是颜色平移,其实质就是非线性地调整了该分量的亮度值。用公式表示各分量的平移量:
r = R + m51 * 255;
g = G + m52 * 255;
b = B + m53 * 255;
a = A + m54 * 255;
5、完全变换:综合以上颜色的缩放、旋转、剪切及平移公式,颜色矩阵完全变换的公式为:
r = R * m11 + G * m21 + B * m31 + A * m41 + m51 * 255
g = R * m12 + G * m22 + B * m32 + A * m42 + m52 * 255
b = R * m13 + G * m23 + B * m33 + A * m43 + m53 * 255
a = R * m14 + G * m24 + B * m34 + A * m44 + m54 * 255
从技术层面上讲,这个公式的含义就是颜色每个分量的新值,等于这个分量在ColorMatrix中对应列的前4行的值与R、G、A、B当前值的乘积之和加上第5行的值与常数255的乘积,而虚拟列(第5列)不起任何作用。
下面是实现的颜色矩阵变换代码。
//---------------------------------------------------------------------------
FORCEINLINE
INT CheckValue(INT value)
{
return (value & ~0xff) == 0? value : value > 255? 255 : 0;
}
//---------------------------------------------------------------------------
VOID ImageSetColorMatrix(BitmapData *dest, CONST BitmapData *source, ColorMatrix *matrix)
{
INT im[5][5];
BOOL scale = TRUE;
for (INT i = 0; i < 5; i ++)
{
for (INT j = 0; j < 4; j ++) // 不包括虚拟列
{
if (i == 4) // 平移
im[4][j] = (INT)(matrix->m[4][j] * 255 + 0.5);
else // 缩放和剪切
im[i][j] = (INT)(matrix->m[i][j] * 256 + 0.5);
if (i != j && im[i][j])
scale = FALSE;
}
}
// 获取数据处理参数
PARGBQuad pd, ps;
UINT width, height;
INT dstOffset, srcOffset;
GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);
if (scale) // 处理缩放
{
for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
{
for (UINT x = 0; x < width; x ++, ps ++, pd ++)
{
pd->Blue = CheckValue((ps->Blue * im[2][2]) >> 8);
pd->Green = CheckValue((ps->Green * im[1][1]) >> 8);
pd->Red = CheckValue((ps->Red * im[0][0]) >> 8);
pd->Alpha = CheckValue((ps->Alpha * im[3][3]) >> 8);
}
}
}
else // 处理全部颜色变换
{
for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
{
for (UINT x = 0; x < width; x ++, ps ++, pd ++)
{
pd->Blue = CheckValue(((
ps->Blue * im[2][2] +
ps->Green * im[1][2] +
ps->Red * im[0][2] +
ps->Alpha * im[3][2]) >> 8) +
im[4][2]);
pd->Green = CheckValue(((
ps->Blue * im[2][1] +
ps->Green * im[1][1] +
ps->Red * im[0][1] +
ps->Alpha * im[3][1]) >> 8) +
im[4][1]);
pd->Red = CheckValue(((
ps->Blue * im[2][0] +
ps->Green * im[1][0] +
ps->Red * im[0][0] +
ps->Alpha * im[3][0]) >> 8) +
im[4][0]);
pd->Alpha = CheckValue(((
ps->Blue * im[2][3] +
ps->Green * im[1][3] +
ps->Red * im[0][3] +
ps->Alpha * im[3][3]) >> 8) +
im[4][3]);
}
}
}
}
//---------------------------------------------------------------------------
VOID ImageSetColorMatrix(BitmapData *data, ColorMatrix *matrix)
{
ImageSetColorMatrix(data, data, matrix);
}
//---------------------------------------------------------------------------
代码完全用C++写的,同《Delphi图像处理 -- 颜色矩阵变换》的汇编代码比,效率要低些。
需要说明的是,本文的颜色矩阵变换效果同《Delphi图像处理 -- 颜色矩阵变换》中实现效果有区别,这并非语言的差异,而是《Delphi图像处理 -- 颜色矩阵变换》是仿照的Window XP环境下GDI+颜色矩阵变换效果,而本文代码实现的是Window 7环境下GDI+颜色矩阵变换的效果,也就是说,Windows 7和Window XP的GDI+颜色矩阵变换是有些不同的,主要是在数据饱和处理方法上的区别,XP在缩放变换时是采用的截断方式,其它变换是饱和方式,而Win7却全部是饱和方式,而且饱和精度要高于XP。特别是变换结果为负数的时候最为明显,如大家在XP下经常用GDI+颜色矩阵RGB主对角线为-1的缩放形式来显示反色图像,在Win7环境下却是全黑!其实利用GDI+颜色矩阵RGB柱对角线为-1显示反色图本身就应该是个BUG,这一点我很早就在文章《GDI+ ColorMatrix的完全揭秘》中指出过。
下面是用BCB写的同《Delphi图像处理 -- 颜色矩阵变换》中类似的例子程序,其中依然保留了“反色”按钮,当然,运行时是黑色图而不是反色图:
头文件:
//---------------------------------------------------------------------------
#ifndef mainH
#define mainH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Buttons.hpp>
#include <ExtCtrls.hpp>
#include <Grids.hpp>
#include "BmpData.h"
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
TPaintBox *PaintBox1;
TSpeedButton *SpeedButton1;
TSpeedButton *SpeedButton2;
TSpeedButton *SpeedButton3;
TSpeedButton *SpeedButton4;
TStringGrid *StringGrid1;
TBitBtn *BitBtn1;
TBitBtn *BitBtn3;
TBitBtn *BitBtn2;
void __fastcall FormCreate(TObject *Sender);
void __fastcall FormDestroy(TObject *Sender);
void __fastcall BitBtn1Click(TObject *Sender);
void __fastcall BitBtn2Click(TObject *Sender);
void __fastcall BitBtn3Click(TObject *Sender);
void __fastcall PaintBox1Paint(TObject *Sender);
void __fastcall StringGrid1DrawCell(TObject *Sender, int ACol, int ARow, TRect &Rect,
TGridDrawState State);
void __fastcall SpeedButton2Click(TObject *Sender);
void __fastcall SpeedButton3Click(TObject *Sender);
void __fastcall SpeedButton1Click(TObject *Sender);
void __fastcall SpeedButton4Click(TObject *Sender);
void __fastcall StringGrid1GetEditText(TObject *Sender, int ACol, int ARow, UnicodeString &Value);
void __fastcall StringGrid1SetEditText(TObject *Sender, int ACol, int ARow, const UnicodeString Value);
private: // User declarations
Bitmap *Source; // 源图像
Bitmap *Dest; // 调整后的图像
BitmapData SrcData;
BitmapData DstData;
ColorMatrix Matrix;
void __fastcall InitColorMatrix(void);
double __fastcall CheckFloatStr(String Str);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1(void);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
代码文件:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
ULONG gdiplusToken;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1(void)
{
GdiplusShutdown(gdiplusToken);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
// 从文件装入图像到tmp
Bitmap *tmp = new Bitmap(L"..\\..\\media\\100_0349.jpg");
Gdiplus::Rect rect(0, 0, tmp->GetWidth(), tmp->GetHeight());
// 分别建立新的源和目标图像数据到srcData和dstData
GetBitmapData(rect.Width, rect.Height, &SrcData);
GetBitmapData(rect.Width, rect.Height, &DstData);
// 将tmp图像数据分别锁定拷贝到srcData和dstData
tmp->LockBits(&rect,
ImageLockModeRead | ImageLockModeWrite | ImageLockModeUserInputBuf,
PixelFormat32bppARGB, &SrcData);
tmp->UnlockBits(&SrcData);
tmp->LockBits(&rect,
ImageLockModeRead | ImageLockModeWrite | ImageLockModeUserInputBuf,
PixelFormat32bppARGB, &DstData);
tmp->UnlockBits(&DstData);
delete tmp;
// 分别用图像数据srcData和dstData建立位图Source和Dest
// 注:图像数据结构用于数据处理,位图用于显示,这样即可绑定数据结构和位图,
// 又能避免每次处理图像数据时的锁定和解锁操作
Source = new Bitmap(SrcData.Width, SrcData.Height, SrcData.Stride,
PixelFormat32bppARGB, (BYTE*)SrcData.Scan0);
Dest = new Bitmap(DstData.Width, DstData.Height, DstData.Stride,
PixelFormat32bppARGB, (BYTE*)DstData.Scan0);
InitColorMatrix();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete Dest;
delete Source;
FreeBitmapData(&DstData);
FreeBitmapData(&SrcData);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::InitColorMatrix(void)
{
for (int i = 0; i < 5; i ++)
{
for (int j = 0; j < 5; j ++)
Matrix.m[i][j] = (i == j)? 1.0 : 0.0;
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BitBtn1Click(TObject *Sender)
{
ImageSetColorMatrix(&DstData, &SrcData, &Matrix);
PaintBox1->Invalidate();
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
FloatToStr(Matrix.m[StringGrid1->Row][StringGrid1->Col]);
StringGrid1->Invalidate();
StringGrid1->SetFocus();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BitBtn2Click(TObject *Sender)
{
InitColorMatrix();
BitBtn1->Click();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BitBtn3Click(TObject *Sender)
{
Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
Gdiplus::Graphics g(PaintBox1->Canvas->Handle);
g.DrawImage(Source, 10, 10);
g.DrawImage(Dest, SrcData.Width + 20, 10);
}
//---------------------------------------------------------------------------
double __fastcall TForm1::CheckFloatStr(String Str)
{
double result = 0;
int len = Str.Length();
if (len == 0) return result;
bool dec = false;
bool neg = false;
int i = 1;
String s = "";
if (Str[i] == '-' || Str[i] == '+')
{
if (Str[i ++] == '-') neg = true;
}
for (; i <= len; i ++)
{
if (Str[i] == '.')
{
if (dec) break;
dec = true;
}
else if (Str[i] < '0' || Str[i] > '9') break;
s += Str[i];
}
if (s.Length() > 0)
{
if (neg) s = "-" + s;
result = s.ToDouble();
}
return result;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, int ACol, int ARow, TRect &Rect,
TGridDrawState State)
{
String text = Format("%.2f", &TVarRec(Matrix.m[ARow][ACol]), 0);
StringGrid1->Canvas->FillRect(Rect);
StringGrid1->Canvas->Pen->Color = clBtnShadow;
StringGrid1->Canvas->Rectangle(Rect);
InflateRect(&Rect, -2, -2);
DrawText(StringGrid1->Canvas->Handle, text.t_str(), text.Length(), &Rect, DT_RIGHT);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1GetEditText(TObject *Sender, int ACol, int ARow,
UnicodeString &Value)
{
Value = Format("%.2f", &TVarRec(Matrix.m[ARow][ACol]), 0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1SetEditText(TObject *Sender, int ACol, int ARow,
const UnicodeString Value)
{
Matrix.m[ARow][ACol] = CheckFloatStr(Value);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButton2Click(TObject *Sender)
{
InitColorMatrix();
for (int i = 0; i < 3; i ++)
Matrix.m[4][i] = 0.1;
BitBtn1->Click();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButton3Click(TObject *Sender)
{
InitColorMatrix();
for (int i = 0; i < 3; i ++)
Matrix.m[i][i] = -1.0;
BitBtn1->Click();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
InitColorMatrix();
for (int i = 0; i < 3; i ++)
{
Matrix.m[0][i] = 0.30;
Matrix.m[1][i] = 0.59;
Matrix.m[2][i] = 0.11;
}
BitBtn1->Click();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButton4Click(TObject *Sender)
{
InitColorMatrix();
Matrix.m[3][3] = 0.5;
BitBtn1->Click();
}
//---------------------------------------------------------------------------
运行界面图:
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《C++图像处理 -- 文章索引》。