1

I need to create an image in memory (can be huge image!) and to extract from it byte array in the size of width x height. Each byte must have value of 0-255 (256 gray scale values: 0 for white and 255 for black). The part of creating the image is easy, here is a simple example of my code:

img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
drawing.Clear(Color.Black);// paint the background
drawing.DrawString(text, font, Brushes.White, 0, 0);

Problem is to convert it to "my" special gray scale byte array. When I'm using any pixel format other then Format8bppIndexed, the byte array I'm getting from the bitmap is not in the size I need (width*length) so I need a conversion that takes too much time. When I'm using Format8bppIndexed I'm getting the byte array very fast and in the right size, but each byte/pixel is 0-15.

Changing the bitmap palette has no affect:

var pal = img.Palette;
for (int i = 1; i < 256; i++){
   pal.Entries[i] = Color.FromArgb(255, 255, 255);
}
img.Palette = pal;

Any idea how to do it?

Edit: Full code:

// assume font can be Times New Roman, size 7500!
static private Bitmap DrawText(String text, Font font)
{
    //first, create a dummy bitmap just to get a graphics object
    var img = new Bitmap(1, 1);
    var drawing = Graphics.FromImage(img);

    //measure the string to see how big the image needs to be
    var textSize = drawing.MeasureString(text, font);

    //free up the dummy image and old graphics object
    img.Dispose();
    drawing.Dispose();

    //create a new image of the right size (must be multiple of 4)
    int width = (int) (textSize.Width/4) * 4;
    int height = (int)(textSize.Height / 4) * 4;
    img = new Bitmap(width, height);

    drawing = Graphics.FromImage(img);

    // paint the background
    drawing.Clear(Color.Black);

    drawing.DrawString(text, font, Brushes.White, 0, 0);

    var bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),    ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);

    var newBitmap = new Bitmap(width, height, bmpData.Stride, PixelFormat.Format8bppIndexed, bmpData.Scan0);

    drawing.Dispose();

    return newBitmap;
}

private static byte[] GetGrayscleBytesFastest(Bitmap bitmap)
{
    BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
    int numbytes = bmpdata.Stride * bitmap.Height;
    byte[] bytedata = new byte[numbytes];
    IntPtr ptr = bmpdata.Scan0;

    Marshal.Copy(ptr, bytedata, 0, numbytes);

    bitmap.UnlockBits(bmpdata);

    return bytedata;
}
Yariv
  • 11
  • 1
  • 4
  • Is this c# / .NET? Please tag it up – geedubb Aug 06 '14 at 16:19
  • 1
    A few notes: Do you aim at an image or a raw byte array? Your create a palette code should be `pal.Entries[i] = Color.FromArgb(i, i, i);` going from 0-255. In the rest of the world 0=black and 255=white. Can you show us the real code for creating a Format8bppIndexed image? Have you looked at lockbits? Necessary to make things fast. – TaW Aug 06 '14 at 16:49
  • I added the full code. This is the fastest way I have found, with only problem of getting 0-15 values per pixel. – Yariv Aug 07 '14 at 10:59
  • BTW, I tried the pal.Entries[i] = Color.FromArgb(i, i, i) before calling to my GetGrayscleBytesFastest. It had no affect (still 0-15 values) – Yariv Aug 07 '14 at 18:42

2 Answers2

1

You probably want to do this in two steps. First, create a 16bpp grayscale copy of your original image as described in Convert an image to grayscale.

Then, create your 8bpp image with the appropriate color table and draw the 16bpp grayscale image onto that image. That will do the conversion for you, converting the 16-bit grayscale values to your 256 different colors.

You should then have an 8bpp image with your 256 different shades of gray. You can then call LockBits to get access to the bitmap bits, which will be index values in the range 0 to 255.

Community
  • 1
  • 1
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • With this solution I need to go over all the pixels. I checked, it can take up to 25 seconds when the bitmap is 75cm x 50cm (I need to support such size). – Yariv Aug 07 '14 at 10:58
  • 2.5 seconds, my bad. From my log: – Yariv Aug 07 '14 at 12:13
  • Converting to grayscale fastest took 159.0159 ms Fix array took 2405.2405 ms (this is by going through all pixels) – Yariv Aug 07 '14 at 12:13
0

I have solved this problem with ImageSharp I calculate the gray value from the rgb values and then add it to the array.

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

private static byte[] GetImageData(byte[] imageData)
{
    using (var image = Image.Load<Rgba32>(imageData))
    {
        var buffer = new byte[image.Width * image.Height];
        var index = 0;

        image.ProcessPixelRows(accessor =>
        {
            for (int y = 0; y < accessor.Height; y++)
            {
                Span<Rgba32> pixelRow = accessor.GetRowSpan(y);
                for (int x = 0; x < pixelRow.Length; x++)
                {
                    ref Rgba32 pixel = ref pixelRow[x];
                    buffer[index] = (byte)((pixel.R + pixel.G + pixel.B) / 3);
                    index++;
                }
            }
        });

        return buffer;
    }
}
live2
  • 3,771
  • 2
  • 37
  • 46