GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换

标变换、矩阵变换

在Graphics类中,有几个函数实现了简单的坐标变换

Graphics::TranslateTransform(dx, dy, order)//平移坐标系
Graphics::RotateTransform(angle, order)//旋转坐标系
Graphics::ScaleTransform(sx, sy, order)//缩放坐标系
这里有个order参数着重讲一下,它是指矩阵的乘法顺序,是左乘还是右乘;

除了这几个基本函数以外,还可以使用矩阵来变换坐标系,使用

Graphics::SetTransform(matrix)
来进行设置,但目前还用不太到矩阵变换,所以就先不仔细写笔记了,如果想具体了解,看《精通GDI+编程》第六章

色彩变换

GDI+提供了Image类和Bitmap类来保存、维护图片的信息。对于色彩的存储,Image类和Bitmap类使用一个32位的数值来保存。红、绿、蓝及透明度各占8位,每一个色彩分量的取值范围是0-255。透明度为0表示完全透明,为255时,色彩完全可见。对透明度的的支持是GDI+的一大特色,但同时也为色彩的运算增加了一定的难度。

一、色彩信息的矩阵表示

四阶表示

由于一个色彩信息包含R、G、B、Alpha信息,所以,我们必然要使用一个4阶色彩变换矩阵来修改色彩的每一个分量值:

GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第1张图片

注意:对于色彩变换矩阵,这里的色彩顺序是R、G、B、A而不是A、R、G、B!!!

如果想将色彩(0,255,0,255)更改为半透明时,可以使用下面的的矩阵运算来表示:
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第2张图片
为什么使用五阶矩阵

上面使用四阶矩阵完全可以改变图片的RGBA值了,但考虑一种情况,如果我们只想在原有的R色上增加一些分量呢?
这时,我们就得再多加一阶来表示平移变换。所以,一个既包含线性变换,又包含平移变换的组合变换,称为仿射变换。使用四阶的色彩变换矩阵来修改色彩,只能够对色彩的每一个分量值进行乘(除)运算,如果要对这些分量值进行加减法的运算(平移变换),只能通过五阶矩阵来完成。
考虑下面这个变换:
1、红色分量值更改为原来的2倍;
2、绿色分量增加100;
则使用4阶矩阵的乘法无法实现,所以,应该在四阶色彩变换矩阵上增加一个“哑元坐标”,来实现所列的矩阵运算。

这个矩阵中,分量值用的是100,但在实际应用中,并没有100这个数,而是用下面的分量饱和度的方法来表示的。
色彩饱和度,是指某色彩占总饱和度(255)的分量,比如一个像素的红色(R值)值为A,那么A/255的值就称为红色分量饱和度。上面的100分量,它的饱和度就是100/255=0.39

所以,假设有一个像素的色彩值为(0.2,0.0,0.7,1)---注意用了色彩饱和度表示,在实际运算中,就是这么表示的。如果想达到对红色分量饱和度扩大2倍,并且分别将3种基准色的饱和度都加上0.2,则引入的色彩矩阵应该是:

GDI+的色彩变换矩阵(ColorMatrix)

在上面的所有讲解之后,大家也应该看出来了,色彩变换矩阵的表示形式,肯定是五阶的那种,所以大家看一下,在默认情况下,色彩变换矩阵的形式:
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第3张图片
所以跟上面的讲解一样:
1、对角线上的数值,相当于对RGB分量进行乘法操作。
2、修改最后一行上的数值,相当于对RGB分量的饱和度进行加(减)运算。
GDI+对色彩变换矩阵的修改是通过ImageAttributes对象的SetColorMatrix函数进行的,其调用方法如下:

Status SetColorMatrix(
  const ColorMatrix* colorMatrix,
  ColorMatrixFlags mode,
  ColorAdjustType type
);

参数说明:
colorMatrix:[in]色彩变换矩阵,数据类型为ColorMatrix,下面具体讲;
mode:[in]修改的标记,ColorMatrixFlags枚举类,它的值可以是以下3个

enum ColorMatrixFlags{
  ColorMatrixFlagsDefault   = 0,//修改所有的颜色值,包括灰度;
  ColorMatrixFlagsSkipGrays = 1,//修改所有的颜色值,但不包括灰度;
  ColorMatrixFlagsAltGray   = 2//对灰度和颜色使用不同的色彩变换矩阵进行修改;
};
type:[in]使用色彩矩阵调整的对象;
着重讲下colorMatrix
先看下定义:
struct ColorMatrix{
  REAL  m[5][5];//5*5数组
};
所以我们在定义ColorMatrix时,应当这样赋值
ColorMatrix colorMatrix={
	1.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,2.0f,0.0f,0.0f,0.0f, //将绿色调调整为原来的2倍
	0.0f,0.0f,1.0f,0.0f,0.0f,
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.0f,0.2f,0.0f,0.0f,1.0f//在将绿色,在原有绿色调的基础上增加0.2饱和度
};
就是上面这个转换矩阵,看个例子
Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();

