图形处理
在VC下显示JPEG、GIF格式图像的一种简便方法
一、 引言
JPEG图像压缩标准随然是一种有损图像压缩标准,但由于人眼视觉的不敏感,经压缩后的画质基本没有发生变化,很快便以较高的压缩率得到了广泛的认可。GIF格式虽然仅支持256色但它对于颜色较少的图像有着很高的压缩率,甚至超过JPEG标准,也得到了广泛的认同。但作为众多程序员的一个重要的开发工具--Microsoft Visual C++ 6.0的MFC库却仅对没有经过任何压缩的BMP位图文件有着良好的支持,可以读取、显示、存储甚至在内存中创建一块内存位图。由于BMP格式的图像没有经过任何的压缩,不论是作为程序的外部文件,还是作为程序的内部资源都要占据大量的空间,尤其是后者会大大增加可执行文件的长度。可以看出,如果能用经过压缩、具有较好的压缩率的JPEG或GIF格式的图像来取代BMP文件在VC中的应用,无疑还是很有吸引力的。
二、 设计思路
虽然有一些操作、处理JPEG、GIF等其他格式图像的Active X控件,但总的来说使用起来并不太方便,笔者经过实验摸索,总结出了一种借助于COM接口的OLE方法来实现上述功能的一种简便方法,现介绍如下以飨广大读者:
下面我们要使用IPicture 的COM接口,有必要对该图像接口做些了解:该接口主要管理图像对象及其属性,图像对象为位图、图标和图元等提供一种与语言无关的抽象。和标准的字体对象一样,系统也提供了对图像对象的标准实现。其主要的接口是IPicture和IPictureDisp,后者是由IDispatch接口派生以便通过自动化对图像的属性进行访问。图像对象也支持外部接口IPropertyNotifySink,以便用户能在图像属性发生改变时作出决定。图像对象也支持IPersistStream接口,所以它能从一个IStream接口的实例对象保存、装载自己,而IStream接口也支持对流对象的数据读写。
我们可以用函数OleLoadPicture从包含有图像数据的流中装载图像。该函数简化了基于流的图像对象的创建过程,可以创建一个新的图像对象并且用流中的内容对它进行初始化。其函数原型为:
STDAPI OleLoadPicture( IStream * pStream, //指向包含有图像数据的流的指针LONG lSize, //从流中读取的字节数BOOL fRunmode, //图像属性对应的初值REFIID riid, //涉及到的接口标识,描述要返回的接口指针的类型VOID ppvObj // 在rrid中用到的接口指针变量的地址);
三、 具体的实现
在显示图像之前,首先要获取到图像文件的存放路径,这里采用标准的文件打开对话框来选取图像文件,文件名存放在CString型的变量m_sPath中:
CFileDialog dlg(TRUE,"jpg","*.jpg",
OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
"JPEG文件(*.jpg)|*.jpg|GIF文件(*.gif)|*.gif||",NULL);
if(dlg.DoModal()==IDOK)
{
m_sPath=dlg.GetPathName();
Invalidate();
}
为简单计,图形显示的代码直接在视类中的OnDraw中编写,首先打开文件并判断文件的可用性,并把文件内容放到流接口IStream的对象pStm中:
IStream *pStm;
CFileStatus fstatus;
CFile file;
LONG cb;
……
if (file.Open(m_Path,CFile::modeRead)&&file.GetStatus(m_Path,fstatus)&& ((cb = fstatus.m_size) != -1))
{
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, cb);
LPVOID pvData = NULL;
if (hGlobal != NULL)
{
if ((pvData = GlobalLock(hGlobal)) != NULL)
{
file.ReadHuge(pvData, cb);
GlobalUnlock(hGlobal);
CreateStreamOnHGlobal(hGlobal, TRUE, &pStm);
}
}
}
然后,就直接调用OleLoadPicture函数从流中装载图像:
IPicture *pPic;
……
OleLoadPicture(pStm,fstatus.m_size,TRUE,IID_IPicture,(LPVOID*)&pPic));
由于该函数有时会导致失败,所以应当用SUCCEEDED宏来做一些适当的保护工作,只有在数据装载成功的前提下才能继续下面的图像显示工作:
if(SUCCEEDED(OleLoadPicture(pStm,fstatus.m_size,TRUE,IID_IPicture,(LPVOID*)&pPic)))
{
OLE_XSIZE_HIMETRIC hmWidth;
OLE_YSIZE_HIMETRIC hmHeight;
pPic->get_Width(&hmWidth);
pPic->get_Height(&hmHeight);
double fX,fY;
……
fX = (double)pDC->GetDeviceCaps(HORZRES)*(double)hmWidth/((double)pDC->GetDeviceCaps(HORZSIZE)*100.0);
fY = (double)pDC->GetDeviceCaps(VERTRES)*(double)hmHeight/((double)pDC->GetDeviceCaps(VERTSIZE)*100.0);
if(FAILED(pPic->Render(*pDC,0,0,(DWORD)fX,(DWORD)fY,0,hmHeight,hmWidth,-hmHeight,NULL)))
AfxMessageBox("渲染图像失败!");
pPic->Release();
}
else
AfxMessageBox("从流中装载图像失败!");
其中,显示工作主要是由IPicture接口对象的Render函数来完成的,该函数主要用来将图片的指定部分画到指定的设备环境的指定位置。原型如下:
HRESULT Render( HDC hdc, //渲染图像用的设备环境句柄
long x, //在hdc上的水平坐标
long y, //在hdc上的垂直坐标
long cx, //图像宽度
long cy, //图像高度
OLE_XPOS_HIMETRIC xSrc, //在源图像上的水平偏移
OLE_YPOS_HIMETRIC ySrc, //在源图像上的垂直偏移
OLE_XSIZE_HIMETRIC cxSrc,//在源图像上水平拷贝的数量
OLE_YSIZE_HIMETRIC cySrc,//在源图像上垂直拷贝的数量
LPCRECT prcWBounds //指向目标图元设备环境句柄的指针);
小结:到此为止,通过上述代码已经能够在程序的客户区内显示JPEG、GIF等标准的图像了,但对于有多帧图片(即有动画)的GIF格式的图像,目前还只能显示第一帧,如要完整的显示GIF 动画的全过程,还需要外部Active X控件的支持。
在屏幕上任何地方画图
可以用你的程序在屏幕上的任何地方画图,效果就象电子宠物一样很简单,只要两个API
The first one is getdesktopwindow and the other is getwindowdc
the detail you can see the Win32 help which was plused in Delphi4
For example: you paste a bitmap on the screen
var wnd:longint;
mydc,bitmapdc:Hdc;
mybitmap:Tbitmap;
mybitmap:=Tbitmap.create();
mybitmap.loadfromfile(c:\windows\waves.bmp);
bitmapdc:=getwindowdc(mybitmap.handle);
wnd:=getdesktopwindow();
mydc:=getwindowdc(wnd);
bitblt(my....... //I forget the detail if you have any questions can
// send email to me at bbs
引入湍流概念实现对大理石纹理的造型模拟
摘要:本文通过在物体表面纹理的造型算法中引入流体力学中的湍流概念而成功实现了对大理石表面纹理的模拟仿真。
前言
计算机仿真模拟技术在生物、医学以及国防等诸多科研和应用领域得到了广泛的应用。仿真的过程也就是把仿真对象从物体提炼成数学模型的过程,因此其内在数学模型的提炼和从数学模型设计出相应的仿真算法在整个仿真过程中是很关键的两个环节。本文以大理石为例,通过引入流体力学中湍流概念从数学角度提出了一个仿真效果比较好的纹理仿真算法,并给出了关键部分的VC++编码。
过程纹理造型技术
过程纹理造型技术在功能同二维纹理管理技术比较相似。但由于二维纹理管理技术采用固定的图像来描述表面细节,而客观世界中的自然物体表面纹理却往往具有较强的不规则性和随机性,因此这种采用固定图像对表面细节进行描述的纹理造型技术效果并不好,不能对自然物体进行较好的造型和模糊。在20世纪80年代中后期提出的过程纹理造型技术采用代码段或算法来编码抽取模型细节,并且允许高层控制和规范,从根本上克服了二维纹理管理技术的上述缺陷。而且该技术是通过控制参数来管理纹理数据的放大,因此在仿真过程中只需简单地设定若干个控制参数就可以动态产生大量丰富的几何细节。
1985年由Peachey和Perlin所提出的三维纹理映射方法将三维纹理函数直接定义在三维纹理空间中,数学形式为:
T=T(x,y,z)(T:颜色的RGB值; (x,y,z):空间点三维坐标)。现在用来构造三维纹理的基本方法常用的主要有两种:
基于高频采样的数字化纹理和采用数学模型动态计算生成纹理。基于高频采样的数字化纹理由于需要有三维数组的支持,在处理高分辨率的纹理时占用空间将急剧增大,对空间的要求比较苛刻。一些真实感较强的图形合成系统多采用数学模型动态计算生成纹理的纹理定义方法。经过多年的研究实践已经开发积累了许多过程迭代函数以产生各种复杂的纹理,这些过程纹理(procedural texture)函数被证明是非常有效的,已经可以对木材、大理石、云彩、火焰和石板等许多自然物体的纹理进行逼真的模拟仿真。这些函数从本质上来说都是一种经验模型。
在算法中引入湍流概念
湍流(turbulence)本是流体力学中的重要研究对象,但在此不准备从湍流的精确物理模型出发对其做任何讨论,而是提出一种近似描述该物理现象的经验模型。这种技术最早在1985年由Perlin提出,曾成功用于对大理石、火焰以及云彩等纹理的仿真。这种经验模型是由一系列的三维噪声函数叠加而成,根据流体力学中湍流的有关概念可以得出可以体现上述经验模型的数学表达式:
turbulence(p)=∑|Noise(pow(2,i)*p)/(pow(2,i))|
其中,p为一纹理空间中的点(x,y,z)。求和区间为i从0到k,上限k是满足下列不等式的最小整数:
1/(pow(2,k+1))<像素通长。通过选取满足上述条件的求和上限值 k,可以避免采样湍流函数时的走样现象。该函数表达式任取和式的相邻两项,后一项噪声函数的变化频率总是前一项函数的变化频率的两倍,幅度则为前者的一半,也就是对湍流函数的贡献率减小了一半。因此,上述的构造方法从数学上保证了湍流函数本身就具有一定的自相似特性。从信号分析的角度看,则其功率谱分布满足1/f的规律。该函数并不能直接对纹理进行仿真,我们所借鉴的只是其随机性和自相关特性,这些特性在纹理定义时可以很好的描述各种自然纹理的不规则细节。因此湍流函数在纹理造型算法中还是能起到重要作用的。纹理的描述过程大致分两步:首先选取一个简单的合适的函数来描述自然物体如大理石的基本纹理结构特征,其中选取的这个描述函数一般应当是连续的且其一阶导数应具有较大的变化。在选定描述函数后用湍流函数来对描述函数的某些基本参数进行扰动,以产生复杂的不规则纹理细节。
对于本文的研究对象--大理石,可以注意到大理石漂亮的纹理其实是其内部不同组成材质的颜色的反映,因此可以定义一个函数来描述这种构造。大理石区别于普通石材的一个比较明显的特点是其纹理多呈周期分布,对此特性可用某方向的正弦波函数作为彩色滤波:
marble(p) = marble_color(sin(x))
p仍为空间点(x,y,z),marble_color()为彩色滤波函数,把[-1,1]区间内任一函数值映射成为RGB颜色之间的颜色值,通常将其表达为三条各自独立的样条曲线。在采取了纹理描述的第二步引入了湍流扰动后,上述函数变为最终的描述函数:
marble(p) = marble_color(sin(x+turbulence(p)))
算法的实现
根据前面总结的结论,笔者经过实验发现,如果定义的样条曲线在某些点存在较大的一阶导数,那么用该样条曲线模拟出来的大理石表面纹理将存在较明显的尖锐边界。在下面采取的描述算法中样条曲线为线性函数(一次样条),效果还是适中的:
COLORREF marble(float px,float py,float pz)
{
float y=py+3.0*turbulence(px,py,pz,0.0125);
y=sin(y*M-PI);
return (marble_color(px));
}
……
COLORREF marble_color(float px);
{
COLORREF col;
float x=sqrt(px+1.0)*0.7071;
int G=(int)(0.30+0.8*x);
x=sqrt(x);
int R=(int)(0.30+0.6*x);
int B=(int)(0.60+0.4*x);
return col;
}
以上两段代码可以较好地对大理石纹理做仿真模拟处理。同其他纹理一样,湍流函数也可以用来表达各种表面颜色属性的不规则性如凹凸纹理、透明度等。
小结
本文通过在过程纹理造型技术引入流体力学的湍流概念,成功对大理石的表面纹理作了仿真模拟。通过本文对大理石纹理的计算机仿真的实现过程,可对此类仿真模拟程序有一个基本的认识。湍流函数算法也适用于需要对参数进行扰动处理的其他一些仿真环境。本文程序算法在Windows 98 SE下,由Microsoft Visual C++ 6.0编译调试通过。
用VC实现桌面文字背景透明摆脱黑白
Module name : Transparent.cpp
*
* Module description :
* To make desktop icon text background transparent.
*
* Project :
*
* Target platform : Win32
*
* Compiler & Library : Visual C++ 6.0
*
* Author : Richard Shen
*
* Creation date : 19 June, 1999
*
#include <windows.h>
int main(void)
{
HWND hWnd;
hWnd = GetDesktopWindow();
if ((hWnd = FindWindowEx(hWnd, 0, "Progman", "Program Manager")) == 0)
return 1;
if ((hWnd = FindWindowEx(hWnd, 0, "SHELLDLL_DefView", NULL)) == 0)
return 1;
if ((hWnd = FindWindowEx(hWnd, 0, "SysListView32", NULL)) == 0)
return 1;
// Change icon text attributes
SendMessage(hWnd, 0x1026, 0, 0xffffffff); // Turn background to transparent
SendMessage(hWnd, 0x1024, 0, 0x00ffffff); // Turn foregound to white
InvalidateRect(hWnd, NULL, TRUE); // Repaint
return 0;
} // main()
用渐变色填充背景
Windows的图形界面为我们提供了无穷的方便和视觉上的快感,由浅及深的颜色给我们无尽的遐想。渐变色的实现有多种方法,好多资料上介绍了利用调色板的方法实现,其过程及其的复杂,需要我们具有一定的图形编程的基础,下面我将向大家介绍一种比较简单的方法,即使你一点都不了解图形编程和调色板的概念。
第一步:新建单文档工程,一切参数都取默认值。
第二步:在shadowview.h中定义变量如下:
private:
int ColorR;
int ColorG;
第三步:在shadowview.cpp的构造函数中初始化变量如下:
CShadowView::CShadowView()
{
// TODO: add construction code here
ColorR = 255;
ColorG = 255;
}
第四步:在Ondraw()中添加如下的实现代码:
void CShadowView::OnDraw(CDC* pDC)
{
CShadowDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CRect m_rcClient;
file://得到客户区域的填充矩形
GetClientRect(&m_rcClient);
int nWidth = m_rcClient.Width();
int nHeight = m_rcClient.Height();
CRect rectangle;
file://分割客户区域成小矩形,逐个填充 for(int i = 0;i < nWidth;i++ )
{
rectangle.SetRect(i, 0, i+1, nHeight);
pDC->FillSolidRect(&rectangle, RGB(ColorR, ColorG, 255-MulDiv(i, 255, nWidth)));
}
}
现在编译、运行程序,我们可以发现单文档界面的背景已经被黄渐变色填充。接下来,我们实现在界面上点击鼠标左键,实现背景颜色的改变。
第五步:在ClassWizard中添加鼠标左键的消息处理映射函数,并添加如下代码:
void CShadowView::OnLButtonDown(UINT nFlags, CPoint point)
{
file://生成小于255的随机数,给ColorR和ColorG赋值
int nRand = rand();
float fMap = (float)255/RAND_MAX;
ColorR = (UINT)(float)nRand*fMap + 0.5f;
nRand = rand();
fMap = (float)255/RAND_MAX;
ColorG = (UINT)(float)nRand*fMap + 0.5f;
file://更新界面
Invalidate();
CView::OnLButtonDown(nFlags, point);
}
好了,所有的功能都实现了,在界面上点击鼠标左键,我们可以发现,背景以不同的渐变色填充。
在VC下显示JPEG、GIF格式图像的一种简便方法
一、 引言
JPEG图像压缩标准随然是一种有损图像压缩标准,但由于人眼视觉的不敏感,经压缩后的画质基本没有发生变化,很快便以较高的压缩率得到了广泛的认可。GIF格式虽然仅支持256色但它对于颜色较少的图像有着很高的压缩率,甚至超过JPEG标准,也得到了广泛的认同。但作为众多程序员的一个重要的开发工具--Microsoft Visual C++ 6.0的MFC库却仅对没有经过任何压缩的BMP位图文件有着良好的支持,可以读取、显示、存储甚至在内存中创建一块内存位图。由于BMP格式的图像没有经过任何的压缩,不论是作为程序的外部文件,还是作为程序的内部资源都要占据大量的空间,尤其是后者会大大增加可执行文件的长度。可以看出,如果能用经过压缩、具有较好的压缩率的JPEG或GIF格式的图像来取代BMP文件在VC中的应用,无疑还是很有吸引力的。
二、 设计思路
虽然有一些操作、处理JPEG、GIF等其他格式图像的Active X控件,但总的来说使用起来并不太方便,笔者经过实验摸索,总结出了一种借助于COM接口的OLE方法来实现上述功能的一种简便方法,现介绍如下以飨广大读者:
下面我们要使用IPicture 的COM接口,有必要对该图像接口做些了解:该接口主要管理图像对象及其属性,图像对象为位图、图标和图元等提供一种与语言无关的抽象。和标准的字体对象一样,系统也提供了对图像对象的标准实现。其主要的接口是IPicture和IPictureDisp,后者是由IDispatch接口派生以便通过自动化对图像的属性进行访问。图像对象也支持外部接口IPropertyNotifySink,以便用户能在图像属性发生改变时作出决定。图像对象也支持IPersistStream接口,所以它能从一个IStream接口的实例对象保存、装载自己,而IStream接口也支持对流对象的数据读写。
我们可以用函数OleLoadPicture从包含有图像数据的流中装载图像。该函数简化了基于流的图像对象的创建过程,可以创建一个新的图像对象并且用流中的内容对它进行初始化。其函数原型为:
STDAPI OleLoadPicture( IStream * pStream, //指向包含有图像数据的流的指针LONG lSize, //从流中读取的字节数BOOL fRunmode, //图像属性对应的初值REFIID riid, //涉及到的接口标识,描述要返回的接口指针的类型VOID ppvObj // 在rrid中用到的接口指针变量的地址);
三、 具体的实现
在显示图像之前,首先要获取到图像文件的存放路径,这里采用标准的文件打开对话框来选取图像文件,文件名存放在CString型的变量m_sPath中:
CFileDialog dlg(TRUE,"jpg","*.jpg",
OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
"JPEG文件(*.jpg)|*.jpg|GIF文件(*.gif)|*.gif||",NULL);
if(dlg.DoModal()==IDOK)
{
m_sPath=dlg.GetPathName();
Invalidate();
}
为简单计,图形显示的代码直接在视类中的OnDraw中编写,首先打开文件并判断文件的可用性,并把文件内容放到流接口IStream的对象pStm中:
IStream *pStm;
CFileStatus fstatus;
CFile file;
LONG cb;
……
if (file.Open(m_Path,CFile::modeRead)&&file.GetStatus(m_Path,fstatus)&& ((cb = fstatus.m_size) != -1))
{
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, cb);
LPVOID pvData = NULL;
if (hGlobal != NULL)
{
if ((pvData = GlobalLock(hGlobal)) != NULL)
{
file.ReadHuge(pvData, cb);
GlobalUnlock(hGlobal);
CreateStreamOnHGlobal(hGlobal, TRUE, &pStm);
}
}
}
然后,就直接调用OleLoadPicture函数从流中装载图像:
IPicture *pPic;
……
OleLoadPicture(pStm,fstatus.m_size,TRUE,IID_IPicture,(LPVOID*)&pPic));
由于该函数有时会导致失败,所以应当用SUCCEEDED宏来做一些适当的保护工作,只有在数据装载成功的前提下才能继续下面的图像显示工作:
if(SUCCEEDED(OleLoadPicture(pStm,fstatus.m_size,TRUE,IID_IPicture,(LPVOID*)&pPic)))
{
OLE_XSIZE_HIMETRIC hmWidth;
OLE_YSIZE_HIMETRIC hmHeight;
pPic->get_Width(&hmWidth);
pPic->get_Height(&hmHeight);
double fX,fY;
……
fX = (double)pDC->GetDeviceCaps(HORZRES)*(double)hmWidth/((double)pDC->GetDeviceCaps(HORZSIZE)*100.0);
fY = (double)pDC->GetDeviceCaps(VERTRES)*(double)hmHeight/((double)pDC->GetDeviceCaps(VERTSIZE)*100.0);
if(FAILED(pPic->Render(*pDC,0,0,(DWORD)fX,(DWORD)fY,0,hmHeight,hmWidth,-hmHeight,NULL)))
AfxMessageBox("渲染图像失败!");
pPic->Release();
}
else
AfxMessageBox("从流中装载图像失败!");
其中,显示工作主要是由IPicture接口对象的Render函数来完成的,该函数主要用来将图片的指定部分画到指定的设备环境的指定位置。原型如下:
HRESULT Render( HDC hdc, //渲染图像用的设备环境句柄
long x, //在hdc上的水平坐标
long y, //在hdc上的垂直坐标
long cx, //图像宽度
long cy, //图像高度
OLE_XPOS_HIMETRIC xSrc, //在源图像上的水平偏移
OLE_YPOS_HIMETRIC ySrc, //在源图像上的垂直偏移
OLE_XSIZE_HIMETRIC cxSrc,//在源图像上水平拷贝的数量
OLE_YSIZE_HIMETRIC cySrc,//在源图像上垂直拷贝的数量
LPCRECT prcWBounds //指向目标图元设备环境句柄的指针);
小结:到此为止,通过上述代码已经能够在程序的客户区内显示JPEG、GIF等标准的图像了,但对于有多帧图片(即有动画)的GIF格式的图像,目前还只能显示第一帧,如要完整的显示GIF 动画的全过程,还需要外部Active X控件的支持。
在屏幕上任何地方画图
可以用你的程序在屏幕上的任何地方画图,效果就象电子宠物一样很简单,只要两个API
The first one is getdesktopwindow and the other is getwindowdc
the detail you can see the Win32 help which was plused in Delphi4
For example: you paste a bitmap on the screen
var wnd:longint;
mydc,bitmapdc:Hdc;
mybitmap:Tbitmap;
mybitmap:=Tbitmap.create();
mybitmap.loadfromfile(c:\windows\waves.bmp);
bitmapdc:=getwindowdc(mybitmap.handle);
wnd:=getdesktopwindow();
mydc:=getwindowdc(wnd);
bitblt(my....... //I forget the detail if you have any questions can
// send email to me at bbs
引入湍流概念实现对大理石纹理的造型模拟
摘要:本文通过在物体表面纹理的造型算法中引入流体力学中的湍流概念而成功实现了对大理石表面纹理的模拟仿真。
前言
计算机仿真模拟技术在生物、医学以及国防等诸多科研和应用领域得到了广泛的应用。仿真的过程也就是把仿真对象从物体提炼成数学模型的过程,因此其内在数学模型的提炼和从数学模型设计出相应的仿真算法在整个仿真过程中是很关键的两个环节。本文以大理石为例,通过引入流体力学中湍流概念从数学角度提出了一个仿真效果比较好的纹理仿真算法,并给出了关键部分的VC++编码。
过程纹理造型技术
过程纹理造型技术在功能同二维纹理管理技术比较相似。但由于二维纹理管理技术采用固定的图像来描述表面细节,而客观世界中的自然物体表面纹理却往往具有较强的不规则性和随机性,因此这种采用固定图像对表面细节进行描述的纹理造型技术效果并不好,不能对自然物体进行较好的造型和模糊。在20世纪80年代中后期提出的过程纹理造型技术采用代码段或算法来编码抽取模型细节,并且允许高层控制和规范,从根本上克服了二维纹理管理技术的上述缺陷。而且该技术是通过控制参数来管理纹理数据的放大,因此在仿真过程中只需简单地设定若干个控制参数就可以动态产生大量丰富的几何细节。
1985年由Peachey和Perlin所提出的三维纹理映射方法将三维纹理函数直接定义在三维纹理空间中,数学形式为:
T=T(x,y,z)(T:颜色的RGB值; (x,y,z):空间点三维坐标)。现在用来构造三维纹理的基本方法常用的主要有两种:
基于高频采样的数字化纹理和采用数学模型动态计算生成纹理。基于高频采样的数字化纹理由于需要有三维数组的支持,在处理高分辨率的纹理时占用空间将急剧增大,对空间的要求比较苛刻。一些真实感较强的图形合成系统多采用数学模型动态计算生成纹理的纹理定义方法。经过多年的研究实践已经开发积累了许多过程迭代函数以产生各种复杂的纹理,这些过程纹理(procedural texture)函数被证明是非常有效的,已经可以对木材、大理石、云彩、火焰和石板等许多自然物体的纹理进行逼真的模拟仿真。这些函数从本质上来说都是一种经验模型。
在算法中引入湍流概念
湍流(turbulence)本是流体力学中的重要研究对象,但在此不准备从湍流的精确物理模型出发对其做任何讨论,而是提出一种近似描述该物理现象的经验模型。这种技术最早在1985年由Perlin提出,曾成功用于对大理石、火焰以及云彩等纹理的仿真。这种经验模型是由一系列的三维噪声函数叠加而成,根据流体力学中湍流的有关概念可以得出可以体现上述经验模型的数学表达式:
turbulence(p)=∑|Noise(pow(2,i)*p)/(pow(2,i))|
其中,p为一纹理空间中的点(x,y,z)。求和区间为i从0到k,上限k是满足下列不等式的最小整数:
1/(pow(2,k+1))<像素通长。通过选取满足上述条件的求和上限值 k,可以避免采样湍流函数时的走样现象。该函数表达式任取和式的相邻两项,后一项噪声函数的变化频率总是前一项函数的变化频率的两倍,幅度则为前者的一半,也就是对湍流函数的贡献率减小了一半。因此,上述的构造方法从数学上保证了湍流函数本身就具有一定的自相似特性。从信号分析的角度看,则其功率谱分布满足1/f的规律。该函数并不能直接对纹理进行仿真,我们所借鉴的只是其随机性和自相关特性,这些特性在纹理定义时可以很好的描述各种自然纹理的不规则细节。因此湍流函数在纹理造型算法中还是能起到重要作用的。纹理的描述过程大致分两步:首先选取一个简单的合适的函数来描述自然物体如大理石的基本纹理结构特征,其中选取的这个描述函数一般应当是连续的且其一阶导数应具有较大的变化。在选定描述函数后用湍流函数来对描述函数的某些基本参数进行扰动,以产生复杂的不规则纹理细节。
对于本文的研究对象--大理石,可以注意到大理石漂亮的纹理其实是其内部不同组成材质的颜色的反映,因此可以定义一个函数来描述这种构造。大理石区别于普通石材的一个比较明显的特点是其纹理多呈周期分布,对此特性可用某方向的正弦波函数作为彩色滤波:
marble(p) = marble_color(sin(x))
p仍为空间点(x,y,z),marble_color()为彩色滤波函数,把[-1,1]区间内任一函数值映射成为RGB颜色之间的颜色值,通常将其表达为三条各自独立的样条曲线。在采取了纹理描述的第二步引入了湍流扰动后,上述函数变为最终的描述函数:
marble(p) = marble_color(sin(x+turbulence(p)))
算法的实现
根据前面总结的结论,笔者经过实验发现,如果定义的样条曲线在某些点存在较大的一阶导数,那么用该样条曲线模拟出来的大理石表面纹理将存在较明显的尖锐边界。在下面采取的描述算法中样条曲线为线性函数(一次样条),效果还是适中的:
COLORREF marble(float px,float py,float pz)
{
float y=py+3.0*turbulence(px,py,pz,0.0125);
y=sin(y*M-PI);
return (marble_color(px));
}
……
COLORREF marble_color(float px);
{
COLORREF col;
float x=sqrt(px+1.0)*0.7071;
int G=(int)(0.30+0.8*x);
x=sqrt(x);
int R=(int)(0.30+0.6*x);
int B=(int)(0.60+0.4*x);
return col;
}
以上两段代码可以较好地对大理石纹理做仿真模拟处理。同其他纹理一样,湍流函数也可以用来表达各种表面颜色属性的不规则性如凹凸纹理、透明度等。
小结
本文通过在过程纹理造型技术引入流体力学的湍流概念,成功对大理石的表面纹理作了仿真模拟。通过本文对大理石纹理的计算机仿真的实现过程,可对此类仿真模拟程序有一个基本的认识。湍流函数算法也适用于需要对参数进行扰动处理的其他一些仿真环境。本文程序算法在Windows 98 SE下,由Microsoft Visual C++ 6.0编译调试通过。
用VC实现桌面文字背景透明摆脱黑白
Module name : Transparent.cpp
*
* Module description :
* To make desktop icon text background transparent.
*
* Project :
*
* Target platform : Win32
*
* Compiler & Library : Visual C++ 6.0
*
* Author : Richard Shen
*
* Creation date : 19 June, 1999
*
#include <windows.h>
int main(void)
{
HWND hWnd;
hWnd = GetDesktopWindow();
if ((hWnd = FindWindowEx(hWnd, 0, "Progman", "Program Manager")) == 0)
return 1;
if ((hWnd = FindWindowEx(hWnd, 0, "SHELLDLL_DefView", NULL)) == 0)
return 1;
if ((hWnd = FindWindowEx(hWnd, 0, "SysListView32", NULL)) == 0)
return 1;
// Change icon text attributes
SendMessage(hWnd, 0x1026, 0, 0xffffffff); // Turn background to transparent
SendMessage(hWnd, 0x1024, 0, 0x00ffffff); // Turn foregound to white
InvalidateRect(hWnd, NULL, TRUE); // Repaint
return 0;
} // main()
用渐变色填充背景
第一步:新建单文档工程,一切参数都取默认值。
第二步:在shadowview.h中定义变量如下:
private:
int ColorR;
int ColorG;
第三步:在shadowview.cpp的构造函数中初始化变量如下:
CShadowView::CShadowView()
{
// TODO: add construction code here
ColorR = 255;
ColorG = 255;
}
第四步:在Ondraw()中添加如下的实现代码:
void CShadowView::OnDraw(CDC* pDC)
{
CShadowDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CRect m_rcClient;
file://得到客户区域的填充矩形
GetClientRect(&m_rcClient);
int nWidth = m_rcClient.Width();
int nHeight = m_rcClient.Height();
CRect rectangle;
file://分割客户区域成小矩形,逐个填充 for(int i = 0;i < nWidth;i++ )
{
rectangle.SetRect(i, 0, i+1, nHeight);
pDC->FillSolidRect(&rectangle, RGB(ColorR, ColorG, 255-MulDiv(i, 255, nWidth)));
}
}
现在编译、运行程序,我们可以发现单文档界面的背景已经被黄渐变色填充。接下来,我们实现在界面上点击鼠标左键,实现背景颜色的改变。
第五步:在ClassWizard中添加鼠标左键的消息处理映射函数,并添加如下代码:
void CShadowView::OnLButtonDown(UINT nFlags, CPoint point)
{
file://生成小于255的随机数,给ColorR和ColorG赋值
int nRand = rand();
float fMap = (float)255/RAND_MAX;
ColorR = (UINT)(float)nRand*fMap + 0.5f;
nRand = rand();
fMap = (float)255/RAND_MAX;
ColorG = (UINT)(float)nRand*fMap + 0.5f;
file://更新界面
Invalidate();
CView::OnLButtonDown(nFlags, point);
}
好了,所有的功能都实现了,在界面上点击鼠标左键,我们可以发现,背景以不同的渐变色填充。