在.Net中采用OctreeQuantizer提高Gif文件的画面质量

.Net中采用System.Drawing.Image保存Gif文件画质较低

在.Net中采用System.Drawing.Bitmap(System.Drawing.Image的衍生类)进行绘图操作的时候,如果把最终的绘图结果保存成为Gif类型的文件,会出现画面质量较低的现象。如果Google: bitmap gif save quality会找到很多个关于这方面的讨论,例如一篇Bmp to gif image quality的文章。

从讨论中提到的文章看,Gif画面质量较低是因为调色板造成的。Bitmap的内部调色板缺省是32位色,而Gif调色板最高只有8位色即256色,进行Gif保存的时候,会出现调色板转换,缺省的转换过程会损失很多颜色,造成画面质量较低。

微软也有多篇KB文章论述这个问题,318343 - GDI+ GIF files are saved using the 8-bpp format对Gif调色板进行了说明,319591 - HOW TO: Save a .gif File with a New Color Table By Using Visual Basic .NET给出了一个示例代码显示如何将32位色的Bitmap进行调色板优化,保存为8位色的黑白灰度Gif。

在微软的ASP.Net文档中,有一篇文章Optimizing Color Quantization for ASP.NET Images,提出了Qctree的算法,可以将32位色的Bitmap调色板优化成为8位色的彩色Gif。文章附带一个示例代码

Breadan Tompkins的整理工作

CodeBetter.Com中,Brendan Tompkins对上述微软给出的代码进行整理和归并,于2004年1月写成Use GDI+ to Save Crystal-Clear GIF Images with .NET一文,文中附带完整源代码

在这份代码中,首先定义了基类Quantizer,然后在这个基础上,定义衍生类PaletteQuantizer对于用户制定的调色板进行颜色表转换,PaletteQuantizer再衍生出GrayscaleQuantizer进行黑白的灰度调色板转换;另外,从Quantizer衍生OctreeQuantizer用Octree算法进行颜色转换。

使用OctreeQuantizer的示例C#代码如下。

System.Drawing.Bitmap b = new System.Drawing.Bitmap(“c:\\original_image.gif“);
System.Drawing.Image thmbnail = b.GetThumbnailImage(100,75,null,new IntPtr());
OctreeQuantizer quantizer = new OctreeQuantizer ( 255 , 8 ) ;
using ( Bitmap quantized = quantizer.Quantize ( thmbnail ) )
{
quantized.Save(“c:\\thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif);
}

OctreeQuantizer grayquantizer = new GrayscaleQuantizer ( ) ;
using ( Bitmap quantized = grayquantizer.Quantize ( thmbnail ) )
{
quantized.Save(“c:\\thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif);
}

Unsafe问题的解决

上述代码有个问题,代码中用到了unsafe函数,这样的话,代码所在的Project必须设定为允许Unsafe的格式。代码中Unsafe部分例如:

protected unsafe virtual void FirstPass ( BitmapData sourceData , int width , int height )

Brendan Tompkins在2007年6月又写了Refactoring Unsafe Code: GIF Image Color Quantizer, Now with Safe Goodness一文,将上述代码中所有Unsafe代码都用.Net标准的C#重写了,文中附带完整源代码

黑色水平线的Bug

然而,上述代码存在一个小问题,就是有转换后的图片会有一定几率出现黑色水平线,破坏画面内容。从文中后面的评论可以看到,作者犯了一个小错误。我根据后面跟贴者的提示,找到了这个错误的具体位置,错误在Quantizer.cs的181行,作者写的是:

if (Marshal.ReadByte(pPreviousPixel) != Marshal.ReadByte(pSourcePixel))

这行代码的作用是比较Bitmap中当前像素和上一个像素是否相同,但是仅仅比较一个字节是不够的,需要比较一个整数的宽度。这行代码修改成为下面代码后,问题解决。我遂即也把错误的具体位置在上述文章后面做了跟贴。

if (Marshal.ReadInt32(pPreviousPixel) != Marshal.ReadInt32(pSourcePixel))

VB.Net的小例子

下面附上从VB.Net调用OctreeQuantizer的示例代码。代码中,dstImg是原先的32位色Bitmap,dstStm是一个目标Stream。

Dim qtz As OctreeQuantizer = New OctreeQuantizer(255, 8)
Dim gifImg As Bitmap = qtz.Quantize(dstImg)
gifImg.Save(dstStm, srcImg.RawFormat)
gifImg.Dispose()

如果要在保存之前进行图片的大小调整,参见.Net中如何将Gif、Jpeg等图片缩小为恰当宽度的图片

作者: 杰棍 [Jegwon]

波波坡原创文章 链接:http://www.bobopo.com/article/code/octreequantizer.htm

标签:

关键词: OctreeQuantizer, .Net Framework, DotNet, Gif, 图像质量, 高质量Gif, Bitmap, Bitmap.Save, Octree, 源代码, 黑色水平线, Quantizer, Unsafe, 示例

创建日期: 2008-02-26

文库 微博 博客 作品 首页