ColorMatrix colorMatrix={
	1.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,1.0f,0.0f,0.0f,0.0f,
	0.0f,0.0f,1.0f,0.0f,0.0f,
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.0f,0.2f,0.0f,0.0f,1.0f
};
imageAttributes.SetColorMatrix(&colorMatrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);

graphics.DrawImage(&image,RectF(0,0,width,height));
graphics.TranslateTransform(width+10,0);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);

二、色彩几种运算方式 (平移、缩放、旋转、投射)

色彩的平移运算

色彩的平移运算,实际上就是色彩的加法运算。其实就是在色彩变换矩阵的最后一行加上某个值;
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第4张图片
图中的灰色部分就是参与色彩平移运算的4个矩阵元素。
我们看个例子,(图片中红色分量增加0.2饱和度)

Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();

ColorMatrix colorMatrix={
	1.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,1.0f,0.0f,0.0f,0.0f,
	0.0f,0.0f,1.0f,0.0f,0.0f,
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.2f,0.0f,0.0f,0.0f,1.0f
};
imageAttributes.SetColorMatrix(&colorMatrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);

graphics.DrawImage(&image,RectF(0,0,width,height));
graphics.TranslateTransform(width+10,0);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);

我们看右边的图像是怎么变过来的,由于图片是由一个个像素组成的,所以用每个像素的饱和度组成数组,来乘转换矩阵,结果就是转换后的当前点的颜色值, 值得说明的是,颜色的饱和度最大为1,当运算后的值超过1时,截取小数部分做为色彩分量饱和度(书上是这么写的P284,但试验之后,并不是);看下面这个转换例子:

色彩的缩放运算

色彩的缩放运算其实就是色彩的乘法运算。在色彩矩阵对角线上的分别代表R、G、B、A的几个值,将其分别乘以指定的值。这就是所谓的缩放变换。
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第5张图片

再重复一遍:值得说明的是,颜色的饱和度最大为1,当运算后的值超过1时,截取小数部分做为色彩分量饱和度(书上是这么写的P284,但试验之后,并不是);

看个例子吧:(将蓝色分量扩大两倍)

Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();

ColorMatrix colorMatrix={
	1.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,1.0f,0.0f,0.0f,0.0f,
	0.0f,0.0f,2.0f,0.0f,0.0f,//B分量变为2倍
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.0f,0.0f,0.0f,0.0f,1.0f
};
imageAttributes.SetColorMatrix(&colorMatrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);

graphics.DrawImage(&image,RectF(0,0,width,height));
graphics.TranslateTransform(width+10,0);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第6张图片

缩放变换的特殊应用(通道输出)

由于在色彩变换矩阵中,对角线上的数的取值范围是从0-1的,所以当取0时,这个色彩就完全不显示,所以当我们R、G都取0,而独有B取1时,就只显示了蓝色,所形成的图像也就是我们通常说的蓝色通道;看下几个通道输出的效果图:
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第7张图片

示例:(只演示绿色通道输出)

Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();

