[转]C#中保存GIF文件后透明背景问题的一个解决方法
原文出处
http://blog.csdn.net/ncjmc/archive/2006/08/28/1132879.aspx
以前在用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
将其中基于调色板量化的代码分离出来,透明背景色的图片保存成功。
完整代码如下:
1
public class GifPalette
2
{
3
private static ArrayList _cardPalette;
4
private Color[] _colors;
5
private Hashtable _colorMap;
6![]()
7
public GifPalette(ArrayList palette)
8
{
9
_colorMap = new Hashtable();
10
_colors = new Color[palette.Count];
11
palette.CopyTo(_colors);
12
}
13![]()
14
public GifPalette()
15
{
16
ArrayList palette = SetPalette();
17
_colorMap = new Hashtable();
18
_colors = new Color[palette.Count];
19
palette.CopyTo(_colors);
20
}
21![]()
22
public Bitmap Quantize(Image source)
23
{
24
int height = source.Height;
25
int width = source.Width;
26![]()
27
Rectangle bounds = new Rectangle(0, 0, width, height);
28![]()
29
Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
30
Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
31![]()
32
using (Graphics g = Graphics.FromImage(copy))
33
{
34
g.PageUnit = GraphicsUnit.Pixel;
35![]()
36
g.DrawImageUnscaled(source, bounds);
37
}
38![]()
39
BitmapData sourceData = null;
40![]()
41
try
42
{
43
sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
44![]()
45
output.Palette = this.GetPalette(output.Palette);
46![]()
47
SecondPass(sourceData, output, width, height, bounds);
48
}
49
finally
50
{
51
copy.UnlockBits(sourceData);
52
}
53![]()
54
return output;
55
}
56![]()
57
private ColorPalette GetPalette(ColorPalette palette)
58
{
59
for (int index = 0; index < _colors.Length; index++)
60
palette.Entries[index] = _colors[index];
61
return palette;
62
}
63![]()
64
private unsafe void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
65
{
66
BitmapData outputData = null;
67![]()
68
try
69
{
70
outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
71![]()
72
byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();
73
Int32* pSourcePixel = (Int32*)pSourceRow;
74
Int32* pPreviousPixel = pSourcePixel;
75![]()
76
byte* pDestinationRow = (byte*)outputData.Scan0.ToPointer();
77
byte* pDestinationPixel = pDestinationRow;
78![]()
79
byte pixelValue = QuantizePixel((Color32*)pSourcePixel);
80![]()
81
*pDestinationPixel = pixelValue;
82![]()
83
for (int row = 0; row < height; row++)
84
{
85
pSourcePixel = (Int32*)pSourceRow;
86![]()
87
pDestinationPixel = pDestinationRow;
88![]()
89
for (int col = 0; col < width; col++, pSourcePixel++, pDestinationPixel++)
90
{
91
if (*pPreviousPixel != *pSourcePixel)
92
{
93
pixelValue = QuantizePixel((Color32*)pSourcePixel);
94![]()
95
pPreviousPixel = pSourcePixel;
96
}
97![]()
98
*pDestinationPixel = pixelValue;
99
}
100![]()
101
pSourceRow += sourceData.Stride;
102![]()
103
pDestinationRow += outputData.Stride;
104
}
105
}
106
finally
107
{
108
output.UnlockBits(outputData);
109
}
110
}
111![]()
112
private unsafe byte QuantizePixel(Color32* pixel)
113
{
114
byte colorIndex = 0;
115
int colorHash = pixel->ARGB;
116![]()
117
if (_colorMap.ContainsKey(colorHash))
118
colorIndex = (byte)_colorMap[colorHash];
119
else
120
{
121
if (0 == pixel->Alpha)
122
{
123
for (int index = 0; index < _colors.Length; index++)
124
{
125
if (0 == _colors[index].A)
126
{
127
colorIndex = (byte)index;
128
break;
129
}
130
}
131
}
132
else
133
{
134
int leastDistance = int.MaxValue;
135
int red = pixel->Red;
136
int green = pixel->Green;
137
int blue = pixel->Blue;
138![]()
139
for (int index = 0; index < _colors.Length; index++)
140
{
141
Color paletteColor = _colors[index];
142![]()
143
int redDistance = paletteColor.R - red;
144
int greenDistance = paletteColor.G - green;
145
int blueDistance = paletteColor.B - blue;
146![]()
147
int distance = (redDistance * redDistance) +
148
(greenDistance * greenDistance) +
149
(blueDistance * blueDistance);
150![]()
151
if (distance < leastDistance)
152
{
153
colorIndex = (byte)index;
154
leastDistance = distance;
155![]()
156
if (0 == distance)
157
break;
158
}
159
}
160
}
161![]()
162
_colorMap.Add(colorHash, colorIndex);
163
}
164![]()
165
return colorIndex;
166
}
167![]()
168
[StructLayout(LayoutKind.Explicit)]
169
public struct Color32
170
{
171
[FieldOffset(0)]
172
public byte Blue;
173![]()
174
[FieldOffset(1)]
175
public byte Green;
176![]()
177
[FieldOffset(2)]
178
public byte Red;
179![]()
180
[FieldOffset(3)]
181
public byte Alpha;
182![]()
183
[FieldOffset(0)]
184
public int ARGB;
185![]()
186
public Color Color
187
{
188
get { return Color.FromArgb(Alpha, Red, Green, Blue); }
189
}
190
}
191![]()
192
public static ArrayList SetPalette()
193
{
194
if (null == _cardPalette)
195
{
196
_cardPalette = new ArrayList();
197![]()
198
//Insert the colors into the arraylist
199
Insert the colors into the arraylist
457
}
458
return _cardPalette;
459
}
460![]()
461![]()
462
}
public class GifPalette2
{3
private static ArrayList _cardPalette;4
private Color[] _colors;5
private Hashtable _colorMap;6

7
public GifPalette(ArrayList palette)8
{9
_colorMap = new Hashtable();10
_colors = new Color[palette.Count];11
palette.CopyTo(_colors);12
}13

14
public GifPalette()15
{16
ArrayList palette = SetPalette();17
_colorMap = new Hashtable();18
_colors = new Color[palette.Count];19
palette.CopyTo(_colors);20
}21

22
public Bitmap Quantize(Image source)23
{24
int height = source.Height;25
int width = source.Width;26

27
Rectangle bounds = new Rectangle(0, 0, width, height);28

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

32
using (Graphics g = Graphics.FromImage(copy))33
{34
g.PageUnit = GraphicsUnit.Pixel;35

36
g.DrawImageUnscaled(source, bounds);37
}38

39
BitmapData sourceData = null;40

41
try42
{43
sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);44

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

47
SecondPass(sourceData, output, width, height, bounds);48
}49
finally50
{51
copy.UnlockBits(sourceData);52
}53

54
return output;55
}56

