[原创] 在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#类:
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 <= 0) return;
21 if (srcRect.Width <= 0 || srcRect.Height <= 0) return;
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) 编辑 收藏 举报