在图象图形的编程中, 经常会见到渐变色以及各种图片的叠加等效果. 这篇文章就是要对这些效果的原理加以分析, 并在Elastos© 操作系统[1]Mobile Edition SDK上和Windows 200 Professional上使用Visual C++ 6.0 编程实现.
一. RGB三维模型与渐变色的原理及实现
1. RGB三维模型
作为计算机图形学中重要的原色混合系统, RGB(红绿蓝)加色系统广泛应用于发光体, 如彩色CRT显示或彩色灯光. 这三种单色是得以匹配或生成可见光谱中几乎所有颜色的最小数量的原色.
为了适应不同的颜色深度, 使用0-1来表示R, G, B颜色深浅. 使用三维坐标分别表示RGB. 如下图1所示. 这样原点RGB(0, 0, 0)就表示黑色, 而原点对应的顶点RGB(1, 1, 1)就表示白色. 使用三维表示的好处就在于直观, 以及在颜色变化过程中容易得到颜色变化的规律.
(图1)
在图1所表示的空间中(点表示为 (R, G, B), 0≤R≤1, 0≤G≤1,0≤B≤1). 现在假设有某颜色(比如图1中的点Q), 其值为(r, g, b). 那么从黑色到该颜色的点(在图1中就对应从点(0,0,0)到点Q(r, g, b)的线段)从视觉上是越来越浅. 设这些点的坐标为(x, y, z)(0≤x≤r, 0≤y≤≤g, 0≤z≤b), 那么在我们的RGB三维坐标空间中就有方程:
(公式1)
(注意: 这里处理的点都是三维模型所表示的立体内的点, 其中边界情况请读者自行考虑)
从该点到白色的点(在图1中就对应从点Q(r, g, b)到点(1, 1, 1)的线段)的颜色继续变浅. 设这些点的坐标为(x, y, z)(r≤x≤1, g≤y≤1, b≤z≤1), 那么这些点满足方程:
(公式2)
(注意: 同上, 这里处理的点都是三维模型所表示的立体内的点, 其中边界情况请读者自行考虑)
- 2. 渐变色的实现思路
渐变色是我们经常见到的颜色处理方式, 最常见的如Windows的标题栏就是使用的渐变处理效果.
比如要实现如下图所示的渐变效果,
(图2)
我们可以采取这样的方式来达到目的: 从这个矩形的上面到下面一条一条地
画线, 而从上往下的线的颜色就越来越浅. 当然, 使用这样的思路进行扩展, 我们还能做出更多的渐变效果, 比如从两边往中间, 或者倾斜45度角进行渐变等等.
- 3. 编程实现
我们在一个按钮上实现这种渐变效果, 那么只要重载这个按钮的OnPaint()函数即可. 在下面代码所在的设备环境中, 一个COLORREF为一个32位数, 从最高到最底的每个8位所代表的含义分别为为: 未定义(31-24bit), 红色(23-16bit), 绿色(15-8bit), 蓝色(7-0bit). 由于考虑到代码的平台移植, 能够自己实现的功能将不使用系统所提供的API.
定义宏, 将RGB转化为COLORREF值(在Windows中有相应的RGB宏):
#define RGBTOCOLORREF(R, G, B) (R << 16) | \
(G << 8) | \
(B)
再定义一个宏, 从COLORREF中取得R, G, B三种颜色(在Windows中相应的GetRValue, GetGValue, GetBValue三个API可以实现下面宏的功能):
#define COLORREFTORGB(COLORREF, R, G, B) R = (COLORREF & 0x00FF0000) >> 16; \
G = (COLORREF & 0x0000FF00) >> 8; \
B = (COLORREF & 0x000000FF)
编程思路:
首先得到当前环境的设备上下文指针(DC指针, 对应于Elastos中为GDC指针, 对应于Windows中为CDC指针), 然后得到按钮所在的矩形, 从上往下一条一条线地进行填充, 线的颜色逐渐变浅.
(1). 在Elastos Mobile Edition SDK中具体代码如下:
void CButtonIndex::OnPaint()
{
GDC* pdc = GetDC();
RECT* rcOld = GetRect();
PEN pen = {SOLID_LINE, 1, m_Color};
POINT point1 = {0, 0};
POINT point2 = {rcOld->right - rcOld->left, 0};
int nColorRed, nColorGreen, nColorBlue;
COLORREFTORGB(pen.color, nColorRed, nColorGreen, nColorBlue);
double alpha = 1.0;
while (point1.y < rcOld->bottom - rcOld->top) {
pen.color = (((int)(nColorRed * alpha)) << 16) |
(((int)(nColorGreen * alpha)) << 8) |
(int)(nColorBlue * alpha);
/* 注意: 此处将RGB颜色数只是简单进行线性增加, 而并未严格按照公式2进行修正, 目的是为了节约计算量*/
pdc->DrawLine(&point1, &point2, &pen);
alpha += 0.025;
point1.y += 1;
point2.y += 1;
}
}
(2). 在Windows 2000上使用Visual C++ 6.0实现
新建一个基于对话框的MFC工程, 使用ClassWizard新建一个类CButtonIndex, 选择它的基类为CButton. 向导将为你生成这个类的ButtonIndex.h和ButtonIndex.cpp文件. 使用ClassWizard为CButtonIndex类增加一个消息处理, 选择WM_PAINT消息, 向导将为CButtonIndex类增加一个 OnPaint()函数, 在此函数中填充代码如下:
void CButtonIndex::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
RECT rcRgn;
GetClientRect(&rcRgn);
POINT ptStart = {rcRgn.left, rcRgn.top};
POINT ptEnd = {rcRgn.right, rcRgn.top};
COLORREF color = 0x456789;
COLORREF nColorRed, nColorGreen, nColorBlue;
COLORREFTORGB(color, nColorRed, nColorGreen, nColorBlue);
double alpha = 1.0; //定义最初的颜色增长基数
while (ptEnd.y < rcRgn.bottom - rcRgn.top)
{
color = (((int)(nColorRed * alpha)) << 16) |
(((int)(nColorGreen * alpha)) << 8) |
(int)(nColorBlue * alpha);
CPen pen(PS_SOLID, 1, color);
CPen* ppen = dc.SelectObject(&pen);
dc.MoveTo(ptStart);
dc.LineTo(ptEnd);
dc.SelectObject(ppen);
alpha += 0.025;
ptStart.y += 1;
ptEnd.y += 1;
}
}
二. Alpha通道的原理及实现
1. alpha通道的原理
在各种广告招贴画, 游戏, 电影海报中经常看到图片叠加之后的深入浅出的效果, 两副图片叠在一起的时候, 看起来感觉“你中有我”, “我中有你”. 这种将两副或者更多的画面叠加在一起所使用的技术就是alpha通道. 它的原理就是将两副图片对应的同一个位置的点的颜色, 各取一部分, 然后叠加. Alpha通道是各种图片处理程序的原理基础.
现在假设有N1, N2, N3, …, Nn副图片要进行叠加(其中n >= 2, 一副图片当然也就谈不上叠加, 也就无所谓alpha通道了), 他们相同坐标(在二维平面中)的某个点的颜色使用RGB表示分别为(R1, G1, B1), (R2, G2, B2), (R3, G3, B3), …, (Rn, Gn, Bn). 如果这N副图片在最终合成的图片中占的比率分别为α1, α2, α3, …, αn(必须满足α1 + α2 + α3 + … + αn = 1), 那么最终的合成的图片的在这一坐标点的颜色的R, G, B就分别为
R = R1*α1 + R2*α2 + R3*α3 + … + Rn*αn;
G = G1*α1 + G2*α2 + G3*α3 + … + Gn*αn;
B = B1*α1 + B2*α2 + B3*α3 + … + Bn*αn; (公式3)
2. 编程实现
为了简化程序的编码, 下面的实例中将只叠加两副图片.
程序的最后结果如下所示:
(图3)
其中左上角的图片为长城, 右边的图片的夕阳中的金字塔, 下面的图片为将“两副图片的颜色各取一半”而合成后的结果. 可以看到主画面的的长城, 当然也可以看到落暮的夕阳和金字塔, 还有树木的轮廓.
编程思路:
首先分别得到两副图片(这里两副图片的尺寸是一样大的)中相同位置的点的颜色, 然后使用alpha混合之后打印到屏幕上, 即得到了合成后的图片.
(1). 在Elastos Mobile Edtion SDK上实现的具体代码如下:
程序代码如下(这回在FORM的OnPaint()函数中实现):
void CFormAlpha::OnPaint()
{
CForm::OnPaint();
RECT rcPic1 = {0, 0, 86, 108};
RECT rcPic2 = {89, 0, 175, 108};
POINT ptPicStart = {45, 110};
GDC* pdc = GetDC();
//将指定图片载入指定矩形
pdc->DrawImageAdapt("/bin/picture/4.jpg", &rcPic1);
pdc->DrawImageAdapt("/bin/picture/7.jpg", &rcPic2);
COLORREF nColor1, nRed1, nGreen1, nBlue1;
COLORREF nColor2, nRed2, nGreen2, nBlue2;
COLORREF nColor, nRed, nGreen, nBlue;
double alpha = 0.5; //定义的alpha值
for (int y = 0; y < 108; y++) {
for (int x = 0; x < 87; x++) {
nColor1 = pdc->GetPixel(rcPic1.left + x, rcPic1.top + y);
nColor2 = pdc->GetPixel(rcPic2.left + x, rcPic2.top + y);
COLORREFTORGB(nColor1, nRed1, nGreen1, nBlue1);
COLORREFTORGB(nColor2, nRed2, nGreen2, nBlue2);
//计算得到目标图片的RGB三原色
nRed = (int)(nRed1 * alpha + nRed2 * (1 - alpha));
nGreen = (int)(nGreen1 * alpha + nGreen2 * (1 - alpha));
nBlue = (int)(nBlue1 * alpha + nBlue2 * (1 - alpha));
nColor = RGBTOCOLORREF(nRed, nGreen, nBlue);
//合成并打印到屏幕
pdc->SetPixel(x + ptPicStart.x, y + ptPicStart.y, nColor);
}
}
}
(2). 在Windows 2000上使用Visual C++ 6.0实现
新建一个SDI的MFC工程, 将要混合的两副图片导入, 然后填充View类的OnDraw函数如下:
void CAlphaPlusView::OnDraw(CDC* pDC)
{
CAlphaPlusDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CDC memDC1;
memDC1.CreateCompatibleDC(pDC);
CBitmap bitmap1;
bitmap1.LoadBitmap(IDB_BITMAP1);
BITMAP bmpInfo1;
bitmap1.GetBitmap(&bmpInfo1);
CBitmap* pBitmap1 = memDC1.SelectObject(&bitmap1);
pDC->BitBlt(0,
0,
bmpInfo1.bmWidth,
bmpInfo1.bmHeight,
&memDC1,
0,
0,
SRCCOPY);
CDC memDC2;
memDC2.CreateCompatibleDC(pDC);
CBitmap bitmap2;
bitmap2.LoadBitmap(IDB_BITMAP2);
BITMAP bmpInfo2;
bitmap2.GetBitmap(&bmpInfo2);
CBitmap* pBitmap2 = memDC2.SelectObject(&bitmap2);
pDC->BitBlt(bmpInfo1.bmWidth + 1,
0,
bmpInfo2.bmWidth,
bmpInfo2.bmHeight,
&memDC2,
0,
0,
SRCCOPY);
COLORREF nColor1, nRed1, nGreen1, nBlue1;
COLORREF nColor2, nRed2, nGreen2, nBlue2;
COLORREF nColor, nRed, nGreen, nBlue;
double alpha = 0.5;
for (int y = 0; y < bmpInfo1.bmHeight; y++)
{
for (int x = 0; x < bmpInfo1.bmWidth; x++)
{
nColor1 = memDC1.GetPixel(x, y);
nColor2 = memDC2.GetPixel(x, y);
COLORREFTORGB(nColor1, nRed1, nGreen1, nBlue1);
COLORREFTORGB(nColor2, nRed2, nGreen2, nBlue2);
nRed = (int)(nRed1 * alpha + nRed2 * (1.0 - alpha));
nGreen = (int)(nGreen1 * alpha + nGreen2 * (1.0 - alpha));
nBlue = (int)(nBlue1 * alpha + nBlue2 * (1.0 - alpha));
nColor = RGBTOCOLORREF(nRed, nGreen, nBlue);
pDC->SetPixel(x, y + bmpInfo1.bmHeight + 1, nColor);
}
}
memDC2.SelectObject(pBitmap2);
memDC1.SelectObject(pBitmap1);
}
[1] Elastos©和欣©操作系统是科泰世纪科技有限公司独立开发, 拥有自主知识产权的嵌入式操作系统. 它是基于微内核结构, 支持多进程, 多线程的嵌入式操作系统; 基于ezCOM©技术的操作系统新型体系结构, 灵活内核技术. 其Mobile Edition是专为智能手持设备所开发的版本.