使用内存中图像压缩技术的可压缩位图






4.77/5 (10投票s)
System.Drawing.Bitmap 的一个包装器,它在内存使用和加载时间方面提供了一个折衷的解决方案。
引言
System.Drawing.Bitmap
确实为我们创建、保存和处理多种类型的图片提供了一种方便的方法。然而,这个类有一个问题,可能并非所有程序员都注意到——它会占用大量的内存资源。对于大小为 1280 x 1024 的非索引 RGB 图片,它将占用 1280 * 1024 * 3 = 3,932,160 = 约 4 MB 的内存。不算太可怕,因为你的工作站上有 1 GB 的内存? 如果你必须同时将一千张这种大小的图片加载到内存中呢? 它会耗尽你所有的物理内存加上虚拟内存,然后仍然抱怨你没有足够的内存。 这不是一个虚构的场景,因为我在编写我的应用程序时遇到了完全相同的问题。 我相信有人会问:“你为什么要将这么多图片加载到内存中? 你总是可以在需要时从磁盘加载它们。” 不幸的是,这行不通,因为从磁盘加载对于我的应用程序来说代价太高了。 所以我想出了这个折衷的解决方案,它比从磁盘加载花费更少的加载时间,并且比存储纯 BITMAP(用大写 BITMAP,我指的是在内存中存储 RGB 值的那个)消耗更少的内存。
背景
这背后的概念非常简单。 我获取该 BITMAP,使用某种压缩方法将其转储到流中,并将该流保存在内存中。 只要使用 BITMAP,我就获取该流并从中加载图片。 众所周知,System.Drawing.Bitmap
提供了一堆非常可爱的名为 Save(...)
的方法。 我们可以使用其中的一个 Save(Stream, ImageFormat)
将我们的 BITMAP 序列化到流中,压缩方法由 ImageFormat
参数控制。
Using the Code
该解决方案被编码到一个名为 CompressibleImage
的类中,该类具有两个构造函数、两个重要方法和一个属性。
构造函数 | CompressibleImage(Bitmap, ImageFormat) |
给定原始图像和压缩格式,创建一个 CompressibleImage 实例。 |
CompressibleImage(MemoryStream) |
给定包含压缩图像的流,创建一个 CompressibleImage 实例。 |
|
方法 | GetDecompressedImage() |
获取未压缩的图像。 如果图像已压缩,它将首先被解压缩。 |
ClearDecompressedImage() |
清除未压缩的图像,在内存中留下压缩的图像。 | |
属性 | IsCompressed |
获取一个值,指示图像是否已压缩 |
两个最重要的函数是
/// <summary>
/// Gets the uncompressed image. If the image is compressed,
/// it will be uncompressed first.
/// </summary>
public Image GetDecompressedImage()
{
if (decompressed == null)
{
stream.Seek(0, SeekOrigin.Begin);
decompressed = new Bitmap(stream);
}
return decompressed;
}
/// <summary>
/// Clears the uncompressed image, leaving the compressed one in memory.
/// </summary>
public void ClearDecompressedImage()
{
if (decompressed != null)
{
if (stream == null)
{
stream = new MemoryStream();
decompressed.Save(stream, format);
}
decompressed = null;
}
}
以下代码段展示了如何使用这个类
void Usage1(Bitmap b)
{
CompressibleImage ci = new CompressibleImage(b, ImageFormat.Jpeg);
//...
Image decompressed = ci.GetDecompressedImage();
//use the decompressed image
ci.ClearDecompressedImage();
// make it compressed and use later
}
void Usage2(Bitmap b)
{
MemoryStream ms = new MemoryStream();
b.Save(ms, ImageFormat.Jpeg);
CompressibleImage ci = new CompressibleImage(ms);
// keep it compressed and use later
}
关注点
请注意,如果给定 Bitmap
实例和 ImageFormat.Jpeg
格式创建 CompressibleImage
对象,则图像质量会受到影响。 但是,如果给定 MemoryStream
实例创建它,则 CompressibleImage
的质量将与 MemoryStream
中包含的内容保持相同。 如果必须进行无损压缩,你应该使用正确的 ImageFormat
或更好地控制传入的 MemoryStream
。
演示项目显示了 CompressibleImage
类的性能。 请注意,两种场景的最终加载时间或多或少相同。 它们是由于操作系统级别或硬件级别缓存造成的。 对于非常大的文件或大量文件,性能会有所不同。
历史
- 2006 年 10 月 9 日 - 初稿
- 2007 年 3 月 13 日 - 少量更新