57
private ColorPalette GetPalette(ColorPalette palette)58
{59
for (int index = 0; index < _colors.Length; index++)60
palette.Entries[index] = _colors[index];61
return palette;62
}63

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

68
try69
{70
outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);71

72
byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();73
Int32* pSourcePixel = (Int32*)pSourceRow;74
Int32* pPreviousPixel = pSourcePixel;75

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

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

81
*pDestinationPixel = pixelValue;82

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

87
pDestinationPixel = pDestinationRow;88

89
for (int col = 0; col < width; col++, pSourcePixel++, pDestinationPixel++)90
{91
if (*pPreviousPixel != *pSourcePixel)92
{93
pixelValue = QuantizePixel((Color32*)pSourcePixel);94

95
pPreviousPixel = pSourcePixel;96
}97

98
*pDestinationPixel = pixelValue;99
}100

101
pSourceRow += sourceData.Stride;102

103
pDestinationRow += outputData.Stride;104
}105
}106
finally107
{108
output.UnlockBits(outputData);109
}110
}111

112
private unsafe byte QuantizePixel(Color32* pixel)113
{114
byte colorIndex = 0;115
int colorHash = pixel->ARGB;116

117
if (_colorMap.ContainsKey(colorHash))118
colorIndex = (byte)_colorMap[colorHash];119
else120
{121
if (0 == pixel->Alpha)122
{123
for (int index = 0; index < _colors.Length; index++)124
{125
if (0 == _colors[index].A)126
{127
colorIndex = (byte)index;128
break;129
}130
}131
}132
else133
{134
int leastDistance = int.MaxValue;135
int red = pixel->Red;136
int green = pixel->Green;137
int blue = pixel->Blue;138

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

143
int redDistance = paletteColor.R - red;144
int greenDistance = paletteColor.G - green;145
int blueDistance = paletteColor.B - blue;146

147
int distance = (redDistance * redDistance) +148
(greenDistance * greenDistance) +149
(blueDistance * blueDistance);150

151
if (distance < leastDistance)152
{153
colorIndex = (byte)index;154
leastDistance = distance;155

156
if (0 == distance)157
break;158
}159
}160
}161

162
_colorMap.Add(colorHash, colorIndex);163
}164

165
return colorIndex;166
}167

168
[StructLayout(LayoutKind.Explicit)]169
public struct Color32170
{171
[FieldOffset(0)]172
public byte Blue;173

174
[FieldOffset(1)]175
public byte Green;176

177
[FieldOffset(2)]178
public byte Red;179

180
[FieldOffset(3)]181
public byte Alpha;182

183
[FieldOffset(0)]184
public int ARGB;185

186
public Color Color187
{188
get { return Color.FromArgb(Alpha, Red, Green, Blue); }189
}190
}191

192
public static ArrayList SetPalette()193
{194
if (null == _cardPalette)195
{196
_cardPalette = new ArrayList();197

198
//Insert the colors into the arraylist199
Insert the colors into the arraylist457
}458
return _cardPalette;459
}460

461

462
}调用方法:
先将该类添加到项目中,再在合适的地方调用。例:
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号