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

      ========================================================================
      【声明】由于网友提醒,GDI+支持png图片的alpha绘制了。所以不再发布到首页了。感谢“斯克迪亚”网友的提醒。 

                  -- hoodlum1980  @ 2009年8月6日

      补充: WINDOWS 从 XP 系统已经支持带有 alpha 通道的图标,即具有“反锯齿”效果的图标,要求这种图标必须提供大概九副图像。

      具体细节可参考MSDN文档中的相关说明。

      -- hoodlum1980 @ 2009年12月24日

      ========================================================================
     随着计算机硬件和操作系统的发展,用户,开发者都对用户界面的美化追求逐渐提高。我们可以看到每一代操作系统都对图形界面进行提升,图标也经历和见证了这个发展过程。目前的图标是随图像携带遮罩(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 图像处理中的特征。

      (补记:正是因为GDI+支持绘制 PNG 格式的透明位图,因此本文实际意义已经失去。)

      为了简单期间,我们对所有的图标都采用格式明确的 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中的代码。
      

AlphaIcon.cpp
#include "StdAfx.h"
#include "AlphaIcon.h"

/*加载位图,注意,只能加载bpp=24的位图*/
bool CAlphaIcon::Load(TCHAR *filename)
{
    FILE* stream;
    int i,j;
    size_t bytesRead=0, stride=0;
    /*必须是long型(即int32)*/
    long offset;
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER infoHeader;
    
    //是否加载过图片
    if(this->m_data.pPixels != NULL)
        this->DeleteBitmap();

    stream=t_fopen(filename,_T("rb"));
    if(stream==NULL)
    {
        //open file failed
        return false;
    }
    fseek(stream,0,0);
    fread(&fileHeader,sizeof(fileHeader),1,stream);
    fread(&infoHeader,sizeof(infoHeader),1,stream);

    /*设置图片的宽度和高度信息*/
    this->m_data.width=(unsigned int)(infoHeader.biWidth);
    this->m_data.height=(unsigned int)(infoHeader.biHeight);
    this->m_data.bpp = (BYTE)infoHeader.biBitCount;//位深度

    /* stride: scan line bytes count. padding for 4 bytes */
    /* stride:扫描行宽度 */
    this->m_data.stride=(infoHeader.biBitCount * infoHeader.biWidth+31)/32*4;
    this->m_data.strideAlpha = (8* infoHeader.biWidth + 31)/32*4;

    /*分配数据空间!*/
    this->m_data.pPixels=(BYTE*)malloc( this->m_data.stride * infoHeader.biHeight );
    if(this->m_data.pPixels==NULL) /*检测内存是否分配成功!*/
    {
        fclose(stream);
        return false;
    }

    this->m_data.pAlpha = (BYTE*)malloc(this->m_data.strideAlpha * infoHeader.biHeight );
    if(this->m_data.pAlpha==NULL) /*检测内存是否分配成功!*/
    {
        free(this->m_data.pPixels);
        fclose(stream);
        return false;
    }
    
    //加载位图数据
    fseek(stream, fileHeader.bfOffBits, SEEK_SET);
    bytesRead = fread(this->m_data.pPixels, 1, this->m_data.stride * this->m_data.height, stream);

    //加载alpha通道
    bytesRead = fread(this->m_data.pAlpha, 1, this->m_data.strideAlpha * this->m_data.height, stream);

    fclose(stream);    /* close the bitmap file */

    //设置bminfo
    memcpy(&this->m_bminfo.bmiHeader, &infoHeader, sizeof(BITMAPINFOHEADER));

    //打开内存映射
    this->m_data.hSection = CreateFileMapping(
        INVALID_HANDLE_VALUE,    // use paging file
        NULL,                    // default security 
        PAGE_READWRITE,          // read/write access
        0,                       // max. object size 
        this->m_data.stride * this->m_data.height,// buffer size  
        NULL);       // name of mapping object

    this->m_data.lpDIBData = (BYTE*)MapViewOfFile(
        this->m_data.hSection,
        FILE_MAP_ALL_ACCESS,
        0,
        0,    //文件偏移地址
        0); //If dwNumberOfBytesToMap is zero, the entire file is mapped. 

    return true;/*加载成功!返回*/
}

/*释放位图数据占用的内存*/
void CAlphaIcon::DeleteBitmap()
{
    /*释放内存!*/
    if(this->m_data.pPixels != NULL)
        free(this->m_data.pPixels);

    if(this->m_data.pAlpha != NULL)
        free(this->m_data.pAlpha);    

    //关闭内存映射
    if(this->m_data.lpDIBData!=NULL)
        UnmapViewOfFile(this->m_data.lpDIBData);

    if(this->m_data.hSection != NULL)
        CloseHandle(this->m_data.hSection);

    this->m_data.pPixels = NULL;
    this->m_data.pAlpha = NULL;
    this->m_data.lpDIBData = NULL;
    this->m_data.hSection = NULL;
    return;    
}

//把图片绘制到HDC
void CAlphaIcon::Draw(HDC hDC, int nXDest, int nYDest)
{
    int i,j;
    //是否已经分配了空间
    if(this->m_data.pPixels == NULL) return;

    LPVOID lpBits;//接收数据起始地址
    HBITMAP hDIB = CreateDIBSection(hDC, &this->m_bminfo, DIB_PAL_COLORS, &lpBits, this->m_data.hSection, 0);

    HDC hmemdc = CreateCompatibleDC(hDC);
    HGDIOBJ hOldBm = SelectObject(hmemdc, hDIB);
    //贴图
    BitBlt(hmemdc, 0, 0, this->m_data.width, this->m_data.height, hDC, nXDest, nYDest, SRCCOPY);

    //依次对每个像素进行alpha合成
    for(i=0; i< this->m_data.stride * this->m_data.height; i++)
    {
        this->m_data.lpDIBData[i] = BLEND(
                this->m_data.lpDIBData[i],    //背景像素值
                this->m_data.pPixels[i],    //上层像素值
                this->m_data.pAlpha[i/3]    //alpha
                );
    }

    BitBlt(hDC, nXDest, nYDest, this->m_data.width, this->m_data.height, hmemdc, 0, 0, SRCCOPY);    
    //清理
    SelectObject(hmemdc, hOldBm);
    DeleteDC(hmemdc);
    DeleteObject(hDIB);
}

// 获取位图尺寸
void CAlphaIcon::GetSize(SIZE* sz)
{
    if(this->m_data.pPixels == NULL || sz==NULL)
    {
        return;
    }

    sz->cx = this->m_data.width;
    sz->cy = this->m_data.height;
    return;
}

//生成一个特殊图标文件,注意文件bmfile就会被改写!
//bmfile: 普通位图文件
//alfile: alpha通道位图
bool CAlphaIcon::WriteIconFile(TCHAR* bmfile, TCHAR* alfile)
{
    TCHAR path[_MAX_PATH];
    TCHAR drive[_MAX_DRIVE];
    TCHAR dir[_MAX_DIR];
    TCHAR fname[_MAX_FNAME], backupfname[_MAX_FNAME];
    TCHAR ext[_MAX_EXT];

    FILE *stream1, *stream2;
    int i,j;
    size_t bytesRead=0, stride=0;
    /*必须是long型(即int32)*/
    long offset;
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER infoHeader;
    
    //是否加载过图片
    if(this->m_data.pPixels != NULL)
        this->DeleteBitmap();

    //对bmp文件进行追加alpha通道的内容
    stream1=t_fopen(bmfile, _T("ab+"));
    if(stream1==NULL)
    {
        //open file failed
        return false;
    }

    stream2=t_fopen(alfile,_T("rb"));
    if(stream2==NULL)
    {
        //open file failed
        fclose(stream1);
        return false;
    }

    //此时,对原来的位图进行以下备份,原位图拷贝到 FILENAME_backup.bmp
    t_splitpath(bmfile, drive, dir, fname, ext);
    t_sprintf(backupfname, _T("%s_backup"), fname);
    t_makepath(path, drive, dir, backupfname, ext);
    CopyFile(bmfile, path, FALSE);    //覆盖写

    fread(&fileHeader,sizeof(fileHeader),1,stream2);
    fread(&infoHeader,sizeof(infoHeader),1,stream2);

    /* stride: scan line bytes count. padding for 4 bytes */
    /* stride:扫描行宽度 */
    stride =(infoHeader.biBitCount * infoHeader.biWidth+31)/32*4;

    /*分配数据空间!*/
    BYTE *buffer = (BYTE*)malloc(stride * infoHeader.biHeight);
    if(buffer==NULL) /*检测内存是否分配成功!*/
    {
        fclose(stream1);
        fclose(stream2);
        return false;
    }

    //加载alpha通道数据
    fseek(stream2, fileHeader.bfOffBits, SEEK_SET);
    bytesRead = fread(buffer, 1, stride * infoHeader.biHeight, stream2);
    //追加到文件1尾部
    fwrite(buffer, 1, stride * infoHeader.biHeight, stream1);
    
    //清理
    fclose(stream1);
    fclose(stream2);    /* close the bitmap file */
    free(buffer);

    //把追加好的文件拷走成为 FILENAME_ICON.bmp
    t_sprintf(backupfname, _T("%s_ICON"), fname);
    t_makepath(path, drive, dir, backupfname, ext);
    CopyFile(bmfile, path, FALSE);    //覆盖写

    //把备份文件拷回成原来的文件名 FILENAME_backup.bmp -> FILENAME.bmp
    t_sprintf(backupfname, _T("%s_backup"), fname);
    t_makepath(path, drive, dir, backupfname, ext);
    CopyFile(path, bmfile, FALSE);    //覆盖写

    return true;/*成功!返回*/
}

 //用于判断已经是否加载了图片
bool CAlphaIcon::IsNull()
{
    return (this->m_data.pPixels == NULL);
}

      
      这样我们在绘制图标时,先通过加载方法从文件中读取图像数据,然后在绘制时,把 HDC 参数和起始位置信息传递给 这个类的 Draw 方法即可完成 alpha 合成。由于这种图标文件在现实中不可能存在,所以我还在关于对话框上做了一些简单功能,用于生成这样的携带alpha 通道数据的位图文件。我在项目的“关于对话框”修改如下所示:

      

      在上面半部分是原本的项目框架生成的关于对话框,在下半部分,主要是用于选择用于制作图标的文件。第一个文件是用于显示的原始 RGB 图像,第二个文件是用于追加到文件尾部去的alpha 通道位图文件,它应该是一个普通的灰度图像,和第一个图像的大小完全相同。选择后点击“改写BMP文件”按钮,即可生成一个带有alpha 通道的图标文件。点击后弹出是否成功的提示消息框。如果两个文件都是存在的,则通常是成功的,我并没有对文件本身内容采取更多的校验。
      例如,输入的文件名分别是 icon01.bmp, icon01_alpha.bmp; 则生成的图标文件命名是 icon01_ICON.bmp,同时我还拷贝了一个原图的备份文件(icon01_backup.bmp)。
      由于编写匆忙,demo程序的提示信息并不明确,因此这里简单再介绍以下:
      (1)在demo 程序中,通过帮助->关于打开“关于对话框”去制作 带有alpha通道的图标。
      (2)在文件->选择背景位图菜单,可以打开一个图像文件,作为程序窗口的背景图。通过选择“特殊图标”菜单,可以打开一个由步骤(1)生成的图标文件。同时,可以用鼠标按下去拖曳图标,可以看到图标在背景图上不同位置的合成效果。由于实时刷新会有很强烈的闪烁感,所以为了避免闪烁,我把实时刷新的方式改为了通过绘制焦点矩形的反馈方式。
      下面是这个程序的截图:
      
      

      结论:
      和现在图标的绘制方式相比,应当说处理量是增大的。对于对效率要求更高的场合比如游戏等,需要适当兼顾性能和视觉效果的平衡。

      最后,这里是相关范例的源代码压缩包:
      http://files.cnblogs.com/hoodlum1980/JRL_AlphaIconDemo.rar

时间: 2024-10-11 12:53:07

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

MIDP2.0下处理Alpha通道产生半透明效果

透明 游戏中经常会用到半透明效果.但MIDP1.0年代似乎只有Nokia和LG两家的扩展API给出了可以处理Alpha通道的API.在MIDP2.0下,我们可以用Image类提供的方法得到一个图片的半透明版本.         try {            image=Image.createImage("/ken.png");//载入原图        }        catch (IOException e) { }        int[] argb=new int[ima

Flash笔记-Alpha通道遮罩的三个要素

笔记 好些日子没有玩flash了,今天拾起来作了几个练习,发现之前在吕聪贤网页上看到的alpha通道遮罩居然不会做,出错了N久,最后在帮助中找到了一句话,特此面壁思过一下. 在flash8版本之前,要想做朦胧效果的遮罩,就必须在遮罩块的上面同时做一个羽化或者渐变的元件,使之能够于遮罩块同步,这样效果差,而且麻烦. 现在flash8里可以完全不用搞第三个元件,就做出朦胧的遮罩效果,但是需要谨记三个因素: 1.遮罩与被遮罩元件都必须是影片剪辑(MovieClip).因为alpha通道的遮罩效果必须是

alpha通道值读取-bmp图alpha通道值的读取问题

问题描述 bmp图alpha通道值的读取问题 想要读取bmp图的alpha通道值,bmp图是在photoshop中加了alpha通道的,但读取下来都为255,不知道该怎么正确读取 解决方案 看下你的bmp保存的格式对不对,是不是丢失了alpha

OpenGL ES 贴图图片是否有 Alpha 通道以及图片大小导致无法显示帖图的原因分析

OpenGL ES 贴图图片是否有 Alpha 通道以及图片大小导致无法显示帖图的原因分析 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 如下挖的坑,现在填一下! 1.Alpha通道的问题,在

云制造的体系结构及平台实现技术研究

云制造的体系结构及平台实现技术研究 重庆大学   马刚 本文针对开放式云制造系统的动态性和灵活性,采用服务组件技术,研究了一种新的面向服务的.组件化的云制造体系结构及其平台的实现技术.主要研究内容包括:①分析云制造系统特征及其设计原则,研究提出一种基于组件的云制造层次化体系结构.给出了该体系结构的各层业务功能及其层次关系,讨论了云制造系统的核心组件.以及相关标准和规范.②针对云制造任务和资源的多样性.异构性和复杂性,扩展OWL-S服务本体,建立云制造本体模型并给出云制造的任务及服务的形式化描述方

面向对象的类测试技术研究

面向对象的类测试技术研究 摘要:类是面向对象软件的基本构成单元,类测试是面向对象软件测试的关键.从基于服务的.基于对象动态测试模型的.基于流图的以及基于规约的四个方面论述了类测试的思想和方法. 关键词:面向对象:软件测试:类测试 1.面向对象软件的类测试 面向对象软件从宏观上来看是各个类之间的相互作用.在面向对象系统中,系统的基本构造模块是封装了的数据和方法的类和对象,而不再是一个个能完成特定功能的功能模块.每个对象有自己的生存周期,有自己的状态.消息是对象之间相互请求或协作的途径,是外界使用对

云计算环境中面向取证的现场迁移技术研究

博士论文 云计算环境中面向取证的现场迁移技术研究 华中科技大学  周刚 首先提出了一种新的云计算环境下的计算机取证模型-云计算取证模型,该模型定义了云计算环境下的工作层次,通过场景描述和过程组件的划分,刻画了完整的取证机制.通过对云计算取证模型的完整性和强隔离性的证明,可以将虚拟机镜像文件作为取证的对象进行分析,进而实现云计算环境下的计算机取证过程. 其次,在云计算平台中通过对虚拟化软件层的控制,利用其状态转换,提出了一种虚拟机镜像文件的迁移方法.通过对虚拟化软件层迁移状态时的上层虚拟机的进程标

ftp服务器-毕设求大神指点!!题目是基于SaaS模式的分布式FTP服务器技术研究

问题描述 毕设求大神指点!!题目是基于SaaS模式的分布式FTP服务器技术研究 内容是: 目前,云计算正在各领域得到越来越多的应用,出现了多种云计算环境,掌握云计算的概念,熟悉相关云计算平台上的软件开发方法,对提高学生综合运用所学知识解决实际问题具有十分重要的现实意义.作为云计算平台之一的百度云,已得到了广泛应用, 本课题在理解FTP服务器模式的前提下,在saas环境下建立分布式的ftp服务,用户可通过ftp客户端传送数据. 该课题具有以下要求: 1. 了解云计算的基本原理,掌握基于saas的开

基于LBP算子的运动目标分割技术研究,关于这个题目有人能提供点资料吗,跪谢。

问题描述 基于LBP算子的运动目标分割技术研究,关于这个题目有人能提供点资料吗,跪谢. 基于LBP算子的运动目标分割技术研究,关于这个题目有人能提供点资料吗,跪谢.