游戏编程一:位图的处理

本文详细介绍了在游戏编程中处理位图的方法,包括如何使用双缓存策略,创建DIBSection,以及位图头文件结构的定义。通过LoadImage和CreateDIBSection函数来加载和创建位图,接着展示了如何进行位图复制、重叠等图像处理,以及处理透明度的方法。代码示例中展示了如何进行图像的复制、混合操作,为游戏场景添加角色和地图。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 在游戏编程中,位图的处理是一个重要的环节,我将结合这几天我们小组制作游戏的具体情况,简单介绍一下我们小组对位图的处理方法。
    首先我们采用的当然是双缓存策略,就是在缓存里面画好图后在用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;
}

//
//

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值