[原创] 在Windows 2000中正确显示带Alpha通道的32位图像 (修订版)

2006-08-11 提交原文。
2006-08-12 修正了对.net以及GDI+在Windows 2000下无法正确显示带Alpha通道的32位位图的错误论断。

以下讨论基于.net 2.0以及Windows 2000平台。

大家都知道,在Windows XP及其后继版本中,桌面图标比之前的要好看多了,这不光是因为Microsoft将图标“画”的更精致、色彩更加丰富了,还有一个很重要的原因是采用了带Alpha通道的32位位图(32bpp Alpha Bitmap),也就是通常说的带有透明信息的位图。
带有透明信息的位图和不带有透明信息的位图显示结果是差很多的,如下图所示,左边的是带Alpha通道的32位位图,右边的是普通的不带Alpha通道的24位位图,虽然他们RGB颜色数量一样多(都由24位来表示),可左边的位图显示效果明显好于右边的,它利用Alpha通道消除了边缘锯齿,而且不论在什么背景上都能有很好的表现。


Alpha通道除了常见的用于消除边缘锯齿外,它还能让你显示出半透明的图像,创建类似于拖动Windows图标时的效果,以及很容易地实现阴影效果。

问题是,我发现在Windows 2000上,.net好像不支持32bpp Alpha 图像(或者说是不对它作相应的处理)。不论是利用Bitmap类的静态方法FromFile直接读取,还是从开发环境中把它作为项目资源加载,像素的Alpha信息都会被抛弃,利用Bitmap.GetPixel方法读取到的每个像素的颜色的Alpha分量都是255,也就是都是完全不透明的(它好像并不理睬实际图像数据中每个像素是有不同的Alpha值的),图像的显示变得不正常,本该是全透明的区域都变成了黑色。而且通过Bitmap.PixelFormat属性得到的值是Format32bppRgb,而不是预想中的Format32bppARgb。我估计这是.net会根据系统平台的类型决定是否对像素的Alpha信息作处理,从而通过Bitmap.GetPixel方法和PixelFormat属性读取到的内容也不一样(我没在Windows XP上试过,所以以上只是目前的推测,如果有谁有这方面的经验,欢迎交流)。问题已经找到了,我所使用的所谓的32bpp位图是由IconWorkshop导出的,估计是该软件对图像像素32位数据作了处理,只有它自己能识别并处理,.net和GDI+对带Alpha通道的位图的支持是没问题的,这我已经用png格式图像做过测试了。本文的相关代码也作了相应的修改。感谢沐枫的关注与提醒。—不吃鱼的猫 2006-08-12]

ows 2000下如何利用.net正确显示32位透明位图呢?我们先来学习一下Alpha通道的原理。

我们都知道,一般位图的每个像素的颜色都用R(Red)G(Green)B(Blue)三个颜色分量表示,而带Alpha通道的位图则增加了一个分量,即Alpha分量,用A表示,它代表着像素的透明度,取值0表示完全透明,255表示完全不透明,0到255之间的值表示半透明,也就是说,取值越接近255,透明度越小,背景颜色被遮挡的程度也越高。在32位的带Alpha通道的位图中,这4个分量分别各用8位来表示,于是每个像素的颜色信息就用32位来表示。

一般透明位图都是作为前景图像叠加到背景图像上的,叠加后某个像素的实际显示颜色的计算公式如下:

实际显示颜色 = 前景颜色*Alpha/255 + 背景颜色*(255-Alpha)/255

由公式可以看出实际上就是前景颜色和背景颜色根据Alpha值按比例叠加。下面是用于实现该叠加算法的一个C#类:

 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4using System.Drawing;
 5using System.Drawing.Imaging;
 6using System.Windows.Forms;
 7
 8namespace Drawing
 9{
10    public class AlphaGraphics
11    {
12        public static void DrawAlphaImage(Bitmap bgIamge, Bitmap fgImage, Rectangle destRect, Rectangle srcRect)
13        {
14            if (bgIamge == null)
15                throw new ArgumentNullException("bgImage");
16
17            if (fgImage == null)
18                throw new ArgumentNullException("fgImage");
19
20            if (destRect.Width <= 0 || destRect.Height <= 0return;
21            if (srcRect.Width <= 0 || srcRect.Height <= 0return;
22            
23            int pixel = 0;
24            for (int i = 0; i < srcRect.Height; i++)
25            {
26                if (i + 1 > destRect.Height) break;
27
28                for (int j = 0; j < srcRect.Width; j++)
29                {
30                    if (j + 1 > destRect.Width) break;
31
32                    int bgR = Convert.ToInt32(((Bitmap)bgIamge).GetPixel(destRect.X + j, destRect.Y + i).R); //red
33                    int bgG = Convert.ToInt32(((Bitmap)bgIamge).GetPixel(destRect.X + j, destRect.Y + i).G); //green
34                    int bgB = Convert.ToInt32(((Bitmap)bgIamge).GetPixel(destRect.X + j, destRect.Y + i).B); //blue
35
36                    int fgB = Convert.ToInt32(fgImage.GetPixel(srcRect.X + j, srcRect.Y + i).B); //blue
37                    int fgG = Convert.ToInt32(fgImage.GetPixel(srcRect.X + j, srcRect.Y + i).G); //green
38                    int fgR = Convert.ToInt32(fgImage.GetPixel(srcRect.X + j, srcRect.Y + i).R); //red
39                    int fgA = Convert.ToInt32(fgImage.GetPixel(srcRect.X + j,srcRect.Y + i).A); //alpha
40
41                    if (fgA == 0// full transparent
42                        ((Bitmap)bgIamge).SetPixel(destRect.X + j, destRect.Y + i, Color.FromArgb(bgR, bgG, bgB));
43                    else if (fgA == 255// full opaque
44                        ((Bitmap)bgIamge).SetPixel(destRect.X + j, destRect.Y + i, Color.FromArgb(fgR, fgG, fgB));
45                    else // semi-transparent
46                    {
47                        
48                        int displayedRed = fgR * fgA / 255 + bgR * (255 - fgA) / 255;
49                        int displayedGreen = fgG * fgA / 255 + bgG * (255 - fgA) / 255;
50                        int displayedBlue = fgB * fgA / 255 + bgB * (255 - fgA) / 255;
51                        ((Bitmap)bgIamge).SetPixel(destRect.X + j, destRect.Y + i, Color.FromArgb(displayedRed, displayedGreen, displayedBlue));
52                    }

53                    
54                    pixel++;
55                }

56            }

57        }


先看传递给DrawAlphaImage方法的参数:bgImage和fgImage分别是前景图像和背景图像,而由srcRect表示的前景图像的的矩形范围将被绘制到背景图像中由destRec所表示的矩形中,这和System.Drawing.Graphics.DrawImage方法中的参数意义一致。

接着便是27-55行代码根据前景图像要叠加的矩形逐一读取前景图像的像素颜色4个分量,以及背景图像在相应位置上的像素颜色R,G,B分量,并且计算出实际显示颜色,填回到背景图像中。这样就完成了图像的叠加显示,是不是很简单?

posted on 2006-08-11 15:31  I.AM.Wright  阅读(3787)  评论(8编辑  收藏  举报

导航