nelsonhoover
nelsonhoover

Reputation: 11

Bitmap.Clone just throws "Out of memory" error. Are there alternatives?

I need to convert a Drawing.Bitmap to 4 bit grayscale. Is there any way to accomplish this? I've tried using Bitmap.Clone but I only get the usual infamous "Out of memory" exception. Would this be grayscale even if it managed to convert to 4 bit?

Any tips would be greatly appreciated.

Thanks,

Nelson H

Upvotes: 1

Views: 3226

Answers (3)

Ry-
Ry-

Reputation: 224904

Assumes Imports System.Runtime.InteropServices, System.Drawing.Imaging at the top of the code file. (LockBits isn't that hard, I do a lot of image processing with it and much prefer VB.NET over 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

This method is not fast, of course (it takes about 1-2 seconds for an 8 megapixel image for me) but it's not bad.

Upvotes: 0

Akash Kava
Akash Kava

Reputation: 39916

Out of memory exception occurs if you are trying to accomplish image processing on ASP.NET web site since IIS application pool limits the amount of memory allocated. So we created a separate windows service which works independent of IIS, and does the conversion. And in IIS website we just trigger the service in the form of either WCF or named pipes.

Upvotes: 0

Hans Passant
Hans Passant

Reputation: 941455

There is no 4bpp grayscale image format. Next best is 4bppIndexed with a palette that contains 16 colors of gray. GDI+ has very poor support for this format, the only way to set pixels is to write them directly with Bitmap.LockBits(). This is quite hard to do in VB.NET, C# is much preferred to manipulate the bitmap data with a pointer. Like this:

    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);
    }

A sample image converted with this code:

enter image description here

It is not particularly fast and the color palette could use some gamma correction to avoid generating images that are too dark.

Upvotes: 2

Related Questions