又是n(n>7)天没有来维护自己的blog了,还是因为没有时间,今天才想起在我得blog上好像还有没有完成的文章(真是没有责任心的一个人),所以,无论如何今天都要抽点时间来完成自己的东东,总不能背一个半途而废的名声吧。
今天是最后一话了,而且废话了这么久都没有切入主题,偶对不住大家啦,今天我们就正儿八经的的进入这次我们这个冗长的故事的核心的部分了,同时也是最后一个部分。
书归正传,在direct编程里面有两个比较重要的函数,也是使用频率相当高的两个函数,在进入主题前我们必须先介绍这两个函数,我们这个程序里面也是用了他们。
第一个很有用的函数是BltFast(),他的原型是
HRESULT BltFast(
DWORD dwX,
DWORD dwY,
LPDIRECTDRAWSURFACE lpDDSrcSurface,
LPRECT lpSrcRect,
DWORD dwTrans
);
下面将逐一介绍这几个参数:
(1)dwX和dwY:图像将被传送到目标页面何处。
(2)lpDDSrcSurface:图像传送操作的源页面。目标页面就是调用此方法的页面。
(3)lpSrcRect:一个 RECT (Rectangle,即矩形)结构的地址,指明源页面上将被传送的区域。如果该参数是NULL,整个源页面将被使用。RECT结构在DirectDraw中非常常用,最好在程序中定义一个RECT类型的全局变量,如rect,再象这样写一个函数:void MakeRect ()(关于这个函数就是我说的第二个非常有用的函数,我们在后面在专门对他进行介绍和说明)。
(4)dwTrans:指定传送类型。有如下几种:
DDBLTFAST_NOCOLORKEY
指定进行一次普通的复制,不带透明成分。
DDBLTFAST_SRCCOLORKEY
指定进行一次带透明色的图像传送,使用源页面的透明色。
DDBLTFAST_WAIT
如果图像传送器正忙,不断重试直到图像传送器准备好并传送好时才返回。一般都使用这个参数。由于第四个参数比较长,而且有时候是两个搭配起来用,所以大多数时候都是定义两个全局变量,后面使用起来就比较方便。
DWORD SrcKey = DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT
DWORD NoKey = DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT
第二个很有用的函数就是我们前面提到的void MakeRect (),这一般是一个自定义函数,我们可以这样定义他:
void MakeRect (int left, int top, int right, int bottom)
{
rect.bottom = bottom;
rect.left = left;
rect.right = right;
rect.top = top;
}
主要是确定了这个r结构的四个边际。
所以要裁减一副图片到一个页面中就很简单了,是不是^_^,举个例子:我们要贴第一回的哪个背景到SPrimary这个主页面里,就可以这样
MakeRect(0,0,640,480);
lpDDSBuffer->BltFast(0,0,lpDDSPrimary,&r,NoKey);
核心部分
因为哪个背景的大小是640*480,所以我们的右边际是640,下边际是480,最后在利用BltFast()函数贴出来就是了,而且没有透明,这样我们就可以贴图了。
可是怎么样裁减呢?其实也就没什么难度了,你想在MakeRect()这个函数里面我们改变要贴图的大小不就可以贴同一幅图里任意位置的图部分了吗,这就是裁减了塞,然后我们设成变量,用鼠标自定义贴的大小,就必须和windows的消息循环机制联合起来使用了塞,我们在mousedown的时候得到他的一个坐标,在mouseup的时候得到他的另一个坐标,将两个坐标之间的图片贴出来就可以了塞。其实这个裁减程序的主要的核心技术就在这里了,实现思想也就是这么回事,简单吧:),其他的都是一些旁支末节的东西了。不过我们还是要一一介绍完。
ok,到这里我们就可以单独写一个类出来,完成我们的图片裁减和贴图功能了,我们写一个类叫draw.cpp,这里我们还把他的头文件draw.h写出来。
draw.h:
#if !defined (draw_h)
#define draw_h
class draw
{
public:
int x1;
int y1;
int x2;
int y2;
void Rectanglep(int x1, int y1, int width, int height);//做成一个鼠标在屏幕移动矩形的函数
void blt(int x1,int y1,int x2,int y2);//完成我们的图片裁减功能
};
#endif
draw.cpp:
#include "main.h"
void draw::blt(int x1,int y1,int x2,int y2)
{
x1=drawp.x1;
y2=drawp.y1;
x2=drawp.x2;
y2=drawp.y2;
MakeRect(x1,y1,x2,y2);
lpDDSBuffer->BltFast(0,0,lpDDSMap,&r,SrcKey);
}
void draw::Rectanglep(int x1,int y1,int x2,int y2)
{
HDC hdc;
lpDDSPrimary->GetDC(&hdc);//主页面得到句柄
HPEN Red_Pen=CreatePen(PS_SOLID,0,RGB(0,255,0));//填充画笔的颜色
SelectObject(hdc,Red_Pen);//设置画笔的类型
Line(x1,y1,x1,y2,hdc);//画线
Line(x1,y1,x2,y1,hdc);
Line(x2,y1,x2,y2,hdc);
Line(x1,y2,x2,y2,hdc);
lpDDSPrimary->ReleaseDC(hdc);//用完后释放句柄
}
这里就是对这两自定义函数的具体化了。
第一个函数就是我们前面介绍的实现图形的裁减的部分,而且定义了四个变量作为鼠标取点的坐标。
第二个函数就是鼠标在屏幕上运动画出的轨迹,是由四条线组成的一个矩形,这里用到了API函数作图,也就是微软给我们的函数(微软什么都给我们封装好了,用起来还真是方便呀)。
然后就是公共使用的函数的一个类,这里面自定义的函数可以全部被自由的使用
我们定义为
publicfuction.cpp
//direct的初始化,前面介绍过,可以参看前面的章节
void init()
{
DDSURFACEDESC2 ddsd;
DirectDrawCreateEx (NULL, (void **)&lpDD,IID_IDirectDraw7, NULL);
lpDD->SetCooperativeLevel(hwnd,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
lpDD->SetDisplayMode( 640, 480, 32, 0, DDSDM_STANDARDVGAMODE );
memset(&ddsd, 0, sizeof(DDSURFACEDESC2));
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE|DDSCAPS_FLIP|DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
lpDDSPrimary->GetAttachedSurface( &ddsd.ddsCaps, &lpDDSBuffer );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwHeight = 480;
ddsd.dwWidth = 640;
lpDD->CreateSurface( &ddsd, &lpDDSMap, NULL );
ddsd.dwHeight=26;
ddsd.dwWidth=32;
lpDD->CreateSurface( &ddsd, &lpDDSMouse, NULL);
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
DDBLTFX ddBltFx;
ddBltFx.dwSize=sizeof(DDBLTFX);
ddBltFx.dwFillColor=0;
lpDDSPrimary ->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSMap->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSMouse->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
DDReLoadBitmap(lpDDSMap,"inn.BMP");
DDReLoadBitmap(lpDDSMouse,"mouse.BMP");
MakeRect(0,0,640,480);
lpDDSPrimary->BltFast(0,0,lpDDSMap,&r,NoKey);
DDSetColorKey(lpDDSMap,RGB(0,255,0));
DDSetColorKey(lpDDSMouse,RGB(0,255,0));
}
//取矩形举例的函数,前面也介绍过
void MakeRect(int left,int top,int right,int bottom)
{
r.bottom=bottom;
r.left=left;
r.right=right;
r.top=top;
}
//画直线的函数
void Line(int x1,int y1,int x2,int y2,HDC dc)
{
MoveToEx (dc, x1, y1, 0);//得到画直线的起始坐标
LineTo (dc, x2, y2);//得到画直线的终点坐标
}
//在屏幕上输出文字的函数
void print(char text[255],int x,int y)
{
HDC hdc;
lpDDSBuffer->GetDC(&hdc);//后台页面得到句柄
SetBkMode(hdc, TRANSPARENT);//设置显示模式
SetTextColor(hdc, RGB(0,255,255));//输出文字颜色
TextOut(hdc, x, y , text, strlen(text));//输出文字
lpDDSBuffer->ReleaseDC(hdc);//释放句柄
}
//前台和后台页面不断交换的函数,这样才能交替显示两个页面
void Flip()
{
HRESULT ddrval;
ddrval=lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);
if (ddrval==DDERR_SURFACELOST)
Restore();
}
//指针释放的函数,指针用完以后得释放
HRESULT Restore(void)
{
HRESULT ddrval;
ddrval = lpDDSPrimary->Restore();
ddrval = lpDDSBuffer->Restore();
ddrval = lpDDSMouse->Restore();
ddrval = lpDDSMap->Restore();
init();
return ddrval;
}
在公共类里面还有一个最关键得函数就是refresh(),
void refresh()
{
//将背景图贴到buffer页面了,这样刷新的时候一直都在显示一幅图
MakeRect(0,0,640,480);
lpDDSBuffer->BltFast(0,0,lpDDSMap,&r,NoKey);
//鼠标的移动,同样写在刷新里面是为了不会有鼠标移动过的痕迹留下
POINT curpos;
GetCursorPos(&curpos);
for (int i=0;i<2;i++)
{
CursorX[i]=CursorX[i+1];
CursorY[i]=CursorY[i+1];
CursorX[2]=curpos.x;
CursorY[2]=curpos.y;
lpDDSBuffer->BltFast(CursorX[1],CursorY[1],lpDDSMouse,NULL,SrcKey);
}
//ok,调用了裁减的方法,有一个标志,在这个标志为1的时候才调用
if(enable==1)
drawp.blt(drawp.x1,drawp.y1,drawp.x2,drawp.y2);
//调用了鼠标移动画矩形的方法,也有一个标志,当标志为2的时候才调用
if(enable==2)
drawp.Rectanglep(x,y,curpos.x,curpos.y);
//为字符串赋值,以便调用这些字符串
char *text="LEFT MOUSEDOWN:点击鼠标左键可以确定裁剪的一个顶点坐标";
char *text1="LEFT MOUSEMOVE:拖动鼠标可以选取裁剪的区域";
char *text2="LEFT MOUSEUP:放掉鼠标左键可以取定另外一个顶点坐标";
char *text3="SPACE:按空格键可以贴出裁剪区域";
char *text4="世间本无事,庸人自扰自!";
//调用显示字的自定义函数,在屏幕上显示文字,放在刷新页面里是因为可以一直都显示
print(text,10,10);
print(text1,10,30);
print(text2,10,50);
print(text3,10,70);
print(text4,400,450);
//翻页,主页和buffer页交替显示
Flip();
}
就这样,这就是全部功能的实现和原理了,再回到消息循环机制里面看看。
main.h:
#if !defined(Course_Design)
#define Course_Design
#include <windows.h>
#include <math.h>
#include <ddraw.h>
#include "ddutil.h"
#include "draw.h"
extern HDC hdc;
extern RECT r;
extern HWND hwnd;
extern DWORD SrcKey;
extern DWORD NoKey;
extern POINT curpos;
extern int enable;
extern int x;
extern int y;
extern int x3;
extern int y3;
extern LPDIRECTDRAW7 lpDD;
extern LPDIRECTDRAWSURFACE7 lpDDSPrimary;
extern LPDIRECTDRAWSURFACE7 lpDDSBuffer;
extern LPDIRECTDRAWSURFACE7 lpDDSMap;
extern LPDIRECTDRAWSURFACE7 lpDDSMouse;
extern draw drawp;
void init();
void MakeRect(int left,int top,int right,int bottom);
void refresh();
void Flip();
void print(char text[255],int x,int y);
void Line(int x1,int y1,int x2,int y2,HDC dc);
HRESULT Restore(void);
#endif
main.cpp
#include "main.h"
main.cpp
BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
HWND hwnd;
RECT r;
HDC hdc;
POINT curpos;
int enable;
int x;
int y;
int x3;
int y3;
LPDIRECTDRAW7 lpDD;
LPDIRECTDRAWSURFACE7 lpDDSPrimary;
LPDIRECTDRAWSURFACE7 lpDDSBuffer;
LPDIRECTDRAWSURFACE7 lpDDSMap;
LPDIRECTDRAWSURFACE7 lpDDSMouse;
DWORD SrcKey = DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT;
DWORD NoKey = DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT;
draw drawp;
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
InitWindow(hInstance,nCmdShow);
init();//初始化
while(1)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message==WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
refresh();//消息循环,完成刷新功能,具体功能在前面介绍这个函数时说过了
}
}
return msg.wParam;
}
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )
{
WNDCLASS wc;
wc.style = NULL;
wc.lpfnWndProc = (WNDPROC)WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "Course Design";
RegisterClass(&wc);
hwnd = CreateWindow("Course Design","My Course
Design",WS_POPUP|WS_MAXIMIZE,0,0,GetSystemMetrics( SM_CXSCREEN ),GetSystemMetrics( SM_CYSCREEN ), NULL,NULL,hInstance,NULL);
if( !hwnd ) return FALSE;
ShowWindow(hwnd,nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_SETCURSOR:
SetCursor(NULL);
return 0;
case WM_KEYDOWN:
switch( wParam )
{
case VK_ESCAPE://按ESC键退出程序
MessageBox(hWnd,"quit!","Keyboard",MB_OK);
PostQuitMessage( 0 );
break;
case VK_SPACE://按空格键得到两个坐标,并将标志设为1
drawp.x1=x;
drawp.y1=y;
drawp.x2=x3;
drawp.y2=y3;
enable=1;
return 0;
}
return 0;
case WM_LBUTTONDOWN://按下鼠标左键得到一个点的坐标
x=(int)LOWORD(lParam);
y=(int)HIWORD(lParam);
break;
case WM_LBUTTONUP://放开鼠标左键得到一个点的坐标。并画出这个矩形
x3=(int)LOWORD(lParam);
y3=(int)HIWORD(lParam);
drawp.Rectanglep(x,y,x3,y3);
break;
case WM_MOUSEMOVE://鼠标移动时设置标志为2
enable=2;
break;
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
这样全部代码我就放出来,我想只要比照着注释看就能明白个大概了。
总结之,洋洋洒洒的写了这么多东西,自己都感觉很凌乱,用一句口头禅:“清不到方向”,所以有必要总结一下,总体来说,我们做了一个图片裁减的小程序,然后这是一个很简单的程序,主要用到了Windows消息循环和Direct编程这两个方面的知识,而要实现程序的核心就是裁减也就只用到了Direct编程的贴图(BltFast())函数,也就是贴图的大小和范围的变化不同就实现了裁减了。(程序-->裁减-->direct-->BltFast),这就是核心完成的任务了,其他的无非是使用起来方便或者其他的。希望我把自己要说的说清楚了的哈。希望大家能看懂我说的是什么,也就不枉费我莫名其妙的说了一大串了哈。
说实话这是我第一次写这么多字的技术性文章,没有什么经验,自己都觉得写的比较凌乱而且似乎没有重点,我只能做到尽量做到把关键的原理和知识说清楚,把源代码尽量多的做上注释,让大家看明白,如果有什么看不懂和希望问的就给我留言到邮箱,有时间的话,我会尽量给大家回复清楚的。其实Dierct编程是很有意思的一个部分,这里面包含了很多的东西,甚至还可以做游戏,不过我也只是一知半解,还有很多东西至今没有琢磨透,同时也非常希望在这方面编程有自己的心得和体会的朋友多和我交流,共同进步啦。
总算大功告成,辛苦啊!!
一直以为写东西是件多么简单的事,到今天自己亲自尝试了后才知道,原本世界上根本没有什么简单的东西哈,这样一来更加佩服侯捷老师和李维老师的智慧了,写出来的东西丝丝入扣,不但能够简单明了的说清楚事物的本质,而且语言幽默,看了以后真是过目不忘哈。还应该多多学习哈。