在游戏编程中,位图的处理是一个重要的环节,我将结合这几天我们小组制作游戏的具体情况,简单介绍一下我们小组对位图的处理方法。
首先我们采用的当然是双缓存策略,就是在缓存里面画好图后在用bitblt映射到client上,我们用的图片是bmp文件,采用的是设备无关位图,所以首先创建一个class DIBSection,用来操纵DIB,这里我们参考的是坂本千寻所写的一个类库。
我们定义一个位图头文件结构
struct {
BITMAPINFOHEADER Info;
DWORD BitField[3];
} Header; //位图头文件结构
然后进行DIBSection的创建,使用函数如下:
BOOL CDibSection::Create(int width, int height, int depth)
{
bytes_per_line = ScanBytes(width, depth);
bytes_per_pixel = PixelBytes(depth);
Header.Info.biSize = sizeof(BITMAPINFOHEADER);
Header.Info.biWidth = width;
Header.Info.biHeight = height;
Header.Info.biBitCount = depth;
Header.Info.biPlanes = 1;
Header.Info.biXPelsPerMeter = 0;
Header.Info.biYPelsPerMeter = 0;
Header.Info.biClrUsed = 0;
Header.Info.biClrImportant = 0;
Header.Info.biCompression = depth == 24? BI_RGB: BI_BITFIELDS;
Header.Info.biSizeImage = bytes_per_line * height;
switch (depth) {
case 16:
Header.BitField[0] = 0x7c00;
Header.BitField[1] = 0x03e0;
Header.BitField[2] = 0x001f;
break;
case 32:
Header.BitField[0] = 0xff0000;
Header.BitField[1] = 0x00ff00;
Header.BitField[2] = 0x0000ff;
break;
default:
Header.BitField[0] = 0;
Header.BitField[1] = 0;
Header.BitField[2] = 0;
break;
}
HDC dc = ::GetDC(0);
hBitmap = CreateDIBSection(dc, (BITMAPINFO *)&Header, DIB_RGB_COLORS, &Bits, NULL, 0);
::ReleaseDC(0, dc);
return hBitmap != 0;
}
在这个函数中,其实就是调用了CreateDIBSection的API来创建了一个DIBSection,该函数需要有一个BITMAPINFO结构体,具体的请自行参照MSDN。在这个函数中,可以实现16,24,以及32位位图的创建,在创建24为位图时biCompression需要设置成BI_RGB,且紧跟在BITMAPINFO后的色盘位字段为0。
然后就是显示BMP文件,Windows有一个自带的函数LoadImage,可是他的功能太过于强大,不能知道很多位图文件的内部信息,但只是可以使用GetObject,就可以从读入的图片中取得想要的信息,同时我们用GetModuleHandle取得调用LoadImage的实体代号,故函数如下:
//位图资源加载
BOOL CDibSection::LoadBmp(const char *path)
{
Destroy();
hBitmap = (HBITMAP)::LoadImage(::GetModuleHandle(0), path, IMAGE_BITMAP, 0, 0,
LR_CREATEDIBSECTION | LR_LOADFROMFILE);
if (!hBitmap)
return FALSE;
DIBSECTION dib;
if (::GetObject(hBitmap, sizeof(DIBSECTION), &dib) != sizeof(DIBSECTION)) {
::DeleteObject(hBitmap);
hBitmap = 0;
return FALSE;
}
Header.Info = dib.dsBmih;
for (int i=0; i<3; i++)
Header.BitField[i] = dib.dsBitfields[i];
bytes_per_pixel = PixelBytes(dib.dsBmih.biBitCount);
bytes_per_line = ScanBytes(dib.dsBmih.biWidth, dib.dsBmih.biBitCount);
Bits = dib.dsBm.bmBits;
return TRUE;
}
在我们通过一个bitblt函数,就可以把图像画到client上,Draw函数如下,
//图像绘制
inline void CDibSection::Draw(HDC dc, int x, int y, int w, int h, int ox, int oy)
{
BitBlt(dc, x, y, w, h, CDibDC(*this, dc), ox, oy, SRCCOPY);
}
inline void CDibSection::Draw(HDC dc, const CRect &rect, CPoint point)
{
Draw(dc, rect.left, rect.top, rect.Width(), rect.Height(), point.x, point.y);
}
inline void CDibSection::Draw(HDC dc, const CRect &rect)
{
Draw(dc, rect.left, rect.top, rect.Width(), rect.Height(), rect.left, rect.top);
}
但是,光能显示图像,是不够的,还要进行图像的处理,如复制,重叠等
对于复制,我们是在缓冲区里进行图像的bitblt,在draw到屏幕表面,代码如下
//图像复制
void CDibSection::Copy(const CDibSection &dib)
{
BitBlt(CDibDC(*this), 0, 0, dib.Width(), dib.Height(), CDibDC(dib), 0, 0, SRCCOPY);
}
void CDibSection::Copy(const CDibSection &dib, CPoint to, CSize size, CPoint from)
{
BitBlt(CDibDC(*this), to.x, to.y, size.cx, size.cy, CDibDC(dib), from.x, from.y, SRCCOPY);
}
对于图像重叠,我现在见到的有两种方法,首先就是通过bitblt的SRCCOPY,与SRCPAINT,反正就是把需要贴图的图片做两份,一份是原图,另一份是图像全部涂黑,背景涂白,然后两张图片与背景贴图进行不断地与和或,具体实现我不记得了,反正觉得这种方法很麻烦。而第二种方法,就是把一种颜色设成背景色,如白色RGB(255, 255, 255),然后把要贴图的图片的像素一个一个读出来,如果是白色就不管,是其它颜色就贴到背景图上去,这种方法虽然实现比较复杂,但是很容易理解,而且不需要画另一张图,对于本来对CG就很头痛得我们是一个不错的选择。图像重叠代码如下
//图象重叠
void CDibSection::Mix(const CDibSection &dib, CPoint to, CSize size, CPoint from, COLORREF tc)
{
if (Depth() == dib.Depth()) {
switch (Depth()) {
case 16:
{
BYTE r = GetRValue(tc);
BYTE g = GetGValue(tc);
BYTE b = GetBValue(tc);
WORD color = (r << 7) & 0x7c00 | (g << 2) & 0x03e0 | (b >> 3) & 0x001f;
for (int y = 0; y < size.cy; y++) {
WORD *p = (WORD *)GetBits(to.x, to.y + y);
const WORD *q = (const WORD *)dib.GetBits(from.x, from.y + y);
for (int x = 0; x < size.cx; x++) {
if (*q != color)
*p = *q;
p++;
q++;
}
}
}
break;
case 24:
{
BYTE r = GetRValue(tc);
BYTE g = GetGValue(tc);
BYTE b = GetBValue(tc);
for (int y = 0; y < size.cy; y++) {
BYTE *p = (BYTE *)GetBits(to.x, to.y + y);
const BYTE *q = (const BYTE *)dib.GetBits(from.x, from.y + y);
for (int x = 0; x < size.cx; x++) {
if (q[0] != b || q[1] != g || q[2] != r) {
p[0] = q[0];
p[1] = q[1];
p[2] = q[2];
}
p += 3;
q += 3;
}
}
}
break;
case 32:
{
for (int y = 0; y < size.cy; y++) {
DWORD *p = (DWORD *)GetBits(to.x, to.y + y);
const DWORD *q = (const DWORD *)dib.GetBits(from.x, from.y + y);
for (int x = 0; x < size.cx; x++) {
if ((*q & 0xffffff) != tc)
*p = *q;
p++;
q++;
}
}
}
break;
}
}
else {
CDibDC dst(*this);
CDibDC src(dib);
for (int y = 0; y < size.cy; y++) {
for (int x = 0; x < size.cx; x++) {
COLORREF c = src.GetPixel(from.x + x, from.y + y);
if (c != tc)
dst.SetPixelV(to.x + x, to.y + y, c);
}
}
}
}
在该函数中,我们使用GetBits来得到指定坐标点的像素指针,然后通过指针操作来进行图像重叠。
另外在图像操作中,还涉及到一些特效的处理,如淡化,百叶窗效果等,本文不做讨论,对于卷屏,将会在以后的角色移动中讨论。
所以通过上面几个函数,我们就可以实现几个最简单的图像处理。
首先声明几个变量
CDibSection view; //视图
CDibSection map; //地图
CDibSection player; //角色
然后进行图片的加载,
map.LoadBmp("map.bmp");
player.LoadBmp("player.bmp");
在进行缓存的创建,注意这里我们是先加载图片后再进行的创建,原因是我们要一次将整个map文件给加载进去,所以创建函数为
view.Create(map.Width(), map.Heigth(), 24);
然后进行贴图处理,先把map贴到view里,然后再与player重叠
view.Copy(map);
View.Mix(player, CPoint(0, 0), size, CPoint(0, 0), RGB(255, 255, 255));
这样就在屏幕上显示了一个贴有角色的地图。
具体代码如下:
#ifndef __DibSection_h__
#define __DibSection_h__
//
// DIB Section class
//
class CDibSection {
public:
CDibSection();
CDibSection(const CDibSection &dib);
~CDibSection();
BOOL Create(int width, int height, int depth);
void Destroy();
BOOL LoadBmp(const char *path);
BOOL SaveBmp(const char *path);
void Copy(const CDibSection &dib);
void Copy(const CDibSection &dib, CPoint to, CSize size, CPoint from);
void Mix(const CDibSection &dib, CPoint to, CSize size, CPoint from,
COLORREF transparent = RGB(0, 255, 0));
void Draw(HDC dc, int x, int y, int w, int h, int ox=0, int oy=0);
void Draw(HDC dc, const CRect &rect, CPoint point);
void Draw(HDC dc, const CRect &rect);
const void *GetBits() const { return Bits; }
void *GetBits() { return Bits; }
const void *GetBits(int x, int y) const;
void *GetBits(int x, int y);
BOOL IsOK() const { return Bits != 0; }
int Width() const { return Header.Info.biWidth; }
int Height() const { return Header.Info.biHeight; }
int Depth() const { return Header.Info.biBitCount; }
int BytesPerLine() const { return bytes_per_line; }
int BytesPerPixel() const { return bytes_per_pixel; }
HBITMAP Handle() const { return hBitmap; }
CDibSection &operator=(const CDibSection &dib);
static int ScanBytes(int width, int depth);
static int PixelBytes(int depth);
protected:
struct {
BITMAPINFOHEADER Info;
DWORD BitField[3];
} Header; //位图头文件结构
HBITMAP hBitmap; //位图句柄
void *Bits; //指向位图数据的指针
int bytes_per_line;
int bytes_per_pixel;
} ;
class CDibDC: public CDC {
public:
CDibDC(const CDibSection &dib);
CDibDC(const CDibSection &dib, HDC hDC);
~CDibDC();
protected:
HDC hScreenDC;
HGDIOBJ hOldBitmap;
} ;
inline CDibDC::CDibDC(const CDibSection &dib)
{
hScreenDC = ::GetDC(0);
HDC memDC = ::CreateCompatibleDC(hScreenDC);
hOldBitmap = ::SelectObject(memDC, dib.Handle());
Attach(memDC);
}
DibSection.cpp
#include "StdAfx.h"
#include "DibSection.h"
//
//
//
CDibSection::CDibSection()
: hBitmap(0), Bits(0)
{
}
CDibSection::CDibSection(const CDibSection &dib)
: hBitmap(0), Bits(0)
{
if (Create(dib.Width(), dib.Height(), dib.Depth()))
Copy(dib);
}
//
//
//
CDibSection::~CDibSection()
{
Destroy();
}
//
//位图结构创建
//
BOOL CDibSection::Create(int width, int height, int depth)
{
Destroy();
bytes_per_line = ScanBytes(width, depth);
bytes_per_pixel = PixelBytes(depth);
Header.Info.biSize = sizeof(BITMAPINFOHEADER);
Header.Info.biWidth = width;
Header.Info.biHeight = height;
Header.Info.biBitCount = depth;
Header.Info.biPlanes = 1;
Header.Info.biXPelsPerMeter = 0;
Header.Info.biYPelsPerMeter = 0;
Header.Info.biClrUsed = 0;
Header.Info.biClrImportant = 0;
Header.Info.biCompression = depth == 24? BI_RGB: BI_BITFIELDS;
Header.Info.biSizeImage = bytes_per_line * height;
switch (depth) {
case 16:
Header.BitField[0] = 0x7c00;
Header.BitField[1] = 0x03e0;
Header.BitField[2] = 0x001f;
break;
case 32:
Header.BitField[0] = 0xff0000;
Header.BitField[1] = 0x00ff00;
Header.BitField[2] = 0x0000ff;
break;
default:
Header.BitField[0] = 0;
Header.BitField[1] = 0;
Header.BitField[2] = 0;
break;
}
HDC dc = ::GetDC(0);
hBitmap = CreateDIBSection(dc, (BITMAPINFO *)&Header, DIB_RGB_COLORS, &Bits, NULL, 0);
::ReleaseDC(0, dc);
return hBitmap != 0;
}
//
//
void CDibSection::Destroy()
{
if (hBitmap) {
GdiFlush();
::DeleteObject(hBitmap);
hBitmap = 0;
}
}
//
//位图资源加载
//
BOOL CDibSection::LoadBmp(const char *path)
{
Destroy();
hBitmap = (HBITMAP)::LoadImage(::GetModuleHandle(0), path, IMAGE_BITMAP, 0, 0,
LR_CREATEDIBSECTION | LR_LOADFROMFILE);
if (!hBitmap)
return FALSE;
DIBSECTION dib;
if (::GetObject(hBitmap, sizeof(DIBSECTION), &dib) != sizeof(DIBSECTION)) {
::DeleteObject(hBitmap);
hBitmap = 0;
return FALSE;
}
Header.Info = dib.dsBmih;
for (int i=0; i<3; i++)
Header.BitField[i] = dib.dsBitfields[i];
bytes_per_pixel = PixelBytes(dib.dsBmih.biBitCount);
bytes_per_line = ScanBytes(dib.dsBmih.biWidth, dib.dsBmih.biBitCount);
Bits = dib.dsBm.bmBits;
return TRUE;
}
//
//位图资源保存
//
BOOL CDibSection::SaveBmp(const char *path)
{
TRY {
CFile file(path, CFile::modeCreate | CFile::modeWrite);
int length = bytes_per_line * Height();
BITMAPFILEHEADER header;
memset(&header, 0, sizeof(header));
header.bfType = (WORD)('M' << 8) | 'B';
header.bfSize = sizeof(header) + Header.Info.biSize + length;
header.bfOffBits = sizeof(header) + Header.Info.biSize;
file.Write(&header, sizeof(header));
file.Write(&Header.Info, Header.Info.biSize);
if (Header.Info.biCompression == BI_BITFIELDS)
file.Write(Header.BitField, sizeof(Header.BitField));
file.Write(Bits, length);
file.Close();
}
CATCH(CFileException, e) {
return FALSE;
}
END_CATCH
return TRUE;
}
//
//图像复制
//
void CDibSection::Copy(const CDibSection &dib)
{
BitBlt(CDibDC(*this), 0, 0, dib.Width(), dib.Height(), CDibDC(dib), 0, 0, SRCCOPY);
}
void CDibSection::Copy(const CDibSection &dib, CPoint to, CSize size, CPoint from)
{
BitBlt(CDibDC(*this), to.x, to.y, size.cx, size.cy, CDibDC(dib), from.x, from.y, SRCCOPY);
}
//
//图象重叠
//
void CDibSection::Mix(const CDibSection &dib, CPoint to, CSize size, CPoint from, COLORREF tc)
{
if (Depth() == dib.Depth()) {
switch (Depth()) {
case 16:
{
BYTE r = GetRValue(tc);
BYTE g = GetGValue(tc);
BYTE b = GetBValue(tc);
WORD color = (r << 7) & 0x7c00 | (g << 2) & 0x03e0 | (b >> 3) & 0x001f;
for (int y = 0; y < size.cy; y++) {
WORD *p = (WORD *)GetBits(to.x, to.y + y);
const WORD *q = (const WORD *)dib.GetBits(from.x, from.y + y);
for (int x = 0; x < size.cx; x++) {
if (*q != color)
*p = *q;
p++;
q++;
}
}
}
break;
case 24:
{
BYTE r = GetRValue(tc);
BYTE g = GetGValue(tc);
BYTE b = GetBValue(tc);
for (int y = 0; y < size.cy; y++) {
BYTE *p = (BYTE *)GetBits(to.x, to.y + y);
const BYTE *q = (const BYTE *)dib.GetBits(from.x, from.y + y);
for (int x = 0; x < size.cx; x++) {
if (q[0] != b || q[1] != g || q[2] != r) {
p[0] = q[0];
p[1] = q[1];
p[2] = q[2];
}
p += 3;
q += 3;
}
}
}
break;
case 32:
{
for (int y = 0; y < size.cy; y++) {
DWORD *p = (DWORD *)GetBits(to.x, to.y + y);
const DWORD *q = (const DWORD *)dib.GetBits(from.x, from.y + y);
for (int x = 0; x < size.cx; x++) {
if ((*q & 0xffffff) != tc)
*p = *q;
p++;
q++;
}
}
}
break;
}
}
else {
CDibDC dst(*this);
CDibDC src(dib);
for (int y = 0; y < size.cy; y++) {
for (int x = 0; x < size.cx; x++) {
COLORREF c = src.GetPixel(from.x + x, from.y + y);
if (c != tc)
dst.SetPixelV(to.x + x, to.y + y, c);
}
}
}
}
//
//
// (﹚)
//
CDibSection &CDibSection::operator=(const CDibSection &dib)
{
if (this != &dib) {
if (Create(dib.Width(), dib.Height(), dib.Depth()))
Copy(dib);
}
return *this;
}
//
//