技术开发 频道

关于携带完整alpha通道图标的技术研究

  【IT168 技术】 随着计算机硬件和操作系统的发展,用户,开发者都对用户界面的美化追求逐渐提高。我们可以看到每一代操作系统都对图形界面进行提升,图标也经历和见证了这个发展过程。目前的图标是随图像携带遮罩(mask)图像的多文件格式,但发展到目前为止,这种遮罩图像还是二值图形,也就是仅有“显示”或“不显示”两种截然分明的选择,而不存在中间选择(也就是和屏幕底色的alpha合成)。

  对图标的显示方式我们简要回顾以下:一个图标有下面的XOR和AND两种图像组成,其中前者可以是从16色 到真彩色图像,后者则是二值图像;

  在系统绘制 icon 时,先把AND二值 图像用“与”合成模式(SRCAND)复制到屏幕,可以从上图中看出,这时屏幕上图标透明像素(mask中的白色部分)所在部分不受影响,而不透明部分都变成0(黑色),然后再用XOR真彩色图像用“异或”(SRCINVERT)操作复制到屏幕,异或的特点是相同为0,相异为1:

  0 ^ 0 =0; 0 ^ 1 =1; 1 ^ 1 =0

  因此,XOR 真彩图像的黑色部分屏幕颜色保持不变,而图标不透明部分和 0 异或,因此这部分XOR图像被覆盖方式复制到屏幕。这样就实现了图标绘制后的效果。可见图标文件定义后的绘制非常简洁方便。

  但是我忽然产生一个想法,由于现在的图标在贴图时非0即1,从不透明到完全透明是一个跳跃性的转变,而没有中间介于二者之间的模糊状态,因此我为什么不尝试把遮罩图像从二值图扩充为一个完整的 Alpha 通道呢。在 Photoshop 里面,alpha 通道是用户极其熟悉的概念,它本质上是一个和原图大小相同的灰度图像(位深度=8),用于表述选区,图层合成,蒙版等等。这样,图标在贴图时,就可以和屏幕进行更加“柔和”的 alpha 合成效果,实现和 photoshop 中多个图层合成相同的效果,可以减少图标的突兀和锯齿感等等。我们知道,随着GDI发展,可能也开始逐渐支持 alpha 合成,但是通常仅仅是携带一个统一的alpha参数,应用到全图。而我们设想的是,alpha信息实际上是属于像素的一个属性,和具体图形相关,因此它应该由图标图形文件本身携带才合理。因此一个图标图像中每一个像素点可以具有自己独立的 alpha 值,这正是 photoshop 图像处理中的特征。

  为了简单期间,我们对所有的图标都采用格式明确的 BMP 格式,这有利于我们的代码实现。考虑一个 普通 RGB图像,位深度(bpp)为24,我们的方法是,在这个文件的结尾追加alpha通道,也就是再补充一个和原图尺寸相同的灰度图像,使图像的位深在逻辑上增加到 32。当然,改写后的文件站在原来角度上来看,它依然是一个普通的 bmp 文件,只是结尾多了一些内容而已。

  设计的合成效果如下图所示:

  下面我采用了VC来做功能实现,最开始我选择VC6,但是忽然想到可能很多人不会装VC6,为了免却项目升级的麻烦,我马上改成了VS2005,但我想这个Demo的代码可以等效的转换成C#,采用 VC 来实现主要是首要基于效率的考虑。

  在项目中我增加了一个类: CAlphaIcon, 它具有以下的一些函数:

CAlphaIcon.h

  //合成像素

  #define BLEND(c1,c2,a) ((((c1)*(255-(a))+(c2)*(a)))/255)

  typedef struct _BITMAP_PRIVATE_DATA

  {

  BYTE *pPixels; /*指向位图数据的指针 RGB图像!原始数据!*/

  BYTE *pAlpha; /*指向Alpha数据的指针(灰度图像)*/

  BYTE *lpDIBData;//DIB文件的位图数据块内存起始地址(Scan0,可以直接修改DIB)

  BYTE bpp; //位深度(=24)

  unsigned int stride; /*扫描行宽度*/

  unsigned int strideAlpha; /*Alpha通道的扫描行宽度*/

  unsigned int width; /*图片高度*/

  unsigned int height; /*图片宽度*/

  HANDLE hSection; /*DIB Section 的内存映射文件句柄*/

  } BITMAP_PRIVATE_DATA, *LPBITMAP_PRIVATE_DATA;

  //带有全Alpha信息通道的图标

  class CAlphaIcon

  {

  private:

  BITMAP_PRIVATE_DATA m_data;

  BITMAPINFO m_bminfo; //DIB信息

  public:

  bool Load(TCHAR *filename);

  void DeleteBitmap();

  void Draw(HDC hDC, int nXDest, int nYDest);

  void GetSize(SIZE* sz);

  bool WriteIconFile(TCHAR* bmfile, TCHAR* alfile);

  bool IsNull(); //用于判断已经是否加载了图片

  };

  这个类主要负责对我们所设想的全alpha信息图标的绘制,加载,以及根据一个位图和一个alpha灰度图生成这种图标。这里的函数是以我以前写过的一篇文章《用C语言读取 bmp 位图》的代码为基础的。

  下面我们主要介绍以下这种全alpha通道图标的绘制过程。我主要考虑以下几点,一是不可能再使用传统图标的那种BitBlt 块传送方式去简单完成,因为每一个像素都需要到和屏幕进行一个自己的合成。二是尽管使用 GetPixel, SetPixel函数读写像素非常方便简单,但是这样做每个像素都需要从HDC走,考虑到绘制效率,我们不能这样去做。而是要通过直接操作内存中的位图数据块去做alpha合成,然后再一次性 bitblt 到屏幕。

  绘制步骤简要如下:

  (1)先把图标的数据和alpha通道数据加载到内存。并得到位图的扫描行宽度(stride),图像尺寸等信息。

  (2)创建了一个内存映射文件句柄 hSection;并得到该内存文件一个视图内存指针: lpDIBData。

  (3)从HDC 创建一个 DIB 位图: CreateDIBSection,把已经创建的内存文件句柄传递进去,这时 lpDIBData 设置为指向文件起始处。

  (4)创建一个内存DC,并选入DIB位图,然后把屏幕数据 bitblt 传送到内存DC。此时 lpDIBData 指向屏幕位图数据块。

  (5)这时我们改写 lpDIBData 指向的内存数据,用加载到内存中的图标数据和alpha通道数据进行逐一alpha合成。

  (6)再把内存DC bitblt 拷贝送回到 HDC 即可。

  这里把 AlphaIcon.cpp的主体代码展示如下,注意,代码中的以“t_”为前缀的函数是我在 AlphaIcon.h 中的函数名宏定义,是为了同时适用于 unicode 和多字节字符串环境。具体可以参见 AlphaIcon.h中的代码。

1
相关文章