使用GDI+进行开发的一些问题(5)

问题5,透明,半透明和不透明

这是个大题目。在WinForm/WPF里面我们经常会看到一些关于透明的属性,比如Backcolor里面可以选择Transparent, Form里面有一个叫Opacity的属性。都是和透明以及透明度相关的。在其实是在GDI+应用层上的一些东西,在这里我就不讲了。主要从更基本的地方讲起,其中还包括两块完全不同的内容。

1.Alpha

我们在上一讲中提到了PixelFormat,当时我们在LockBits的时候把PixelFormat设定成为Format24bppRgb。但是如果你仔细研究,会发现其实里面有各种各样的图片格式,其中有一种叫做Format32bppArgb。这个意思是说除了RGB,在图像中还存在一个通道,叫做A。这个A就是用来描述当前像素是透明,半透明,还是全透明的分量。这个通道是2个叫Catmull和Smith在上世纪70年代初发明的。通过这个分量,我们可以进行alpha混合的一些计算。从而使表面的图像和背景图像混合,从而造成透明半透明的效果。在这种格式下A作为一个byte,取值可以从0到255,那么0表示图像完全透明,则完全不可见,255则表示图像完全不透明。每个像素都可以实现这种透明或者半透明的效果。更详细解释可以参考http://en.wikipedia.org/wiki/Alpha_compositing ,或者去买本数字图像处理的书回来看。让我们来看看下面这段代码,这个函数可以把图像变成半透明的。

        publicunsafeBitmap GenerateTransParentBitmap(byte alpha)
        {
            FileStream fs = newFileStream(image, FileMode.Open, FileAccess.Read);
            Image img = Image.FromStream(fs, false, false);
            Bitmap bmp = newBitmap(img);
            img.Dispose();
            fs.Close();
 
            int width = bmp.Width;
            int height = bmp.Height;
 
            BitmapData bmData = bmp.LockBits(
                newRectangle(0, 0, width, height),
                ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
 
            byte* p = (byte*)bmData.Scan0;
            int offset = bmData.Stride -width * 4;
            for (int j = 0; j < height; j++)
            {
                for (int i = 0; i < width; i++)
                {
                    p[3] = alpha;
                    p += 4;
                }
                p += offset;
            }
 
            bmp.UnlockBits(bmData);
 
            return bmp;
        }

大家可以注意一下第17,22和23行,由于图像格式不对了,所以我们计算offset和递加的操作都该了,此外由于用小数端存储方式,Alpha通道在p[3]的位置。


顺便提一句,还有一种格式叫做Format32bppPArgb,这叫做premultiplied alpha,就是说在RGB分量里面,alpha分量的数据已经被预先乘进去了。比如说,一个半透明的红色点,在ARGB下,矢量是(255,0,0,128),而在PARGB下就变成了(128,0,0,128)。这是为了不要每次都做乘法。


还有要注意的是,如果你想把这个Bitmap保存成为一个文件,那么必须用png格式,才能够保存alpha通道的信息。如果你存为JPG/BMP/GIF,那么alpha通道的信息将会被丢失。如果存为BMP,那么文件格式将变成Format32bppRgb,其中1个字节不再使用;如果保存为JPEG,那么是Format24bppRgb;存为GIF,格式将变成Format8bppIndexed。根据标准,BMP/JPG本来就不支持透明通道,所以没有可能保留透明信息。GIF倒是支持透明,但是GIF中颜色的信息都是索引,所以Alpha的解释对GIF完全没有效果,接下去我们来分析怎么样使用GIF的透明。

2.GIF

GIF的全称是图像交换格式Graphics Interchange Format,是CompuServe公司在1987年创建并使用的。这种格式使用8位索引值来表达一个像素,也就是说1个像素1个byte,最多可以表示256种颜色。它使用LZW无损压缩算法来对图像进行压缩,之后这家公司又和几家其他的公司发明了PNG文件格式,并被更广泛地应用在Web以及其他领域。GIF支持动画,可以保存数个帧并不断地播放。关于动画的部分我们将会放到非常后面来讲,现在只谈谈GIF的透明。


在GIF文件的头部有一个调色板Palette,里面保存了颜色的信息。一般而言,如果对GIF进行LockBits的操作,只能把它lock成Format*bppIndexed,这样才不会导致前面调色板信息的丢失,在处理上也更方便一些。在调色板里面定义了透明的颜色,也就是说当实际数据为这个颜色时,那个位置的颜色为透明。让我们来看看Palette是怎么使用的。 顺便再说一句,GIF没有半透明,只支持完全透明或者不透明。此外,在一个调色板中,只有一种颜色可以设置为透明,这是GIF标准所决定的。


下面的代码可以转换透明的GIF。

       publicstaticunsafevoid ConvertTransparancyGif(int colorIndex, string baseFile, string outputFile)
       {
            using (FileStream fs = newFileStream(baseFile, FileMode.Open, FileAccess.Read))
            {
                Bitmap img = (Bitmap)Image.FromStream(fs, false, false);
                int width = img.Width;
                int height = img.Height;

                Bitmap resultbmp = newBitmap(width, height, PixelFormat.Format8bppIndexed);
                ColorPalette palette = resultbmp.Palette;
                int n = 0;
                foreach (Color tc in img.Palette.Entries)
                {
                    palette.Entries[n] = Color.FromArgb(255, tc);
                    n++;
                }

                palette.Entries[colorIndex] = Color.FromArgb(0,palette.Entries[colorIndex]);
                resultbmp.Palette = palette;

                //now to copy the actual bitmap data 
                BitmapData src = img.LockBits(
                    newRectangle(0, 0, width, height),
                   ImageLockMode.ReadOnly,
                    img.PixelFormat);

                BitmapData dst = resultbmp.LockBits(
                    newRectangle(0, 0, width, height),
                    ImageLockMode.WriteOnly,
                    resultbmp.PixelFormat);

                byte* pSrc = (byte*)src.Scan0.ToPointer();
                byte* pDst = (byte*)dst.Scan0.ToPointer();
                int offset = src.Stride - width;

                //steps through each pixel 
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        pDst[0] = pSrc[0];
                        pDst++;
                        pSrc++;
                    }
                   pDst += offset;
                   pSrc += offset;
                }

                //unlock the bitmaps 
                img.UnlockBits(src);
                resultbmp.UnlockBits(dst);

                resultbmp.Save(outputFile, ImageFormat.Gif);
                img.Dispose();
                resultbmp.Dispose();
            }
        }

请注意,在这里,我读图的时候和我之前推荐的方法不同。 我没有创建一个新的Bitmap,这是因为在创建新的Bitmap的时候,调色板信息会完全丢失,所以Indexed的格式不可以随意进行复制,否则将造成信息的丢失。这也就是为什么当时我说这是一个土办法的原因。真正的好办法是复制那个流,而不是直接去复制Bitmap。不过那是看需求的。在创建一个带透明颜色的GIF的时候,只要创建一个调色板,就一切OK了。这比Alpha通道修正要简单。还可以参考KB 319061 http://support.microsoft.com/kb/319061/en-us

最后提一句,Bitmap类还提供了一个MakeTransparent方法用于设置透明颜色,不过只对PNG有效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值