【问题标题】:How to avoid bitmap out of memory when working on very large image for ie: 10.000.000 pixel and above在处理非常大的图像时如何避免位图内存不足,即:10.000.000 像素及以上
【发布时间】:2015-06-15 05:08:48
【问题描述】:

目前我正在开发一个加载非常大图像的系统,最小宽度 x 高度 >= 10.000.000 像素。

但是用户上传图片的比例通常不符合我们要求的比例,所以我必须将它裁剪到适当的比例,但是当使用 System.Drawing 位图进行裁剪时,我总是遇到 SytemOutOfMemory 异常。

我用正确的 RectangleF 尝试了 Bitmap.Clone 和 Graphic.DrawImage,但没有运气。

是否有办法在不出现内存不足异常的情况下执行此操作,或者是否有任何替代 System.Drawing 库的方法可以轻松完成此任务?

我从用户上传文件加载图像的代码:

    var fileBinary = new byte[stream.Length];
    stream.Read(fileBinary, 0, fileBinary.Length);
    stream.Position = 0;
    var fileExtension = Path.GetExtension(fileName);
    using (Image image = Image.FromStream(stream, false, false))
    {
        //validation and check ratio
        CropImage(image, PORTRAIT_RATIO, fileExtension);
     }

还有 CropImage 函数:

//Crop Image from center with predefine ratio
    private byte[] CropImage(Image sourceImg, float ratio, string fileExtension)
        var height = sourceImg.Height;
        var width = sourceImg.Width;

        var isPortrait = width < height;
        RectangleF croppingRec = new RectangleF();

        float positionX = 0;
        float positionY = 0;
        float cropHeight = (float)height;
        float cropWidth = cropHeight * PORTRAIT_RATIO;
        positionY = 0;
        positionX = (width - cropWidth) / 2;

        if (cropWidth > width)
        {
            cropWidth = width;
            cropHeight = cropWidth * (1 / PORTRAIT_RATIO);
            positionX = 0;
            positionY = ((height - cropHeight) / 2);

        }

        croppingRec.Width = cropWidth;
        croppingRec.Height = cropHeight;
        croppingRec.X = positionX;
        croppingRec.Y = positionY;

        Bitmap bmpImage = sourceImg as Bitmap;
        Bitmap bmpCrop = bmpImage.Clone(croppingRec, bmpImage.PixelFormat);
        bmpCrop.Save("D:/test" + fileExtension, ImageFormat.Jpeg);

        ImageConverter converter = new ImageConverter();

        return (byte[])converter.ConvertTo(bmpCrop, typeof(byte[]));
    }

}

【问题讨论】:

  • 您是否尝试编译为带有特殊标志的 64bit 应用程序,该标志消除了 clr 的内存限制(单个 List 集合的最大 2GB 除外)?
  • 1000 万像素通常不是问题,十亿是理论上的最大值,但在 32 位进程中,您往往会在此之前跌倒。如果您的程序已经运行了一段时间,通常约为 2500 万。您的代码中最有可能出现的错误是忘记调用 Dispose(),当您使用 Bitmap 类时,这是一个非常严格的要求,并且太多的程序员忘记了它。如果您不发布代码,任何人都无法看到您的错误。
  • 既然您提到了上传,可能值得您注意System.Drawing namespace 上的警告:“不支持在 Windows 或 ASP 中使用 System.Drawing 命名空间中的类.NET 服务。”
  • 从代码中可以明显看出,bmpCrop 永远不会被释放。转换为 byte[] 后必须这样做。
  • 究竟在哪里抛出异常?

标签: c# bitmap system.drawing


【解决方案1】:

您可以将位图转换为字节数组。尝试这样的事情(看起来很老套,但我不知道其他方法):

int pixelSize = 3;
int bytesCount = imgHeight * imgWidth * pixelSize;
byte[] byteArray= new byte[bytesCount];

BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, imgWidth, imgHeight), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bitmapData.Scan0, byteArray, 0, bytesCount);

此数组中的每个像素由 3 个字节表示(这取决于位图类型)。所以你知道位图线的长度是 3 * imgWidth。使用它,您可以简单地在字节数组中导航并将您需要的内容复制到新数组中。

然后,您将创建一个具有所需最终大小的新位图,获取位图数据和 Marshal。将新数组复制到其中:

Bitmap newBitmap = new Bitmap(Width, Height);
BitmapData newBitmapData = b.LockBits(BoundsRect,
                                ImageLockMode.WriteOnly,
                                newBitmap.PixelFormat);
Marshal.Copy(newByteArray, 0, newBitmapData.Scan0, newBytesCount);

最后解锁位图:

newBitmap.UnlockBits(newBitmapData );
bitmap.UnlockBits(bitmapData);

希望这会有所帮助。干杯。

【讨论】:

    【解决方案2】:

    尝试使用 graphicsmagick.net 库,然后在 MagickImage 上使用 Crop 方法。它应该在 asp.net 下仍然可以正常工作,并且可以使用磁盘临时处理巨大的图像文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-02
      • 2011-04-13
      • 1970-01-01
      • 1970-01-01
      • 2020-06-09
      • 2014-02-19
      相关资源
      最近更新 更多