现在的位置: 首页 > 综合 > 正文

安全的图像引擎

2014年01月12日 ⁄ 综合 ⁄ 共 3079字 ⁄ 字号 评论关闭
Symbian C++游戏的2D图像显示部分一般由下面几个类组成:
  图像 - 封装了一个CWsBitmap。是基本的图片资源。支持图像之间的各种贴图和混合操作。
  双缓冲 - 一个和屏幕分辨率、色深相等的图像。
  直接写屏支持 - 复合一个CDirectScreenAccess对象,实现MDirectScreenAccess接口。负责直接写屏的安全处理。比如来电、屏保时适时的停止和开启直接写屏与游戏逻辑。
  绘图类 - 负责在图像中绘图。它不是对Gc的封装,而是通过直接修改图像内存区进行绘图。
  位图字体类 - 使用预先创建的位图资源写字。如下图就是一个预先创建的位图资源。优点是速度快,缺点是无法支持大字符集合,比如中文。

 

  字体缓冲区类 - 还是使用Gc的DrawText函数绘制文字。但是同时用一张位图作为一个缓冲区存储最近绘制的文字。既能支持大字符集合,速度也很快。
如果需要学习图形和直接写屏的基础,请参考Programming Games in C++ v1.0(www.forum.nokia.com/main/1.6566.21.00.html)。本文主要针对图像类和直接写屏类讲几个容易被忽略的问题。

1.4.1. 图像类的直接内存访问

        贴图是2D游戏最主要的画面操作。为了实现快速的贴图,或者实现某种混合效果,就不能再使用CFbsBitGc的BitBlt或者BitBltMasked进行贴图,而必须自己得到图片的内存地址,直接读写其中的数据。在读写图片内存地址的过程中,有几点需要加以注意。
首先,只有当源图片和目标图片色深相等时,才更容易进行贴图操作。所以,再载入图片的过程中,我习惯把非4k色的图片转化为4k色。之所以选择4k色是因为它也是后台缓冲区的色深。下面的代码通过转换可以保证iImage是4k色的图像。
// Make sure that we have a 4K color depth image in iImage
 if (iImage->DisplayMode() != EColor4K)
 {
  // Create 4k color image
  CFbsBitmap* image = new (ELeave) CWsBitmap();
  CleanupStack::PushL(image);
  User::LeaveIfError(image->Create(iSize, EColor4K));

  // Create device
  CFbsBitmapDevice* device = CFbsBitmapDevice::NewL(image);
  CleanupStack::PushL(device);
  CFbsBitGc* gc;
  User::LeaveIfError(device->CreateContext(gc));
  CleanupStack::PushL(gc);

  // Bitblt to new color depth
  gc->BitBlt(TPoint(0,0), iImage);

  // Destroy context and device;
  CleanupStack::PopAndDestroy(); // gc
  CleanupStack::PopAndDestroy(); // device
  CleanupStack::Pop();  // image
  delete iImage;
  iImage = image;
 }
        其次,Symbian系统在内存匮乏时会进行碎片整理。所以如果简单的用CFbsBitmap::DataAddress获取内存首地址并开始读写,那么可能在你读写的过程中,图片已经被悄悄的移动了位置,你读写的就是一块无效的内存区域。解决这个问题的办法是在获取首地址前,必须先锁定图像内存区域。在高版本的60系列SDK中(比如2.0,2.1),有LockHeap和UnlockHeap函数可以完成这个操作。但是在低版本的SDK中(比如0.9,1.0),这两个函数是私有的。我们必须通过TBitmapUtil锁定内存。但是不一定必须使用TBitmapUtil的SetPixel和GetPixel函数进行位操作。下面是最基本的没有关键色和Alpha通道的简单贴图代码。
void CImage::RenderToBitmapL(CFbsBitmap* aBmp, TPoint aPos, const TRect& aRect)
{
 // 在此计算贴图目标矩形区域
 // 代码略去

 // 没有关键色和蒙板的最简单、最快情况
 if (!iKey && iMask == NULL)
 {
  // 锁定
  TBitmapUtil bmpUtil1(ImageL());
  TBitmapUtil bmpUtil2(aBmp);
  bmpUtil1.Begin(TPoint(0,0));
  bmpUtil2.Begin(TPoint(0,0), bmpUtil1);

  // 获取首地址
  TUint16* addr2 = (TUint16*)ImageL()->DataAddress(); // source image
  TUint16* addr = (TUint16*)aBmp->DataAddress(); // target bmp
  TInt line = aBmp->ScanLineLength(
   aBmp->SizeInPixels().iWidth,
   EColor4K) / 2;
  TInt line2 = iImage->ScanLineLength(  // line length in 16bit word
   iImage->SizeInPixels().iWidth,
   EColor4K) / 2;

  // 计算扫描持续量和跳跃量
  TInt jump = line - rectw;
  TInt lasting2 = rectw;
  TInt jump2 = line2 - lasting2;

  // 获取贴图首地址
  TUint16* p = addr + fromY * aBmp->SizeInPixels().iWidth + fromX;
  TUint16* p2 = addr2 + line2 * recty + rectx;
  
  // The first pixel out of interest
  TUint16* p2end = p2 + line2 * (toY - fromY - 1) + lasting2 + jump2;

  // 开始扫描
  while(p2 != p2end)
  {
   // 开始一个扫描行
   TUint16* p2endline = p2 + lasting2;
   while(p2 != p2endline)
   {
    // 复制一个像素
    *p = *p2;
    // 移动到下一个像素
    p++; p2++;
   }
   // 跳到下一行
   p += jump; p2 += jump2;
  }

  // 解锁
  bmpUtil2.End();
  bmpUtil1.End();

  return;
 }

 // 其它情况。有关键色等等.
 // ...

最后告诉大家几个优化的小窍门:
  使用While循环直接把指针的比较作为循环结束条件。不要再多用一个整数来控制循环。
  贴图是个两重循环,如果你的代码需要判断是否支持关键色和Alpha通道等,尽量把判断外移到循环之外。每个象素都进行好几个if判断的开销太不值得。比如上面的代码,处理最简单的情况时,while循环内一个if都没有。
  4k色时,RGB内存排列如下图。所以未被使用的4位正巧可以用来存储alpha通道。

抱歉!评论已关闭.