ColorMatrix colorMatrix={
	0.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,1.0f,0.0f,0.0f,0.0f,
	0.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.0f,0.0f,0.0f,0.0f,1.0f
};
//绘制原图
graphics.DrawImage(&image,RectF(0,0,width,height));
//绘制变换后的图像
graphics.TranslateTransform(width+10,0);
imageAttributes.SetColorMatrix(&colorMatrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第8张图片

色彩的旋转运算

RGB色是如何旋转的呢,首先用R、G、B三色建立立体坐标系:

所以,我们可以把一个色彩值看成三维空间里的一个点,色彩值的三个分量可以看成该点的坐标(三维坐标)。我们先不考虑,在三个维度综合情况下是怎么旋转的,我们先看看,在某个轴做为Z轴,在另两个轴形成的平面上旋转的情况,下图分析了,在将蓝色轴做为Z轴,仅在红—绿平面上旋转a度的情况:

GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第9张图片
这里我们分别讲解下为什么最终的R=原R*cosa-原G*sina;
在图中,我们可以看到,在旋转后,原R在R轴的分量变为:原R*cosa,但原G分量在旋转后,在R轴上也有了分量,但分量落在了负轴上,所以我们要减去这部分分量,所以最终的结果是最终的R=原R*cosa-原G*sina;

下面就看下书上关于几种旋转计算及结果矩阵,(注意:这几个图只标记了原X轴色彩分量的旋转,没有把Y轴色彩分量的旋转标记出来)

构造一个“红色绕着蓝色旋转”的色彩变换矩阵

构造一下“绿色绕着红色旋转”的色彩变换矩阵

GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第10张图片
构造一下“蓝色绕着红色旋转”的色彩变换矩阵

GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第11张图片
示例:(红色向蓝色旋转60度)
注意:为了演示如何清除色彩矩阵,这里颠倒了变换图像与原图的显示顺序注意加上头文件<math.h>

Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();
//红色绕蓝色旋转
ColorMatrix colorMatrix={
	cos(60.0f),sin(60.0f),0.0f,0.0f,0.0f,
	-sin(60.0f),cos(60.0f),0.0f,0.0f,0.0f,
	0.0f,0.0f,1.0f,0.0f,0.0f,
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.0f,0.0f,0.0f,0.0f,1.0f
};
imageAttributes.SetColorMatrix(&colorMatrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);
//输出原图
graphics.TranslateTransform(width+10,0);
imageAttributes.ClearColorMatrix(ColorAdjustTypeBitmap);//清除色彩变换
graphics.DrawImage(&image,RectF(0,0,width,height));
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第12张图片
色彩的投射运算

上面我们对对角线上的值进行了修改,对最后一行的值进行了修改,但对其它位置的值还没有修改。这里指的位置,看下图阴影部分;
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第13张图片
对这些值进行修改时,修改所使用的增加值来自于其它色彩分量的信息。
看下具体的运算方式:
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第14张图片
注意:最终结果的0.5=0.2*1+1*0.3,可见红色分量在原有红色分量的基础上,增加了绿色分量值的0.3倍;利用其它色彩分量的倍数来更改自己色彩分量的值,这种运算就叫投射运算。

示例:(红色分量在原有基础上增加上绿色分量的40%)

Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();

ColorMatrix colorMatrix={
	1.0f,0.0f,0.0f,0.0f,0.0f,
	0.0f,1.0f,0.0f,0.0f,0.0f,
	0.4f,0.0f,1.0f,0.0f,0.0f,//增加绿色分量值的0.4倍
	0.0f,0.0f,0.0f,1.0f,0.0f,
	0.0f,0.0f,0.0f,0.0f,1.0f
};
//绘制原图
graphics.DrawImage(&image,RectF(0,0,width,height));
//绘制变换后的图
graphics.TranslateTransform(width+10,0);
imageAttributes.SetColorMatrix(&colorMatrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第15张图片
三、色彩映射 (色彩替换)

色彩映射,说白了,就是对图片原有的颜色进行替换,比如将原来的蓝色替换成红色。在制作电视时,背景通常都是蓝色的,目的是为了方便后期视频的深加工。如果需要更改背景或者进行视频合成,只需要将蓝色背景抠去,保留主要画面就行了。

GDI+中使用ColorMap结构体就能实现将指定颜色替换成其它颜色。ImageAttributes类的SetRemapTable(设置重映射表)可以将这个数据结构应用到图像中,SetRemapTable函数的调用格式为:

Status SetRemapTable(
  UINT mapSize,
  const ColorMap* map,
  ColorAdjustType type
);
ColorMap的定义格式如下:
struct ColorMap{
  Color  oldColor;
  Color  newColor;
};
参数说明:
mapSize:
[in]ColorMap数组大小;
map:[in]ColorMap数组,表示一种或多种色彩替换的对应关系,如果数组中定义了多个替换关系,那么所有被定义要替换的颜色都将被替换掉。
type:[in]表示在什么对象上进行色彩替换。

示例:(将原图中的背景,全部替换成纯红色 ---注意背景由两种颜色组成
Image image(L"wlh.bmp");
ImageAttributes imageAttributes;
UINT width=image.GetWidth();
UINT height=image.GetHeight();

ColorMap colorMap[2];
colorMap[0].oldColor=Color(255,0,0,255);
colorMap[0].newColor=Color(255,255,0,0);
colorMap[1].oldColor=Color(255,0,145,247);
colorMap[1].newColor=Color(255,255,0,0);

imageAttributes.SetRemapTable(2,colorMap,ColorAdjustTypeBitmap);
graphics.DrawImage(&image,RectF(0,0,width,height));
//绘制变换后的图
graphics.TranslateTransform(width+10,0);
graphics.DrawImage(&image,Rect(0,0,width,height),0,0,width,height,UnitPixel,&imageAttributes);
GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换_第16张图片






你可能感兴趣的:(GDI+学习及代码总结之-----坐标变换、矩阵变换及色彩变换)