65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (10投票s)

2006 年 10 月 9 日

CPOL

3分钟阅读

viewsIcon

79086

downloadIcon

5909

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 日 - 少量更新
© . All rights reserved.