C#中保存GIF文件后透明背景问题的一个解决方法
以前在用C#做网站保存缩略图的程序中发现,当保存为GIF文件类型时,原来的透明背景变成了黑色,当时由于赶时间,就统一用白色代替了背景,并用Jpeg格式存储,并没有深究。
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(0, 0, 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;
}
![]()
}
}
近来在网上查阅了许多资料,看到了两种解决方法:一种是在显示时设置透明背景色,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(0, 0, 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色调色板,内存开销可能较大,转换时间相对较慢,图像质量也有一定影响。此外,代码中使用了非安全代码(指针),在编译时项目属性中要设置允许不安全代码。


浙公网安备 33010602011771号