Bitmap.Clone只是抛出“Out of memory”错误。还有替代品吗?

时间:2011-04-17 02:23:19

标签: .net vb.net bitmap out-of-memory system.drawing

我需要将Drawing.Bitmap转换为4位灰度。有没有办法实现这个目标?我尝试过使用Bitmap.Clone,但我只得到通常臭名昭着的“Out of memory”异常。即使它设法转换为4位,这会是灰度吗?

任何提示都将不胜感激。

谢谢,

Nelson H

3 个答案:

答案 0 :(得分:2)

没有4bpp灰度图像格式。接下来最好的是4bppIndexed,其中包含16种颜色的灰色调色板。 GDI +对这种格式的支持非常差,设置像素的唯一方法是使用Bitmap.LockBits()直接编写它们。这在VB.NET中很难做到,C#更倾向于用指针操作位图数据。像这样:

    public unsafe static void Save4bppGrayscale(Bitmap src, string path) {
        var bmp = new Bitmap(src.Width, src.Height, System.Drawing.Imaging.PixelFormat.Format4bppIndexed);

        // Create gray-scale palette
        var pal = bmp.Palette;
        for (int ix = 0; ix < 16; ++ix) {
            var c = 255 * ix / 15;
            pal.Entries[ix] = Color.FromArgb(c, c, c);
        }
        bmp.Palette = pal;

        // Map pixels
        var data = bmp.LockBits(new Rectangle(0, 0, src.Width, src.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format4bppIndexed);
        for (int y = 0; y < src.Height; ++y) {
            byte* line = (byte*)(IntPtr)((long)data.Scan0 + y * data.Stride);
            for (int x = 0; x < src.Width; ++x) {
                var pix = src.GetPixel(x, y);
                var c = (int)(15 * pix.GetBrightness());
                if (x % 2 == 1) c <<= 4;
                *(line + x / 2) |= (byte)c;
            }
        }
        bmp.UnlockBits(data);

        bmp.Save(path, System.Drawing.Imaging.ImageFormat.Bmp);
    }

使用此代码转换的示例图像:

enter image description here

它不是特别快,调色板可以使用一些伽玛校正来避免生成太暗的图像。

答案 1 :(得分:0)

如果您尝试在ASP.NET网站上完成图像处理,则会发生内存不足异常,因为IIS应用程序池会限制分配的内存量。因此,我们创建了一个独立于IIS的独立Windows服务,并进行转换。在IIS网站中,我们只是以WCF或命名管道的形式触发服务。

答案 2 :(得分:0)

假设Imports System.Runtime.InteropServices, System.Drawing.Imaging位于代码文件的顶部。 (LockBits并不难,我用它做了很多图像处理,而且更喜欢VB.NET而不是C#。)

Private Sub To4BitGrayScale(ByVal b As Bitmap)
    Dim bd As BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, ImageFormat.Format24BppRgb)
    Dim arr(bd.Width * bd.Height * 3 - 1) As Byte
    Marshal.Copy(bd.Scan0, arr, 0, arr.Length)

    For i As Integer = 0 To arr.Length - 1 Step 3
        Dim c As Color = Color.FromArgb(255, arr(i), arr(i + 1), arr(i + 2))

        ' Convert c to grayscale however you want; weighted, average, whatever.

        arr(i) = c.R
        arr(i + 1) = c.G
        arr(i + 2) = c.B
    Next

    Marshal.Copy(arr, 0, bd.Scan0, arr.Length)
    b.UnlockBits(bd)
End Sub

这种方法当然不是很快(对我而言,800万像素的图像大约需要1-2秒),但这并不错。