本文为GDI+ for VCL基础系列文章之一,主要供GDI+初学者入门参考,例子使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。如有错误或者建议请来信:[email protected]
ColorMatrix是个5 * 5浮点数矩阵,下面是有关资料对它的介绍:
GDI+ 提供用于存储和操作图像的 Image 和 Bitmap 类。Image 和 Bitmap 对象将每个像素的颜色都存储为 32 位的数:红色、绿色、蓝色和 alpha 各占 8 位。这四个分量的值都是 0 到 255,其中 0 表示没有亮度,255 表示最大亮度。alpha 分量指定颜色的透明度:0 表示完全透明,255 表示完全不透明。
颜色矢量采用 4 元组形式(红色、绿色、蓝色、alpha)。例如,颜色矢量 (0, 255, 0, 255) 表示一种没有红色和蓝色但绿色达到最大亮度的不透明颜色。
表示颜色的另一种惯例是用数字 1 表示亮度达到最大。使用这种惯例,上一段中描述的颜色将用 (0, 1, 0, 1) 表示。GDI+ 在进行颜色变换时使用以 1 表示最大亮度的惯例。
可通过用 4×4 矩阵乘以这些颜色矢量将线性变换(旋转和缩放等)应用到颜色矢量中。但是,您不能使用 4×4 矩阵进行平移(非线性)。如果在每个颜色矢量中再添加一个虚拟的第 5 坐标(例如,数字 1),则可使用 5×5 矩阵应用任何组合形式的线性变换和平移。由线性变换组成的后跟平移的变换称为仿射变换。
一言而蔽之,ColorMatrix是通过5 * 5矩阵对图像颜色(包括Alpha)进行的几何变换,理解和掌握ColorMatrix变换,能使你的图像产生千变万化的效果。在GDI+ for VCL中ColorMatrix被定义为:
TColorMatrix = array[0..4, 0..4] of TREAL;
其单位矩阵的定义如下(从左上至右下,主对角线依次为红、绿、蓝、Alpha和虚拟位):
ColorMatrix: TColorMatrix =
(
(1.0, 0.0, 0.0, 0.0, 0.0),
(0.0, 1.0, 0.0, 0.0, 0.0),
(0.0, 0.0, 1.0, 0.0, 0.0),
(0.0, 0.0, 0.0, 1.0, 0.0),
(0.0, 0.0, 0.0, 0.0, 1.0)
);
在GDI+ for VCL中,是通过建立TGpImageAttributes对象,调用其设置颜色矩阵的方法,来达到图像颜色变换的,因此,必须先掌握好TGpImageAttributes设置颜色矩阵的方法。
TGpImageAttributes有下面2个方法用于设置颜色矩阵:
//
为指定类别设置颜色调整矩阵。
procedure SetColorMatrix(
const
colorMatrix: TColorMatrix;
mode: TColorMatrixFlags
=
cfDefault; catype: TColorAdjustType
=
ctDefault);
//
为指定类别设置颜色调整矩阵和灰度调整矩阵。
procedure SetColorMatrices(
const
colorMatrix, grayMatrix: TColorMatrix;
mode: TColorMatrixFlags
=
cfDefault; catype: TColorAdjustType
=
ctDefault);
方法中的TColorMatrix类型的参数是颜色矩阵,上面已经介绍了。SetColorMatrices有2个矩阵参数,colorMatrix用于调整彩色像素,grayMatrix调整灰度像素(灰度像素是指RGB值相等的像素);而SetColorMatrix方法只有一个颜色矩阵参数。
方法中的catype参数是个TColorAdjustType枚举类型,定义为:
TColorAdjustType = (ctDefault, ctBitmap, ctBrush, ctPen, ctText, ctCount, ctAny);
TGpImageAttributes几乎所有方法都用到它,默认为ctDefault,也就是ctAny(任意对象),如果指定了要调整的对象,如ctBitmap为图像,ctBrush为画刷等,在进行调整时,只涉及该对象,对其它对象不影响。比如我们建立了一个TGpImage对象,又以此对象建立了一个TGpTextureBrush对象,然后又以TGpTextureBrush对象创建了TGpPen对象,我们在对这些对象施以TGpImageAttributes调整时,只对指定对象进行调整。
mode参数是设置颜色矩阵方法中独有的参数类型,其定义如下:
TColorMatrixFlags = (cfDefault, cfSkipGrays, cfAltGray);
该枚举类型的运用不太好理解,对照SetColorMatrices方法,我们可以作如下分析:
假设把上面ColorMatrix单位矩阵中表示红色的ColorMatr[0,0]元素改为0,也就是去掉图像中的红色成分, 先让SetColorMatrices方法的colorMatrix和grayMatrix参数使用同一个矩阵。catype设置为缺省值ctDefault,然后设置mode参数:
1、mode=ctDefault时,用colorMatrix矩阵调整所有像素(包括灰度像素),grayMatrix在这里不起作用,效果见下图A;
2、mode=cfSkipGrays时,只用colorMatrix调整彩色像素,grayMatrix也不起作用,效果见下图B;
3、mode=cfAltGray时,colorMatrix和grayMatrix分别调整图像的彩色像素和灰度像素,因为colorMatrix和grayMatrix用的是同一个矩阵,彩色像素和灰度像素作了相同的调整,所以效果和下图A一样,我们把colorMatrix设置为单位矩阵,即只调整灰度像素,不调整彩色像素,其效果为下图C。从效果图,还可以看出一个现象,把B和C图上变换的部分合起来,刚好就是A图。
另一个方法SetColorMatrix没有灰度像素调整矩阵,所以只有前面2个枚举项有效,用上面的矩阵作参数后的效果与上图A、B一样,注意:cfAltGray在这里是非法的!
方法的使用介绍后,我们再依次讲讲TColorMatrix的缩放、旋转、平移及剪切。
一、颜色的缩放。颜色的缩放就是把图像像素的R、G、B分量乘上某个值,在ColorMatrix矩阵中,就是把主对角线上对应的位设置为某个值。例如上例把红色分量设置为0,就是把图像中各个像素的红色成分缩放为0,其结果就是如上图。把R、G、B全部乘上-1,可以使图像像素反相,得到图像的负片,请看下面的代码和效果图:
const
ColorMatrix: TColorMatrix
=
(
(
-
1
,
0.0
,
0.0
,
0.0
,
0.0
),
(
0.0
,
-
1
,
0.0
,
0.0
,
0.0
),
(
0.0
,
0.0
,
-
1
,
0.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
0.0
,
1.0
)
);
procedure PaintImage(g: TGpGraphics; Image: TGpImage);
var
ImageAttr: TGpImageAttributes;
begin
ImageAttr : TGpImageAttributes.Create;
ImageAttr.SetColorMatrix(ColorMatrix);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height));
g.TranslateTransform(Image.Width
+
10
,
0
);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height),
0
,
0
, Image.Width, Image.Height, utPixel, ImageAttr);
ImageAttr.Free;
end;
对灰度图的缩放还可起到简单着色的效果:
const
GrayMatrix: TColorMatrix
=
(
(
1.75
,
0.0
,
0.0
,
0.0
,
0.0
),
(
0.0
,
1.2
,
0.0
,
0.0
,
0.0
),
(
0.0
,
0.0
,
1.0
,
0.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
0.0
,
1.0
)
);
procedure PaintImage(g: TGpGraphics; Image: TGpImage);
var
ImageAttr: TGpImageAttributes;
begin
ImageAttr := TGpImageAttributes.Create;
//
这里的ColorMatrix为单位矩阵,没贴在代码中,
ImageAttr.SetColorMatrices(ColorMatrix, GrayMatrix, cfAltGray);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height));
g.TranslateTransform(Image.Width
+
10
,
0
);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height),
0
,
0
, Image.Width, Image.Height, utPixel, ImageAttr);
ImageAttr.Free;
end;
例子利用灰度矩阵对图像的红色和绿色进行缩放,分量分别乘以1.75和1.20,使灰度图呈现为黄色,效果如下图,左边为原灰度图,右边为着色图,咋一看去,还有点肤色的味道:
二、颜色的旋转。可以以RGB中任意2色围绕另一色旋转,下面的代码和效果图演示了红 - 绿平面围绕蓝色轴旋转60度的效果:
const
ColorMatrix: TColorMatrix
=
(
(1.0
,
0.0
,
0.0
,
0.0
,
0.0
),
(0.0
, 1.0
,
0.0
,
0.0
,
0.0
),
(
0.0
,
0.0
,
1
,
0.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
0.0
,
1.0
)
);
procedure PaintImage(g: TGpGraphics; Image: TGpImage);
var
ImageAttr: TGpImageAttributes;
v, r: Single;
begin
r :
=
60.0
*
PI
/
180.0
;
v :
=
cos(r);
ColorMatrix[
0
,
0
] :
=
v;
ColorMatrix[
1
,
1
] :
=
v;
v :
=
sin(r);
ColorMatrix[
0
,
1
] :
=
v;
ColorMatrix[
1
,
0
] :
=
-
v;
ImageAttr : TGpImageAttributes.Create;
ImageAttr.SetColorMatrix(ColorMatrix);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height));
g.TranslateTransform(Image.Width
+
10
,
0
);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height),
0
,
0
, Image.Width, Image.Height, utPixel, ImageAttr);
ImageAttr.Free;
end;
三、颜色的平移。颜色的旋转和缩放是线性变换,而平移却是非线性变换,所以在ColorMatrix矩阵中要通过虚拟行来表示,所谓颜色平移,就是把颜色RGB的某个分量加上这个分量值的某个比率,例如,让前面单位矩阵Colormatrix[0,4]=0.08,就是把图像各像素的红色分量加上255*0.08=20.4,如果Colormatrix[1,4]和 Colormatrix[2,4]也等于0.08,即颜色的RGB分量都加上20.4,相当于非线性地调整了图像的亮度,例子请看《GDI+ 在Delphi程序的应用 -- ColorMatrix与图像亮度》一文。
四、颜色的剪切。R、G、B各分量按照与另一种颜色分量成比例的量来增加或减少颜色分量就是剪切,下面BCB例子就是表示将图像每个像素的红色分量加上蓝色分量的50%:
TColorMatrix ColorMatrix
=
{
1.0
,
0.0
,
0.0
,
0.0
,
0.0
,
0.0
,
1.0
,
0.0
,
0.0
,
0.0
,
0.5
,
0.0
,
1.0
,
0.0
,
0.0
,
0.0
,
0.0
,
0.0
,
1.0
,
0.0
,
0.0
,
0.0
,
0.0
,
0.0
,
1.0
};
void
PaintImage(TGpGraphics
*
g, TGpImage
*
Image)
{
TGpImageAttributes
*
ImageAttr
=
new
TGpImageAttributes();
ImageAttr
->
SetColorMatrix(ColorMatrix);
g
->
DrawImage(Image, TGpRect(
0
,
0
, Image
->
Width, Image
->
Height));
g
->
TranslateTransform(Image
->
Width
+
10
,
0
);
g
->
DrawImage(Image, TGpRect(
0
,
0
, Image
->
Width, Image
->
Height),
0
,
0
, Image
->
Width, Image
->
Height, utPixel, ImageAttr);
delete ImageAttr;
}
效果图为:
五、颜色变换的综合运用。前面已经说了,在线性变换后又加上平移的操作叫仿射变换,颜色的仿射变换就是ColorMatrix变换的综合,例子就不再举了。
下面的这个比较熟悉的灰度矩阵也是个典型的缩放加剪切变换的运用:
ColorMatrix: TColorMatrix =
(
(0.30, 0.30, 0.30, 0.0, 0.0),
(0.59, 0.59, 0.59, 0.0, 0.0),
(0.11, 0.11, 0.11, 0.0, 0.0),
(0.0, 0.0, 0.0, 1.0, 0.0),
(0.0, 0.0, 0.0, 0.0, 1.0)
);
为了理解这个矩阵,可以以红色分量R为例进行一下分析:
ColorMatrix[0, 0]=0.30,属于红色分量缩放;
ColorMatrix[0, 1]=0.59,加上绿色分量的0.59倍,属于剪切;
ColorMatrix[0, 2]=0.11,加上蓝色分量的0.11倍,属于剪切。
综合起来,红色分量: R = R * 0.3 + G * 0.59 + B * 0.11,同样的方法对绿色分量G和蓝色分量B进行分析,得出的公式与红色分量完全一样,而这恰好就是图像灰度化的公式。我的文章《GDI+ 在Delphi程序的应用 -- ColorMatrix与图像灰度化》就是利用这个矩阵。
上面的所有变换都只涉及到图像像素的R、G、B,对于Alpha,只需把ColorMatrix[3,3]设置为0 - 1的值,就可使图像透明和半透明的显示。可参见《GDI+ 在Delphi程序的应用 -- 图像的透明显示技巧》的有关例子。
最后,给出一个完整的ColorMatrix演示例子程序:
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Gdiplus, ExtCtrls, StdCtrls, Buttons, Grids;
type
TMainForm
=
class
(TForm)
Label1: TLabel;
PaintBox1: TPaintBox;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
StringGrid1: TStringGrid;
BitBtn1: TBitBtn;
BitBtn3: TBitBtn;
BitBtn2: TBitBtn;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
RadioButton3: TRadioButton;
procedure FormCreate(Sender: TObject);
procedure BitBtn1Click(Sender: TObject);
procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
procedure StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
var Value: String);
procedure PaintBox1Paint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton4Click(Sender: TObject);
procedure SpeedButton3Click(Sender: TObject);
procedure RadioButton1Click(Sender: TObject);
procedure BitBtn3Click(Sender: TObject);
private
{ Private declarations }
Matrix: TColorMatrix;
Image: TGpImage;
ImageAttr: TGpImageAttributes;
MatrixFlag: TColorMatrixFlags;
procedure SetMatrix;
procedure GetMatrix;
public
{ Public declarations }
procedure SetMatrixFlag;
end;
var
MainForm: TMainForm;
implementation
{$R
*
.dfm}
const
ColorMatrix: TColorMatrix
=
(
(
1.0
,
0.0
,
0.0
,
0.0
,
0.0
),
(
0.0
,
1.0
,
0.0
,
0.0
,
0.0
),
(
0.0
,
0.0
,
1.0
,
0.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
1.0
,
0.0
),
(
0.0
,
0.0
,
0.0
,
0.0
,
1.0
)
);
procedure TMainForm.GetMatrix;
var
i, j: Integer;
begin
for
i :
=
0
to
4
do
for
j :
=
0
to
4
do
Matrix[i, j] :
=
StrToFloat(StringGrid1.Cells[j, i]);
end;
procedure TMainForm.SetMatrix;
var
i, j: Integer;
PMatrix: PColorMatrix;
begin
for
i :
=
0
to
4
do
for
j :
=
0
to
4
do
StringGrid1.Cells[j, i] :
=
Format(
'
%.2f
'
, [Matrix[i, j]]);
ImageAttr.Reset;
if
MatrixFlag
=
cfAltGray then
PMatrix :
=
@ColorMatrix
else
PMatrix :
=
@Matrix;
ImageAttr.SetColorMatrices(PMatrix
^
, Matrix, MatrixFlag);
end;
procedure TMainForm.SetMatrixFlag;
begin
RadioButton1.Checked :
=
True;
MatrixFlag :
=
cfDefault;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
Stream: TStreamAdapter;
begin
//
这里示范用Delphi流建立图像对象,也可直接用文件名建立,
//
但使用文件名不能覆盖保存
Stream :
=
TStreamAdapter.Create(TMemoryStream.Create, soOwned);
TMemoryStream(Stream.Stream).LoadFromFile(
'
....Media@_0349.jpg
'
);
Image :
=
TGpImage.Create(Stream);
ImageAttr :
=
TGpImageAttributes.Create;
BitBtn2.Click;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
Image.Free;
ImageAttr.Free;
end;
//
灰度化
procedure TMainForm.SpeedButton1Click(Sender: TObject);
var
I: Integer;
begin
SetMatrixFlag;
Move(ColorMatrix, Matrix, Sizeof(Matrix));
for
I :
=
0
to
2
do
begin
Matrix[
0
, I] :
=
0.3
;
Matrix[
1
, I] :
=
0.59
;
Matrix[
2
, I] :
=
0.11
;
end;
SetMatrix;
PaintBox1.Invalidate;
end;
//
调整亮度0.1(25.5)
procedure TMainForm.SpeedButton2Click(Sender: TObject);
var
I: Integer;
begin
SetMatrixFlag;
Move(ColorMatrix, Matrix, Sizeof(Matrix));
for
I :
=
0
to
2
do
Matrix[
4
, I] :
=
0.1
;
SetMatrix;
PaintBox1.Invalidate;
end;
//
反色
procedure TMainForm.SpeedButton3Click(Sender: TObject);
begin
SetMatrixFlag;
Move(ColorMatrix, Matrix, Sizeof(Matrix));
Matrix[
0
,
0
] :
=
-
1
;
Matrix[
1
,
1
] :
=
-
1
;
Matrix[
2
,
2
] :
=
-
1
;
SetMatrix;
PaintBox1.Invalidate;
end;
//
半透明显示
procedure TMainForm.SpeedButton4Click(Sender: TObject);
begin
SetMatrixFlag;
Move(ColorMatrix, Matrix, Sizeof(Matrix));
Matrix[
3
,
3
] :
=
0.5
;
SetMatrix;
PaintBox1.Invalidate;
end;
procedure TMainForm.StringGrid1DrawCell(Sender: TObject; ACol,
ARow: Integer; Rect: TRect; State: TGridDrawState);
var
r: TRect;
Text: string;
x: Integer;
begin
with StringGrid1
do
begin
Text :
=
Cells[ACol, ARow];
if
Text
=
''
then Text :
=
'
0.00
'
else
begin
x :
=
Pos(
'
.
'
, Text);
if
x
<
1
then Text :
=
Text
+
'
.00
'
else
if
x
=
1
then Text :
=
'
0
'
+
Text;
end;
r :
=
TRect(Rect);
Canvas.FillRect(r);
Canvas.Pen.Color :
=
clBtnShadow;
Canvas.Rectangle(r);
InflateRect(r,
-
2
,
-
2
);
DrawText(Canvas.Handle, PChar(Text), Length(Text), r, DT_RIGHT);
end;
end;
procedure TMainForm.StringGrid1GetEditText(Sender: TObject; ACol,
ARow: Integer; var Value: String);
var
v: Double;
begin
if
Value
=
''
then v :
=
0.0
else
v :
=
StrToFloat(Value);
Value :
=
Format(
'
%.2f
'
, [v]);
end;
procedure TMainForm.PaintBox1Paint(Sender: TObject);
var
g : TGpGraphics;
begin
g :
=
TGpGraphics.Create(PaintBox1.Canvas.Handle);
g.FillRectangle(Brushs.White, GpRect(PaintBox1.ClientRect));
try
g.TranslateTransform(
10
,
10
);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height));
g.TranslateTransform(Image.Width
+
10
,
0
);
g.DrawImage(Image, GpRect(
0
,
0
, Image.Width, Image.Height),
0
,
0
, Image.Width, Image.Height, utPixel, ImageAttr);
finally
g.Free;
end;
end;
procedure TMainForm.RadioButton1Click(Sender: TObject);
begin
MatrixFlag :
=
TColorMatrixFlags(TRadioButton(Sender).Tag);
end;
procedure TMainForm.BitBtn1Click(Sender: TObject);
begin
GetMatrix;
SetMatrix;
PaintBox1.Invalidate;
end;
procedure TMainForm.BitBtn2Click(Sender: TObject);
begin
SetMatrixFlag;
Move(ColorMatrix, Matrix, Sizeof(Matrix));
SetMatrix;
PaintBox1.Invalidate;
end;
procedure TMainForm.BitBtn3Click(Sender: TObject);
begin
ShowMessage(FloatToStr(cos(
60
/
180
)));
end;
end.
该例子可以对GDI+的颜色调整矩阵进行任何的测试,以加深ColorMatrix的理解。
程序界面图如下,界面右上方图像是Alpha=0.5时的半透明显示状态。注意那3个TRadioButton的Tag分别设置为0、1、2,对应TColorMatrixFlags的3个值: