**
**
简单图形编程的学习(2)-–点 (Windows GDI实现)
一、 又一顿牢骚
虽然知道不应该老是说些与技术无关的话。。。。但是有的时候又总是想说。。。。难怪有同事说我最近已经有点像唐僧了-_-!总而言之,因为相对来说书看的太快,(现在租的房子离公司太远,老是坐地铁,导致有了非常固定的看书时间),而因为工作一直太忙,一直加班回家太晚的原因,所以实际的实践太慢(基本上现在就是以写博客的形式),所以Qt与Android的部分要是同步跟上我DirectX的书都要看好几本了,这样的方式好像不太好,所以目前暂时还是以Windows的为主了。。。。这也体现了一点理想与现实的差距-_-!虽然初期项目目标过大,项目中即时调整起码还能保证项目完成吧。。。。。(扯的远了)。
二、 画点
很简单的一个Windows GDI函数,SetPixel,原型如下:
COLORREF SetPixel(
HDC _ hdc_, // handle to DC
int _ X_, // x-coordinate of pixel
int _ Y_, // y-coordinate of pixel
COLORREF _ crColor_ // pixel color
);
也许属于最最简单的Windows GDI 之一了,但是如同在Small Basic画点中所讲解的一样,画点这样简单的函数可以实现无限的效果。还是那句话,能够设置一个像素点,就能够描绘一个世界。。。。。。唯一限制程序实现的就是编写程序人的思维。
因为Windows代码本身的复杂性,不能如Small Basic那样直击要害,要是每次给出完整代码太占用空间,这里给出《Tricks Of the Windows Game Programming GURUS》一书中的框架代码,方便以后代码的添加。
// T3D Game Console, creates a game console application
// INCLUDES ///////////////////////////////////////////////
#define WIN32_LEAN_AND_MEAN // just say no to MFC
#include <tchar.h>
#include <windows.h> // include important windows stuff
#include <windowsx.h>
#include <mmsystem.h>
#include <iostream> // include important C/C++ stuff
#include <conio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <io.h>
#include <fcntl.h>
// DEFINES ////////////////////////////////////////////////
// defines for windows
#define WINDOW_CLASS_NAME _T("WINCLASS1")
// MACROS /////////////////////////////////////////////////
#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEYUP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
// GLOBALS ////////////////////////////////////////////////
HWND main_window_handle = NULL; // globally track main window
HINSTANCE hinstance_app = NULL; // globally track hinstance
char buffer[80]; // general printing buffer
#define FRAME_PER_SECOND (20)
#define TIME_IN_FRAME (1000/FRAME_PER_SECOND)
#define WIDTH 800
#define HEIGHT 600
// FUNCTIONS //////////////////////////////////////////////
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
// this is the main message handler of the system
PAINTSTRUCT ps; // used in WM_PAINT
HDC hdc; // handle to a device context
char buffer[80] = {0}; // used to print strings
// what is the message
switch(msg)
{
case WM_CREATE:
{
// do initialization stuff here
// return success
return(0);
} break;
case WM_PAINT:
{
// simply validate the window
hdc = BeginPaint(hwnd,&ps);
// end painting
EndPaint(hwnd,&ps);
// return success
return(0);
} break;
case WM_DESTROY:
{
// kill the application, this sends a WM_QUIT message
PostQuitMessage(0);
// return success
return(0);
} break;
default:break;
} // end switch
// process any messages that we didn't take care of
return (DefWindowProc(hwnd, msg, wparam, lparam));
} // end WinProc
///////////////////////////////////////////////////////////
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
{
Sleep(1);
}
// return success or failure or your own return code here
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
// return success or failure or your own return code here
return(1);
} // end Game_Init
/////////////////////////////////////////////////////////////
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all you cleanup and shutdown here
// return success or failure or your own return code here
return(1);
} // end Game_Shutdown
// WINMAIN ////////////////////////////////////////////////
int WINAPI WinMain( HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
WNDCLASSEX winclass; // this will hold the class we create
HWND hwnd; // generic window handle
MSG msg; // generic message
HDC hdc; // graphics device context
// first fill in the window class stucture
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC |
CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = WINDOW_CLASS_NAME;
winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// save hinstance in global
hinstance_app = hinstance;
// register the window class
if (!RegisterClassEx(&winclass))
return(0);
// create the window
if (!(hwnd = CreateWindowEx(NULL, // extended style
WINDOW_CLASS_NAME, // class
_T("Show Point 0.1"), // title
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0,0, // initial x,y
WIDTH,HEIGHT, // initial width, height
NULL, // handle to parent
NULL, // handle to menu
hinstance,// instance of this application
NULL))) // extra creation parms
return(0);
// save main window handle
main_window_handle = hwnd;
// initialize game here
Game_Init();
// enter main event loop
while(TRUE)
{
// test if there is a message in queue, if so get it
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
// test if this is a quit
if (msg.message == WM_QUIT)
break;
// translate any accelerator keys
TranslateMessage(&msg);
// send the message to the window proc
DispatchMessage(&msg);
} // end if
// main game processing goes here
Game_Main();
} // end while
// closedown game here
Game_Shutdown();
// return to Windows like this
return(msg.wParam);
} // end WinMain
///////////////////////////////////////////////////////////
1. 随机在屏幕上画随机颜色点
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
HDC hdc;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
hdc = GetDC(main_window_handle);
// draw 1000 pixels
for (int index=0; index < 1000; index++)
{
// get random position
int x = rand()%WIDTH;
int y = rand()%HEIGHT;
COLORREF color = RGB(rand()%255,rand()%255,rand()%255);
SetPixel(hdc, x,y, color);
} // end for index
// release the dc
ReleaseDC(main_window_handle, hdc);
while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
{
Sleep(1);
}
// return success or failure or your own return code here
return(1);
} // end Game_Main
同样还是简单,当然,前提是框架存在并且熟悉了一大堆Windows的特性以后,说实话,Windows的死消息机制也不是一天两天就熟悉的了的,我当年学MFC的时候那是一愣一愣的,用了较旧以后才能大概知道怎么回事儿。知道这些以后,剩下的也就是一个Rand函数+SetPixel函数的理解量了。这里不放截图了,这么简单的东西放个截图我都觉得没有意思。
2. 老电视机雪花点的效果:
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
HDC hdc;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
hdc = GetDC(main_window_handle);
// draw 1000 pixels
for (int index=0; index < 500; index++)
{
// get random position
int x = rand()%WIDTH;
int y = rand()%HEIGHT;
COLORREF color = RGB(255,255,255);
SetPixel(hdc, x,y, color);
} // end for index
// release the dc
ReleaseDC(main_window_handle, hdc);
Sleep(10);
InvalidateRect(main_window_handle, NULL, TRUE);
//while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
//{
// Sleep(1);
//}
// return success or failure or your own return code here
return(1);
} // end Game_Main
这样基本能够达到效果,但是实际上还有点问题,为了统一程序的架构方便讲解才这样写代码,这里因为利用Windows原有消息去擦除原有点,放在WM_DRAW中去做画点效果会更好一点。
3. 闪烁的星空:
#define POSITION_COUNT (500)
int gaWidth[POSITION_COUNT];
int gaHeight[POSITION_COUNT];
///////////////////////////////////////////////////////////
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
HDC hdc;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
hdc = GetDC(main_window_handle);
for (int index=0; index < POSITION_COUNT; index++)
{
SetPixel(hdc, gaWidth[index], gaHeight[index], RGB(255,255,255));
gaWidth[index] += 1;
gaHeight[index] += 1;
} // end for index
Sleep(1000);
Sleep(10);
// release the dc
ReleaseDC(main_window_handle, hdc);
//while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
//{
// Sleep(1);
//}
// return success or failure or your own return code here
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
// 一次初始化一个随机POSITION_COUNT大小的数组用于固定位置
for (int index=0; index < POSITION_COUNT; index++)
{
// get random position
gaWidth[index] = rand()%WIDTH;
gaHeight[index] = rand()%HEIGHT;
} // end for index
// return success or failure or your own return code here
return(1);
} // end Game_Init
按Small Basic实现的方式我发现闪烁效果没有Small Basic好,原因可能是C的Win32程序速度比Small Basic快太多了,以至于几乎看不到闪烁,于是,按上面这样将闪烁实际的用Sleep停留一下才能看到较好的效果。也算是不同语言实现同一个算法(大概这么称呼实现这个效果的方法吧)需要注意的不同之处。
4. 屏幕刮花效果
///////////////////////////////////////////////////////////
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
HDC hdc;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
hdc = GetDC(main_window_handle);
for (int index=0; index < POSITION_COUNT; index++)
{
SetPixel(hdc, gaWidth[index], gaHeight[index], RGB(255,255,255));
// 这算是实现的一种,横向刮花
gaWidth[index] += 1;
// 这算是实现的另一种,斜向刮花
//gaWidth[index] += 1;
//gaHeight[index] += 1;
// 这算是实现的又一种,纵向刮花,纵向刮花建议配合血红色颜色观看效果。。。。。。。
//gaHeight[index] += 1;
} // end for index
// release the dc
ReleaseDC(main_window_handle, hdc);
while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
{
Sleep(1);
}
// return success or failure or your own return code here
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
// 一次初始化一个随机POSITION_COUNT大小的数组用于固定位置
for (int index=0; index < POSITION_COUNT; index++)
{
// get random position
gaWidth[index] = rand()%WIDTH;
gaHeight[index] = rand()%HEIGHT;
} // end for index
// return success or failure or your own return code here
return(1);
} // end Game_Init
一个屏幕刮花效果,不同的方向给人感受完全不同,最有意思的是纵向向下的红色效果,如插图1.让我想起某游戏死亡时的结束画面,满眼都是向下流动的血液。。。。。。。。
5. 移动的星空:
本来用我在Small Basic中的方法实现也可以出现移动的星空的,但是我发现《Tricks of the Windows Game Programming GURUS》一书中的例子更加有趣,这里引用一下了。
///////////////////////////////////////////////////////////
void Init_Stars(void)
{
// this function initializes all the stars
for (int index=0; index < NUM_STARS; index++)
{
// select random position
stars[index].x = rand()%WINDOW_WIDTH;
stars[index].y = rand()%WINDOW_HEIGHT;
// set random velocity
stars[index].vel = 1 + rand()%16;
// set intensity which is inversely prop to velocity for 3D effect
// note, I am mixing equal amounts of RGB to make black -> bright white
int intensity = 15*(17 - stars[index].vel);
stars[index].col = RGB(intensity, intensity, intensity);
} // end for index
} // end Init_Stars
////////////////////////////////////////////////////////////
void Erase_Stars(void)
{
// this function erases all the stars
for (int index=0; index < NUM_STARS; index++)
SetPixel(global_dc, stars[index].x, stars[index].y, RGB(0,0,0));
} // end Erase_Stars
////////////////////////////////////////////////////////////
void Draw_Stars()
{
// this function draws all the stars
for (int index=0; index < NUM_STARS; index++)
SetPixel(global_dc, stars[index].x, stars[index].y, stars[index].col);
} // end Draw_Stars
////////////////////////////////////////////////////////////
void Move_Stars(void)
{
// this function moves all the stars and wraps them around the
// screen boundaries
for (int index=0; index < NUM_STARS; index++)
{
// move the star and test for edge
stars[index].x+=stars[index].vel;
if (stars[index].x >= WINDOW_WIDTH)
stars[index].x -= WINDOW_WIDTH;
} // end for index
} // end Move_Stars
////////////////////////////////////////////////////////////
int Game_Main(void *parms = NULL, int num_parms = 0)
{
// this is the main loop of the game, do all your processing
// here
// get the time
DWORD start_time = GetTickCount();
// erase the stars
Erase_Stars();
// move the stars
Move_Stars();
// draw the stars
Draw_Stars();
// lock to 30 fps
while((start_time \- GetTickCount() < 33));
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
// return success or failure or your own return code here
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
// first get the dc to the window
global_dc = GetDC(main_window_handle);
// initialize the star field here
Init_Stars();
// return success or failure or your own return code here
return(1);
} // end Game_Init
/////////////////////////////////////////////////////////////
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all you cleanup and shutdown here
// release the global dc
ReleaseDC(main_window_handle, global_dc);
// return success or failure or your own return code here
return(1);
} // end Game_Shutdown
书中例子利用不同的亮度,及不同的移动速度来模拟一种类3D的效果。。。。。。。。。。。。估计不是一般的人能想到的,呵呵,要知道,仅仅利用了一个SetPixel函数。。。。。。。
Have Fun,aha?在有了一个大概的思路以后,其实Window下面的GDI编程也没有那么难吧,的确是没有那么难吧?毕竟我们专注的也就一个SetPixel函数而已 ,越专注反而能让我们有更多新的想法。
这里用Windows GDI来实现我从Small Basic学到的一招显示文字的华丽技巧,你以前要是从来没有看过类似例子(也不是个在图形编程领域混过很多年的骨灰级程序员)你别说看了没有感觉惊艳,要知道其实仅仅是利用了画点和文字输出两个如此平常而简单的特性。
6. 星空中的文字
///////////////////////////////////////////////////////////
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
for (int index=0; index < 1000; index++)
{
// get random position
int x = rand()%WIDTH;
int y = rand()%HEIGHT;
COLORREF color = RGB(rand()%255,rand()%255,rand()%255);
SetPixel(ghDC, x,y, color);
} // end for index
TextOut(ghDC, 100, 100, CHAR_OUT, _tcslen(CHAR_OUT));
// release the dc
while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
{
Sleep(1);
}
// return success or failure or your own return code here
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
HFONT hfont;
ghDC = GetDC(main_window_handle);
hfont = CreateFont( 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("新宋体"));
SelectObject(ghDC, hfont);
SetTextColor(ghDC, RGB(0,0,0));
SetBkColor(ghDC, RGB(0,0,0));
DeleteObject(hfont);
// return success or failure or your own return code here
return(1);
} // end Game_Init
/////////////////////////////////////////////////////////////
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all you cleanup and shutdown here
ReleaseDC(main_window_handle, ghDC);
// return success or failure or your own return code here
return(1);
} // end Game_Shutdown
按照以前的实现思想,我很快写出了上述程序,结果效果和我想象的不太一样,反而有点像个面具。。。。。留出了眼睛的观察窗口。。。-_-!见插图2。实际的原因想了一下才知道,背景前景都是黑的,当时的第一想法是不能让星空给盖了,但是这样不都黑了啊?。。。。呵呵,改成下面这个样子,效果就出来了,多的行用红色标明,看了就明白了,让背景透明,能够被星空给盖了,这样才能显示出文字。
///////////////////////////////////////////////////////////
int Game_Main(void *parms = NULL, int num_parms = 0)
{
DWORD dwStartTime;
dwStartTime = GetTickCount();
// this is the main loop of the game, do all your processing
// here
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
for (int index=0; index < 300; index++)
{
// get random position
int x = rand()%WIDTH;
int y = rand()%HEIGHT;
COLORREF color = RGB(rand()%255,rand()%255,rand()%255);
SetPixel(ghDC, x,y, color);
} // end for index
TextOut(ghDC, 100, 100, CHAR_OUT, _tcslen(CHAR_OUT));
// release the dc
while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
{
Sleep(1);
}
// return success or failure or your own return code here
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here
HFONT hfont;
ghDC = GetDC(main_window_handle);
hfont = CreateFont( 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("新宋体"));
SelectObject(ghDC, hfont);
SetTextColor(ghDC, RGB(0,0,0));
SetBkMode (ghDC, TRANSPARENT);
DeleteObject(hfont);
// return success or failure or your own return code here
return(1);
} // end Game_Init
/////////////////////////////////////////////////////////////
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
// this is called after the game is exited and the main event
// loop while is exited, do all you cleanup and shutdown here
ReleaseDC(main_window_handle, ghDC);
// return success or failure or your own return code here
return(1);
} // end Game_Shutdown
我第一次看到这个例子的时候真的感叹作者是个天才-_-!也许是自己太笨了所以想不到用这样的方式去显示文字吧。这次来个系列效果,可以参考《简单图形编程的学习(2)-–点 (small basic实现)》文中的插图效果,或者简单的自己运行一下就好了。
三、 小结
一个个简单的点就能够构成如此繁多的效果,简直有点不可思议,但是其实,能够绘制一个点,就能够绘制整个世界,要知道,整个屏幕不过也就是一个一个像素构成的,呵呵。其实,从另外的角度来说,一连串连续的点就能构成一条直线,一排排直线就能构成一个面,有了点,线,面,还有什么不够构成的?你可以表达整个世界。另外,就我看的老的游戏编程书籍介绍,DirectDraw的原始接口也仅仅是能画点/位图而已,游戏的开发的先驱们还不是用这样简单的接口实现了那么多画面丰富,效果华丽的2D游戏啊?
插图
插图一:

插图2:

阅读全文....
简单图形编程的学习( 2)—点 (small basic实现)
一、 又一顿牢骚
虽然知道不应该老是说些与技术无关的话。。。。但是有的时候又总是想说。。。。难怪有同事说我最近已经有点像唐僧了-_-!总而言之,因为相对来说书看的太快,(现在租的房子离公司太远,老是坐地铁,导致有了非常固定的看书时间),而因为工作一直太忙,一直加班回家太晚的原因,所以实际的实践太慢(基本上现在就是以写博客的形式),所以Qt与Android的部分要是同步跟上我DirectX的书都要看好几本了,这样的方式好像不太好,所以目前暂时还是以Windows的为主了。。。。这也体现了一点理想与现实的差距-_-!虽然初期项目目标过大,项目中即时调整起码还能保证项目完成吧。。。。。(扯的远了)。
二、 画点
画点,在Small Basic中属于太基础的东西,当然,其实在Small Basic中什么都基础。。。呵呵
GraphicsWindow.SetPixel
函数用于画点,这里的点仅仅只有一个像素,所以叫SetPixel(设置像素),和Windows GDI的命名一致(其实Small Basic中的画图函数很多都与Windows GDI一致),参数的解释如下:
SetPixel
Draws the pixel specified by the x and y co-ordinates using the specified color.
GraphicsWindow.SetPixel(x, y, color)
x
The x co-ordinate of the pixel.
y
The y co-ordinate of the pixel.
color
The color of the pixel to set.
Returns
Nothing
1. 随机在屏幕上画随机颜色点(HHT897)
GraphicsWindow.BackgroundColor = "White"
GraphicsWindow.PenColor = "LightBlue"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
While ("True")
GraphicsWindow.SetPixel(Math.GetRandomNumber(gw), Math.GetRandomNumber(gh), GraphicsWindow.GetRandomColor())
EndWhile
So简单。。。不是吗?效果如插图1
但是,不要小看点的作用,点可以用于模拟星空。。。。。。。。。这里展示几个效果,真的觉得small basic用于演示什么叫简单的技术惊艳的效果非常合适。。。。。。在图形领域,感觉技术固然重要,但是思维强大也能利用简单的技术实现惊艳的效果。
2. 老电视机雪花点的效果:(PXB396)
GraphicsWindow.BackgroundColor = "DarkNight"
GraphicsWindow.PenColor = "LightBlue"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
While ("True")
For i = 1 To 1000
GraphicsWindow.SetPixel(Math.GetRandomNumber(gw), Math.GetRandomNumber(gh), "White")
EndFor
Program.Delay(10)
GraphicsWindow.Clear()
EndWhile
3. 闪烁的星空:(TPK996)
GraphicsWindow.BackgroundColor = "DarkNight"
GraphicsWindow.PenColor = "LightBlue"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
' 以数组记录下随机出来的点,这样才能保证星空是在闪烁而不是移动
For i = 1 To 500
width[i] = Math.GetRandomNumber(gw)
height[i] = Math.GetRandomNumber(gh)
EndFor
While ("True")
For i = 1 To 500
GraphicsWindow.SetPixel(width[i], height[i], "White")
EndFor
Program.Delay(1000)
GraphicsWindow.Clear()
EndWhile
4. 屏幕刮花效果(RMP025)
GraphicsWindow.BackgroundColor = "DarkNight"
GraphicsWindow.PenColor = "LightBlue"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
For i = 1 To 500
width[i] = Math.GetRandomNumber(gw)
height[i] = Math.GetRandomNumber(gh)
EndFor
While ("True")
For i = 1 To 500
GraphicsWindow.SetPixel(width[i], height[i], "White")
width[i] = width[i] + 1
EndFor
Program.Delay(10)
EndWhile
5. 移动的星空:(ZGB224)
GraphicsWindow.BackgroundColor = "DarkNight"
GraphicsWindow.PenColor = "LightBlue"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
For i = 1 To 50
width[i] = Math.GetRandomNumber(gw)
height[i] = Math.GetRandomNumber(gh)
EndFor
While ("True")
Program.Delay(1)
For i = 1 To 50
GraphicsWindow.SetPixel(width[i], height[i], "White")
width[i] = width[i] + 1
' 保证星空不是直接消失了-_-!
If(width[i] > gw) Then
width[i] = 0
EndIf
EndFor
GraphicsWindow.Clear()
EndWhile
Have Fun,aha?呵呵,的确是,很久没有这样爽的写程序了,有了思维,很简单的就能体现在Small Basic上,让人愉快。后面的字母都是可以直接在Small Basic中import的,现在Small Basic 0.6出来了,我用的都是Small Basic 0.6。另外,发现没有,不像在讲其他语言/程序的时候一样,对各个参数一通饱讲,10分钟还没有看到一个函数的参数,对于Small Basic的程序我感觉仅仅需要展示效果和源代码就好了,展示的直接就是编程的思想,而不是语言,因为语言本身如此的简单。完成上面所有的示例都没有花掉我一个小时。。。。。很难想象用GDI或者DX我要用多久。。。。。。。呵呵,虽然我用C/C++出身的(现在也靠这个吃饭),也稍微学习过一下汇编,但是我怎么感觉我对越简单的语言越有好感啊?LUA, Python, Bash ,JAVA都稍微学过一点,但是实在是没有如Small Basic这样让人愉快的语言了^^,也许最最重要的一点在于,现有的大部分语言(上面提及的都是),逻辑表达能力虽然很强,库很丰富,但是为了适应足够广阔的领域并达到工业强度,GUI编程方面都是复杂的让人吐血,MFC就不说了,TK号称简单,其实我感觉也好不到哪去,我没有尝试过用Bash没写GUI,Qt已经算是非常好的GUI库了,但是上百个类足够让你头晕目眩。Small Basic这样的语言虽然是玩具,也就因为其是玩具才敢这么简单。。。。。。。。。呵呵,欣赏它,起码作为一种简单的演示也不错。
上面的例子都是自己随便想的,下面看一个偷师来的例子,以前在讲small Basic的时候其实已经展示过了,但是因为这个例子给了我太多惊喜,我决定反复提起,告诉你们什么叫编程思维利用简单的技术,你别说看了没有感觉惊艳,要知道其实仅仅是利用了画点和文字输出两个如此平常而简单的特性。
6. 星空中的文字(HQG707)
GraphicsWindow.BackgroundColor = "midnight"
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
GraphicsWindow.FontSize = 100
Turtle.Move (100)
Turtle.Turn (1*1)
While ("True")
For i = 1 To 50
GraphicsWindow.SetPixel(Math.GetRandomNumber(gw),Math.GetRandomNumber(gh),GraphicsWindow.GetRandomColor())
EndFor
Turtle.Move(1)
GraphicsWindow.BrushColor = "Black"
GraphicsWindow.DrawBoundText(30,110,gw-20,"Small Basic")
EndWhile
第一次看到这个例子的时候我真的感叹作者是个天才-_-!也许是自己太笨了所以想不到用这样的方式去显示文字吧。这次来个系列效果,如星空中的文字-插图1-4。怎么样?效果惊艳吧?呵呵,直接运行一下程序吧,将文字改成你想要的,你会有更好的感觉。
三、 小结
一个个简单的点就能够构成如此繁多的效果,简直有点不可思议,但是其实,能够绘制一个点,就能够绘制整个世界,要知道,整个屏幕不过也就是一个一个像素构成的,呵呵。其实,从另外的角度来说,一连串连续的点就能构成一条直线,一排排直线就能构成一个面,有了点,线,面,还有什么不够构成的?你可以表达整个世界。
插图1:

