C#中保存GIF文件后透明背景问题的一个解决方法

以前在用C#做网站保存缩略图的程序中发现,当保存为GIF文件类型时,原来的透明背景变成了黑色,当时由于赶时间,就统一用白色代替了背景,并用Jpeg格式存储,并没有深究。

  近来在网上查阅了许多资料,看到了两种解决方法:一种是在显示时设置透明背景色,GIF文件本身并不改变,另一种是不推荐使用的调用API的方法。将后一种I的VB源码用C#重写后,发现其中的调色板设置太少,转换效果不理想。

  重新到网上搜索关于调色板的资料,发现MSDN上的一篇《对 ASP.NET 图像的颜色量化(Quantization)进行优化》的文章。

http://www.microsoft.com/china/MSDN/library/archives/library/DNAspp/html/colorquant.asp

  将其中基于调色板量化的代码分离出来,透明背景色的图片保存成功。

  完整代码如下:

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace WindwoodGif
{
    
public class GifPalette
    
{
        
private static ArrayList _cardPalette;
        
private Color[] _colors;
        
private Hashtable _colorMap;

        
public GifPalette(ArrayList palette)
        
{
            _colorMap 
= new Hashtable();
            _colors 
= new Color[palette.Count];
            palette.CopyTo(_colors);
        }


        
public GifPalette()
        
{
            ArrayList palette 
= SetPalette();
            _colorMap 
= new Hashtable();
            _colors 
= new Color[palette.Count];
            palette.CopyTo(_colors);
        }


        
public Bitmap Quantize(Image source)
        
{
            
int height = source.Height;
            
int width = source.Width;

            Rectangle bounds 
= new Rectangle(00, width, height);

            Bitmap copy 
= new Bitmap(width, height, PixelFormat.Format32bppArgb);
            Bitmap output 
= new Bitmap(width, height, PixelFormat.Format8bppIndexed);

            
using (Graphics g = Graphics.FromImage(copy))
            
{
                g.PageUnit 
= GraphicsUnit.Pixel;

                g.DrawImageUnscaled(source, bounds);
            }


            BitmapData sourceData 
= null;

            
try
            
{
                sourceData 
= copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

                output.Palette 
= this.GetPalette(output.Palette);

                SecondPass(sourceData, output, width, height, bounds);
            }

            
finally
            
{
                copy.UnlockBits(sourceData);
            }


            
return output;
        }


        
private ColorPalette GetPalette(ColorPalette palette)
        
{
            
for (int index = 0; index < _colors.Length; index++)
                palette.Entries[index] 
= _colors[index];
            
return palette;
        }


        
private unsafe void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
        
{
            BitmapData outputData 
= null;

            
try
            
{
                outputData 
= output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);

                
byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();
                Int32
* pSourcePixel = (Int32*)pSourceRow;
                Int32
* pPreviousPixel = pSourcePixel;

                
byte* pDestinationRow = (byte*)outputData.Scan0.ToPointer();
                
byte* pDestinationPixel = pDestinationRow;

                
byte pixelValue = QuantizePixel((Color32*)pSourcePixel);

                
*pDestinationPixel = pixelValue;

                
for (int row = 0; row < height; row++)
                
{
                    pSourcePixel 
= (Int32*)pSourceRow;

                    pDestinationPixel 
= pDestinationRow;

                    
for (int col = 0; col < width; col++, pSourcePixel++, pDestinationPixel++)
                    
{
                        
if (*pPreviousPixel != *pSourcePixel)
                        
{
                            pixelValue 
= QuantizePixel((Color32*)pSourcePixel);

                            pPreviousPixel 
= pSourcePixel;
                        }


                        
*pDestinationPixel = pixelValue;
                    }


                    pSourceRow 
+= sourceData.Stride;

                    pDestinationRow 
+= outputData.Stride;
                }

            }

            
finally
            
{
                output.UnlockBits(outputData);
            }

        }


        
private unsafe byte QuantizePixel(Color32* pixel)
        
{
            
byte colorIndex = 0;
            
int colorHash = pixel->ARGB;

            
if (_colorMap.ContainsKey(colorHash))
                colorIndex 
= (byte)_colorMap[colorHash];
            
else
            
{
                
if (0 == pixel->Alpha)
                
{
                    
for (int index = 0; index < _colors.Length; index++)
                    
{
                        
if (0 == _colors[index].A)
                        
{
                            colorIndex 
= (byte)index;
                            
break;
                        }

                    }

                }

                
else
                
{
                    
int leastDistance = int.MaxValue;
                    
int red = pixel->Red;
                    
int green = pixel->Green;
                    
int blue = pixel->Blue;

                    
for (int index = 0; index < _colors.Length; index++)
                    
{
                        Color paletteColor 
= _colors[index];

                        
int redDistance = paletteColor.R - red;
                        
int greenDistance = paletteColor.G - green;
                        
int blueDistance = paletteColor.B - blue;

                        
int distance = (redDistance * redDistance) +
                                           (greenDistance 
* greenDistance) +
                                           (blueDistance 
* blueDistance);

                        
if (distance < leastDistance)
                        
{
                            colorIndex 
= (byte)index;
                            leastDistance 
= distance;

                            
if (0 == distance)
                                
break;
                        }

                    }

                }


                _colorMap.Add(colorHash, colorIndex);
            }


            
return colorIndex;
        }


        [StructLayout(LayoutKind.Explicit)]
        
public struct Color32
        
{
            [FieldOffset(
0)]
            
public byte Blue;

            [FieldOffset(
1)]
            
public byte Green;

            [FieldOffset(
2)]
            
public byte Red;

            [FieldOffset(
3)]
            
public byte Alpha;

            [FieldOffset(
0)]
            
public int ARGB;

            
public Color Color
            
{
                
get return Color.FromArgb(Alpha, Red, Green, Blue); }
            }

        }


        
public static ArrayList SetPalette()
        
{
            
if (null == _cardPalette)
            
{
                _cardPalette 
= new ArrayList();

                
Insert the colors into the arraylist
            }

            
return _cardPalette;
        }


    }

}

  调用方法:

  先将该类添加到项目中,再在合适的地方调用。例:

        Bitmap bitmap = new System.Drawing.Bitmap(width, height);     // Image类也可
        // ......(图形操作代码)
        WindwoodGif.GifPalette gifPalette = new WindwoodGif.GifPalette();
        bitmap = gifPalette.Quantize(bitmap);
        bitmap.Save(SaveFileName, ImageFormat.Gif);

  经测试,这种方法能够实现GIF文件的透明背景存储,在WinForm、WebForm均能使用。由于使用了标准256色调色板,内存开销可能较大,转换时间相对较慢,图像质量也有一定影响。此外,代码中使用了非安全代码(指针),在编译时项目属性中要设置允许不安全代码。

posted @ 2008-01-04 11:18  K!ngZ  阅读(1743)  评论(4编辑  收藏  举报