Reputation: 11
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
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
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
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:
It is not particularly fast and the color palette could use some gamma correction to avoid generating images that are too dark.
Upvotes: 2