插图2:

星空中的文字-插图1:

星空中的文字-插图2:

星空中的文字-插图3:

星空中的文字-插图4:

阅读全文....
Small Basic V0.6版本发布,新增了图形缩放、三角函数、文字输入等实用功能,并修复了多项bug,继续让编程充满乐趣。
阅读全文....
简单图形编程的学习(1)—文字 (Windows GDI实现)
一、全部简单图形编程的学习说在前面的话
此系列文章均假设读者已经具备一定的对应的程序编写知识,无论是最简单的small basic,还是因为常用而被人熟知的Windows GDI,或者是Linux下用的更多的Qt(一般我用PyQt),甚至是现在国内知道的人并不多的Android,我都不准备讲太多基础的语法,或者与平台相关的太多背景知识,这些靠读者先行学习,我仅仅准备在自己学习的过程中找点乐子:)看看我用一些简单的接口都能想出干什么事情,然后展示给大家看看,图形程序实在是最好展示的一类程序了,不像其他程序一样,哪怕我讲了一堆的boost,真正见识到boost强大的又有几个呢?-_-!要知道,今天起,所有程序都是窗口程序,不再是命令行!!!!人类用了多久才走到这一步我不知道。。。。我用了25年…….(从我出生算起)或者1年(从工作开始)
另外,想要看怎么编写窗口应用程序的就不要走错地方了,这里不是想怎么描述怎么使用一个又一个的控件,这里都是讲绘图的-_-!
二、谈谈Windows GDI
由于今天是第一篇,所以谈谈Windows GDI
虽然因为兴趣和工作需要,对Linux也有所了解,但是Windows到目前为止绝对是本人最熟悉的平台。。。也许也是绝大部分程序员最熟悉的平台吧,但是GDI用的实在是并不多。。。。也许又要说了。。我本质上是个服务器端的程序员-_-!呵呵,服务器端程序员每天面对的只能是控制台,与图形化界面无关,更加与GDI无关。。。。呵呵,但是假如要做客户端的话,是有图形界面了,但是其实游戏还是不需要用到GDI的。。。Windows下不都是用DirectX嘛。但是我对GDI还是比较有兴趣,主要来源于一个资深同事描述用Windows GDI去描述IM软件界面的往事(公司以前是做IM软件的),呵呵,我听着都觉得出神入化。自己好歹也了解一下,虽然说其实用到的机会不多。
文字好像都不像是图形编程中应该学习的东西,但是别忘了,文字可都是由象形文字发展过来的。。。中文至今还是象形文字呢,文字在远古的时代可本来就是图形啊,为啥学习图形编程的时候不要学习怎么显示文字啊?呵呵,前面的都是废话,其实你编点啥程序都会碰到需要在图形中显示文字的情况,所以我们先来看看文字的显示。另外,其实在显示文字的时候,假如需要对文字的显示进行设置,也能学到很多普通图形的设置方式,这点以后就能看到。
三、Windows GDI的文字显示
事实上因为一个完整的Windows程序已经较为复杂,所以以后的程序得有个模板可以套才行,不然老是纠缠在窗口注册,创建和消息循环上了,那样效率太低。这里就用VS2005创建Win32程序本身的那一套了,不用MFC是不想拦住不了解也不像熟悉MFC的兄弟,也许另外弄个MFC+GDI+ (注:切分格式是(MFC+(GDI+))-_-!)篇吧。
基本程序如下:
// Win32GraphicEx1.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "Win32GraphicEx1.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WIN32GRAPHICEX1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32GRAPHICEX1));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
// 注释:
//
// 仅当希望
// 此代码与添加到Windows 95 中的"RegisterClassEx"
// 函数之前的Win32 系统兼容时,才需要此函数及其用法。调用此函数十分重要,
// 这样应用程序就可以获得关联的
// "格式正确的"小图标。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32GRAPHICEX1));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32GRAPHICEX1);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// "关于"框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
以后讲解的时候可能就不列出完整的源代码了,因为用C++编写Win32程序,可能动辄几页的代码,完整列出影响阅读,为了完整,就如前面所言,我已经假定读者都具有相关领域一定的编程经验,将描述的代码放进恰当的位置应该不成问题。
文字的显示是个复杂的问题,(自有历史以来就复杂)不仅仅是调用几个类似于TextOut,DrawText ,DrawTextEx API那么简单,看过《Windows 图形编程》(参考1)的人应该会有同感,此书是我见过关于Windows下字体,文字显示最深入的一本书,(其实是其他的专著太深看不懂也没有看)。
至于Windows下普通的文字显示API,《Programming Windows》一书按照惯例还是最好的一本。
Windows下最常用的文字输出API就是前面提到的3个,见下面的例子:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TextOut
TextOut(hdc, 0, 0, szHelloWorld, (int)_tcslen(szHelloWorld));
// DrawText
RECT rtText = {0, 50, 100, 100};
DrawText(hdc, szHelloWorld, -1, &rtText, DT_LEFT);
// DrawTextEx
rtText.top += 50;
rtText.bottom += 50;
DrawTextEx(hdc, szHelloWorld, -1, &rtText, DT_LEFT, NULL);
EndPaint(hWnd, &ps);
}
显示效果如插图1。
其实还有几个使用复杂度稍微高一点,但是控制力更强一些的文本输出函数,比如TabbedTextOut,ExtTextOut。(八卦一下:ExtTextOut是少有的将Ext放在函数名前表示扩展而不是按照微软惯例将Ex表示扩展放在函数名后的函数,估计是设计此函数的人刚到微软工作-_-!)
四、字体
其实字体是个更加复杂的问题。。。。有多少人知道在字体的现实问题上MS,Apple,Adobe的研究人员投入了多少精力啊。。。。今天的TrueType可不是一开始就存在的。。。。
当然,其实今天我们已经没有必要再去重复研究了,Windows下用MS提供的API就好了,一般有两个接口:
HFONT CreateFont(
int nHeight, // height of font
int nWidth, // average character width
int nEscapement, // angle of escapement
int nOrientation, // base-line orientation angle
int fnWeight, // font weight
DWORD fdwItalic, // italic attribute option
DWORD fdwUnderline, // underline attribute option
DWORD fdwStrikeOut, // strikeout attribute option
DWORD fdwCharSet, // character set identifier
DWORD fdwOutputPrecision, // output precision
DWORD fdwClipPrecision, // clipping precision
DWORD fdwQuality, // output quality
DWORD fdwPitchAndFamily, // pitch and family
LPCTSTR lpszFace // typeface name
);
这个接口直接通过超多的参数创建字体,看看参数的数量。。。就知道我说过的字体显示是个复杂问题没有错了。。。。另外有个更加人性化的接口,那就是利用结构,本质上没有太大区别,只是从软件接口设计上来说,当参数超过6,7个的时候提供结构传递参数会更加人性化一点,CreateWindow之类的也就是遵循了这样的方式。这也是BS说C++中不提供关键词参数,关键词参数没有那么重要的主要理由之一。(不明白我说的是什么那就忽略此句。。。见D&E6.5)
所谓的用结构创建字体的接口如下:
HFONT CreateFontIndirect(
CONST LOGFONT* lplf // characteristics
);
LOGFONT结构就是前面一个直接创建的接口的参数的堆叠:
typedef struct tagLOGFONTW
{
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
WCHAR lfFaceName[LF_FACESIZE];
} LOGFONTW, *PLOGFONTW, NEAR *NPLOGFONTW, FAR *LPLOGFONTW;
这里列出来的是宽字节版本。参数如此之多,一方面体现了复杂度,一方面也是自由度,其实个人认为有点点设计的累赘了。。。。其实完全没有必要用一个一个整数来表示一个bool值的内容,MS习惯的位标志竟然在此处看不到痕迹。。。估计。。。写字体模块的哥们光研究怎么更好的显示字体了,没有关注接口的设计。。。。。或者,和写TabbedTextOut,ExtTextOut就是同一个人。。。。这个人很显然刚刚来微软。。。。参数的含义不一个一个解释了,看MSDN或者《Programming Windows》,见下面一个例子,来自于《Programming Windows》。
/*---------------------------------------
EZFONT.C -- Easy Font Creation
(c) Charles Petzold, 1998
\---------------------------------------*/
#include <windows.h>
#include <math.h>
#include "ezfont.h"
HFONT EzCreateFont (HDC hdc, TCHAR * szFaceName, int iDeciPtHeight,
int iDeciPtWidth, int iAttributes, BOOL fLogRes)
{
FLOAT cxDpi, cyDpi ;
HFONT hFont ;
LOGFONT lf ;
POINT pt ;
TEXTMETRIC tm ;
SaveDC (hdc) ;
SetGraphicsMode (hdc, GM_ADVANCED) ;
ModifyWorldTransform (hdc, NULL, MWT_IDENTITY) ;
SetViewportOrgEx (hdc, 0, 0, NULL) ;
SetWindowOrgEx (hdc, 0, 0, NULL) ;
if (fLogRes)
{
cxDpi = (FLOAT) GetDeviceCaps (hdc, LOGPIXELSX) ;
cyDpi = (FLOAT) GetDeviceCaps (hdc, LOGPIXELSY) ;
}
else
{
cxDpi = (FLOAT) (25.4 * GetDeviceCaps (hdc, HORZRES) /
GetDeviceCaps (hdc, HORZSIZE)) ;
cyDpi = (FLOAT) (25.4 * GetDeviceCaps (hdc, VERTRES) /
GetDeviceCaps (hdc, VERTSIZE)) ;
}
pt.x = (int) (iDeciPtWidth * cxDpi / 72) ;
pt.y = (int) (iDeciPtHeight * cyDpi / 72) ;
DPtoLP (hdc, &pt, 1) ;
lf.lfHeight = - (int) (fabs (pt.y) / 10.0 + 0.5) ;
lf.lfWidth = 0 ;
lf.lfEscapement = 0 ;
lf.lfOrientation = 0 ;
lf.lfWeight = iAttributes & EZ_ATTR_BOLD ? 700 : 0 ;
lf.lfItalic = iAttributes & EZ_ATTR_ITALIC ? 1 : 0 ;
lf.lfUnderline = iAttributes & EZ_ATTR_UNDERLINE ? 1 : 0 ;
lf.lfStrikeOut = iAttributes & EZ_ATTR_STRIKEOUT ? 1 : 0 ;
lf.lfCharSet = DEFAULT_CHARSET ;
lf.lfOutPrecision = 0 ;
lf.lfClipPrecision = 0 ;
lf.lfQuality = 0 ;
lf.lfPitchAndFamily = 0 ;
lstrcpy (lf.lfFaceName, szFaceName) ;
hFont = CreateFontIndirect (&lf) ;
if (iDeciPtWidth != 0)
{
hFont = (HFONT) SelectObject (hdc, hFont) ;
GetTextMetrics (hdc, &tm) ;
DeleteObject (SelectObject (hdc, hFont)) ;
lf.lfWidth = (int) (tm.tmAveCharWidth *
fabs (pt.x) / fabs (pt.y) + 0.5) ;
hFont = CreateFontIndirect (&lf) ;
}
RestoreDC (hdc, -1) ;
return hFont ;
}
/*----------------------------------------
FONTROT.C -- Rotated Fonts
(c) Charles Petzold, 1998
\----------------------------------------*/
#include <windows.h>
#include "ezfont.h"
TCHAR szAppName [] = TEXT ("FontRot") ;
TCHAR szTitle [] = TEXT ("FontRot: Rotated Fonts") ;
void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea)
{
static TCHAR szString [] = TEXT (" Rotation") ;
HFONT hFont ;
int i ;
LOGFONT lf ;
hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 540, 0, 0, TRUE) ;
GetObject (hFont, sizeof (LOGFONT), &lf) ;
DeleteObject (hFont) ;
SetBkMode (hdc, TRANSPARENT) ;
SetTextAlign (hdc, TA_BASELINE) ;
SetViewportOrgEx (hdc, cxArea / 2, cyArea / 2, NULL) ;
for (i = 0 ; i < 12 ; i ++)
{
lf.lfEscapement = lf.lfOrientation = i * 300 ;
SelectObject (hdc, CreateFontIndirect (&lf)) ;
TextOut (hdc, 0, 0, szString, lstrlen (szString)) ;
DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
}
}
/*------------------------------------------------
FONTDEMO.C -- Font Demonstration Shell Program
(c) Charles Petzold, 1998
\------------------------------------------------*/
#include <windows.h>
#include "EzFont.h"
#include "resource.h"
extern void PaintRoutine (HWND, HDC, int, int) ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HINSTANCE hInst ;
extern TCHAR szAppName [] ;
extern TCHAR szTitle [] ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
TCHAR szResource [] = TEXT ("FontDemo") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
hInst = hInstance ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szResource ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static DOCINFO di = { sizeof (DOCINFO), TEXT ("Font Demo: Printing") } ;
static int cxClient, cyClient ;
static PRINTDLG pd = { sizeof (PRINTDLG) } ;
BOOL fSuccess ;
HDC hdc, hdcPrn ;
int cxPage, cyPage ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_PRINT:
// Get printer DC
pd.hwndOwner = hwnd ;
pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;
if (!PrintDlg (&pd))
return 0 ;
if (NULL == (hdcPrn = pd.hDC))
{
MessageBox (hwnd, TEXT ("Cannot obtain Printer DC"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
}
// Get size of printable area of page
cxPage = GetDeviceCaps (hdcPrn, HORZRES) ;
cyPage = GetDeviceCaps (hdcPrn, VERTRES) ;
fSuccess = FALSE ;
// Do the printer page
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0))
{
PaintRoutine (hwnd, hdcPrn, cxPage, cyPage) ;
if (EndPage (hdcPrn) > 0)
{
fSuccess = TRUE ;
EndDoc (hdcPrn) ;
}
}
DeleteDC (hdcPrn) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
if (!fSuccess)
MessageBox (hwnd,
TEXT ("Error encountered during printing"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
case IDM_ABOUT:
MessageBox (hwnd, TEXT ("Font Demonstration Program/n")
TEXT ("(c) Charles Petzold, 1998"),
szAppName, MB_ICONINFORMATION | MB_OK);
return 0 ;
}
break ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
PaintRoutine (hwnd, hdc, cxClient, cyClient) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
显示效果如插图2,一个显示成一圈的Rotation,用最最简单的手段实现绚烂的效果,以Small Basic中的一个星空显示文字的程序为最,这也算是比较突出的例子了。。。。这里也不能老是抄袭Petzold。。。。。。。我将其转起来。实现旋转的动画:)
/*------------------------------------------------
FONTDEMO.C -- Font Demonstration Shell Program
(c) 改自Charles Petzold, 1998,因为不知道其原来是啥版权,这里也不声明自己的版权了
\------------------------------------------------*/
#include <windows.h>
#include "EzFont.h"
#include "resource.h"
extern void PaintRoutine (HWND, HDC, int, int) ;
extern void PaintAnimateOrientation(HWND hwnd, HDC hdc, int cxArea, int cyArea);
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HINSTANCE hInst ;
extern TCHAR szAppName [] ;
extern TCHAR szTitle [] ;
#define WND_WIDTH (800)
#define WND_HEIGHT (800)
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
TCHAR szResource [] = TEXT ("FontDemo") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
HDC hdc ;
hInst = hInstance ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szResource ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
WND_WIDTH, WND_HEIGHT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (TRUE)
{
// 就像游戏中一贯的做法
if(PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
{
break;
}
TranslateMessage (&msg);
DispatchMessage (&msg);
}
hdc = GetDC(hwnd);
PaintAnimateOrientation(hwnd, hdc, WND_WIDTH, WND_HEIGHT);
ReleaseDC(hwnd, hdc);
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static DOCINFO di = { sizeof (DOCINFO), TEXT ("Font Demo: Printing") } ;
static int cxClient, cyClient ;
static PRINTDLG pd = { sizeof (PRINTDLG) } ;
BOOL fSuccess ;
HDC hdc, hdcPrn ;
int cxPage, cyPage ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_PRINT:
// Get printer DC
pd.hwndOwner = hwnd ;
pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;
if (!PrintDlg (&pd))
return 0 ;
if (NULL == (hdcPrn = pd.hDC))
{
MessageBox (hwnd, TEXT ("Cannot obtain Printer DC"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
}
// Get size of printable area of page
cxPage = GetDeviceCaps (hdcPrn, HORZRES) ;
cyPage = GetDeviceCaps (hdcPrn, VERTRES) ;
fSuccess = FALSE ;
// Do the printer page
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0))
{
PaintRoutine (hwnd, hdcPrn, cxPage, cyPage) ;
if (EndPage (hdcPrn) > 0)
{
fSuccess = TRUE ;
EndDoc (hdcPrn) ;
}
}
DeleteDC (hdcPrn) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
if (!fSuccess)
MessageBox (hwnd,
TEXT ("Error encountered during printing"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
case IDM_ABOUT:
MessageBox (hwnd, TEXT ("Font Demonstration Program/n")
TEXT ("(c) Charles Petzold, 1998"),
szAppName, MB_ICONINFORMATION | MB_OK);
return 0 ;
}
break ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
/*----------------------------------------
FONTROT.C -- Rotated Fonts
(c) 改自Charles Petzold, 1998
\----------------------------------------*/
#include <windows.h>
#include <tchar.h>
#include "ezfont.h"
TCHAR szAppName [] = TEXT ("FontRot") ;
TCHAR szTitle [] = TEXT ("FontRot: Rotated Fonts") ;
#define FRAME_PER_SECOND (20)
#define TIME_IN_FRAME (1000/FRAME_PER_SECOND)
#define WHITE_COLOR (RGB(255,255,255))
#define BLACK_COLOR (RGB(0,0,0))
void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea, int iAnimateOrientation)
{
static TCHAR szString [] = TEXT (" Rotation") ;
HFONT hFont ;
int i ;
LOGFONT lf ;
hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 540, 0, 0, TRUE) ;
GetObject (hFont, sizeof (LOGFONT), &lf) ;
DeleteObject (hFont) ;
SetBkMode (hdc, TRANSPARENT) ;
SetTextAlign (hdc, TA_BASELINE) ;
SetViewportOrgEx (hdc, cxArea / 2, cyArea / 2, NULL) ;
SetTextColor(hdc, WHITE_COLOR);
for (i = 0 ; i < 12 ; i++)
{
lf.lfEscapement = lf.lfOrientation = i * 300 + iAnimateOrientation-1;
SelectObject (hdc, CreateFontIndirect (&lf)) ;
TextOut (hdc, 0, 0, szString, lstrlen (szString)) ;
DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
}
SetTextColor(hdc, BLACK_COLOR);
for (i = 0 ; i < 12 ; i++)
{
lf.lfEscapement = lf.lfOrientation = i * 300 + iAnimateOrientation;
SelectObject (hdc, CreateFontIndirect (&lf)) ;
TextOut (hdc, 0, 0, szString, lstrlen (szString)) ;
DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
}
}
// By 九天雁翎:
// 主要的新添函数
void PaintAnimateOrientation(HWND hwnd, HDC hdc, int cxArea, int cyArea)
{
DWORD dwStartTime;
DWORD dwEndTime;
DWORD dwInTime;
int iIntervalTimeNeed ;
static int iAnimateOrientation = 0;
dwStartTime = GetTickCount();
iAnimateOrientation += 1;
PaintRoutine(hwnd, hdc, cxArea, cyArea, iAnimateOrientation);
// 帧数控制
while(GetTickCount() - dwStartTime < TIME_IN_FRAME)
{
Sleep(1);
}
}
其实目前的源代码中有个瑕疵,因为是用透明文字背景画图,用白色的文字覆盖原有文字的时候会出现一些覆盖不了的情况,这个情况导致文字转动后会留有一些背景的尾巴。。。。效果如插图3了,另外。。。之所以使用此种循环画图方式纯粹因为最近看了《Window游戏编程大师级技巧》一书,其实用纯timer的方式实现如此简单的动画应该也是可行的,并且因为可以依赖消息机制的背景刷新,就不会有此问题。见下面的改良版旋转文字动画:
将SetBkMode (hdc, TRANSPARENT) ;一句改为SetBkMode (hdc, OPAQUE) ;倒是可以解决这个问题,但是因为背景的不透明,中间R重合的部分会导致互相的遮掩,这点没有深入研究了,希望有人能给出更好的解决方案。另外,因为是使用GDI,所以在每秒20帧重画的时候都会有闪烁。。。。。
改良版旋转文字动画:
/*------------------------------------------------
FONTDEMO.C -- Font Demonstration Shell Program
(c) 改自Charles Petzold, 1998
\------------------------------------------------*/
#include <windows.h>
#include "EzFont.h"
#include "resource.h"
void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea, int iAnimateOrientation);
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HINSTANCE hInst ;
extern TCHAR szAppName [] ;
extern TCHAR szTitle [] ;
#define WND_WIDTH (800)
#define WND_HEIGHT (800)
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
TCHAR szResource [] = TEXT ("FontDemo") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
HDC hdc ;
hInst = hInstance ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szResource ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
WND_WIDTH, WND_HEIGHT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
SetTimer(hwnd, 1, 50, NULL);
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static DOCINFO di = { sizeof (DOCINFO), TEXT ("Font Demo: Printing") } ;
static int cxClient, cyClient ;
static PRINTDLG pd = { sizeof (PRINTDLG) } ;
BOOL fSuccess ;
HDC hdc, hdcPrn ;
int cxPage, cyPage ;
PAINTSTRUCT ps ;
static int iAnimateOrientation = 0;
switch (message)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_PRINT:
// Get printer DC
pd.hwndOwner = hwnd ;
pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;
if (!PrintDlg (&pd))
return 0 ;
if (NULL == (hdcPrn = pd.hDC))
{
MessageBox (hwnd, TEXT ("Cannot obtain Printer DC"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
}
// Get size of printable area of page
cxPage = GetDeviceCaps (hdcPrn, HORZRES) ;
cyPage = GetDeviceCaps (hdcPrn, VERTRES) ;
fSuccess = FALSE ;
// Do the printer page
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0))
{
PaintRoutine (hwnd, hdcPrn, cxPage, cyPage, iAnimateOrientation) ;
if (EndPage (hdcPrn) > 0)
{
fSuccess = TRUE ;
EndDoc (hdcPrn) ;
}
}
DeleteDC (hdcPrn) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
if (!fSuccess)
MessageBox (hwnd,
TEXT ("Error encountered during printing"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
case IDM_ABOUT:
MessageBox (hwnd, TEXT ("Font Demonstration Program/n")
TEXT ("(c) Charles Petzold, 1998"),
szAppName, MB_ICONINFORMATION | MB_OK);
return 0 ;
}
break ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
PaintRoutine(hwnd, hdc, WND_WIDTH, WND_HEIGHT, iAnimateOrientation);
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_TIMER:
{
iAnimateOrientation += 1;
InvalidateRect(hwnd, NULL, TRUE);
return 0;
}
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
#define BLACK_COLOR (RGB(0,0,0))
TCHAR szAppName [] = TEXT ("FontRot") ;
TCHAR szTitle [] = TEXT ("FontRot: Rotated Fonts") ;
void PaintRoutine (HWND hwnd, HDC hdc, int cxArea, int cyArea, int iAnimateOrientation)
{
static TCHAR szString [] = TEXT (" Rotation") ;
HFONT hFont ;
int i ;
LOGFONT lf ;
hFont = EzCreateFont (hdc, TEXT ("Times New Roman"), 540, 0, 0, TRUE) ;
GetObject (hFont, sizeof (LOGFONT), &lf) ;
DeleteObject (hFont) ;
SetBkMode (hdc, TRANSPARENT) ;
SetTextAlign (hdc, TA_BASELINE) ;
SetViewportOrgEx (hdc, cxArea / 2, cyArea / 2, NULL) ;
SetTextColor(hdc, BLACK_COLOR);
for (i = 0 ; i < 12 ; i++)
{
lf.lfEscapement = lf.lfOrientation = i * 300 + iAnimateOrientation;
SelectObject (hdc, CreateFontIndirect (&lf)) ;
TextOut (hdc, 0, 0, szString, lstrlen (szString)) ;
DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
}
}
注意几个改动的地方,其实这个版本应该是最先的思路。。。。。。及利用Timer及Windows消息机制来完成动画绘制,这样可以利用背景擦除消息来擦除背景,不需要重新自己用白色文字覆盖上一帧的文字,也不会有拖尾现象,但是,因为总是重复擦除然后重绘,所以会有闪烁。
另外,从理论上来说,直接将绘制放在擦除背景消息中,如下:
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_ERASEBKGND:
hdc = GetDC(hwnd);
Rectangle(hdc, 0, 0, WND_WIDTH, WND_HEIGHT);
PaintRoutine(hwnd, hdc, WND_WIDTH, WND_HEIGHT, iAnimateOrientation);
ReleaseDC(hwnd, hdc);
return 0;
闪烁会小一点,因为擦除背景到重新绘制的间隔更短了(放在WM_PAINT中还有个重新分配消息的过程),但是事实上看不怎么出来。
随机文字的那个例子比较简单,这里不再实现了。
五、参考:
- 《Windows 图形编程》第14,15章(原版名《Windows Graphics Programming Win32 GDI and DirectDraw》,Feng Yuan著,机械工业出版社
- 《Programming Windows》 Fifth Edition,Chapter4,17 Charles Petzold著,Microsoft Press
插图1:


插图3:

阅读全文....
博主分享Ubuntu技巧:安装多媒体编解码器和C语言手册,解决了视频播放和查阅函数文档的难题。
阅读全文....
本文解决了Ubuntu 9.04下笔记本触摸板易误触的问题,提供了使用rmmod和modprobe命令禁用和启用触摸板的简单方法。
阅读全文....
Ubuntu服务开机自启方法:将启动脚本放/etc/init.d/,在/etc/rc2.d/创建软链接即可。也可用sysvconfig工具设置。
阅读全文....
本文是一份Ubuntu服务器SSH安装配置指南,详细介绍了从网络设置、安装openssh-server,到配置证书认证以实现安全便捷远程登录的全过程。
阅读全文....
去这个地址
http://java.sun.com/javase/downloads/
下载 jdk-6-doc.zip(英文) 或者 jdk-6-doc-ja.zip(日文) 包(二者选一即可)
sudo chown root:root jdk-6-doc.zip 改变文件所有者为root
cp jdk-6-doc.zip /tmp 下 然后再执行安装程序
/tmp 目录下面的东西会在系统关闭的时候被删除,所以如果想保存jdk-6-doc.zip请把它备份到其他位置
从http://forum.ubuntu.org.cn转载过来的
阅读全文....
本文提供了在Ubuntu中配置Samba共享可读写文件夹的详细教程。主要步骤包括安装Samba、修改smb.conf配置文件、添加系统与Samba用户,并设置正确的文件夹权限以实现网络共享。
阅读全文....