0

I have some raw sensor data which is in a single dimension byte array. The data is actually in IEEE single precision floating point format. I know the X and Y axis legths and I want to create a Windows Bitmap (greyscale - there is only one colour plane containing luminance data) from my data.

Here's what I'm trying so far:

        var bitmap = new Bitmap(xAxis, yAxis, PixelFormat.Format16bppGrayScale);
        var pixelReader = GetPixelReader(hdu.MandatoryKeywords.BitsPerPixel);
        using (var stream = new MemoryStream(hdu.RawData, writable: false))
            {
            using (var reader = new BinaryReader(stream, Encoding.ASCII))
                {
                for (var y = 0; y < yAxis; y++)
                    {
                    for (var x = 0; x < xAxis; x++)
                        {
                        var pixel = pixelReader(reader);
                        var argb = Color.FromArgb(pixel, pixel, pixel);
                        bitmap.SetPixel(x, y, argb);
                        }
                    }
                }
            }
        return bitmap;

pixelReader is a delegate and defined as:

    private static int ReadIeeeSinglePrecision(BinaryReader reader)
        {
        return (int) reader.ReadSingle();
        }

When I run this code, I get an exception InvalidArgumentException on the line where I try to set the pixel value. I stepped it in the debugger and x=0, y=0 and pixel=0. It doesn't say which argument is invalid or why (thanks Microsoft).

So clearly I'm doing something wrong and actually, I suspect there is probably a more efficient way of going about this. I would appreciate any suggestions. For reasons I can't quite put my finger on, I am finding this code very challenging to write.

Tim Long
  • 13,508
  • 19
  • 79
  • 147
  • 1
    Don't use `SetPixel` for bulk operations, [use `LockBits` instead](http://stackoverflow.com/questions/1563038/fast-work-with-bitmaps-in-c-sharp). Also, please [get the exception details](https://blogs.msdn.microsoft.com/saraford/2008/08/07/did-you-know-you-can-copy-the-exception-details-with-one-click-from-the-exception-assistant-276/) of the InvalidArgumentException and include it here. it will help greatly figure out what is going wrong. – Scott Chamberlain Oct 03 '16 at 21:41
  • @ScottChamberlain yes I had read that in the documentation, but I think that's just a matter of performance, isn't it? I need to solve the problem first and then optimise it maybe later, or never. To be honest, the documentation of LockBits made even less sense to me. – Tim Long Oct 03 '16 at 21:43
  • 1
    are you getting a valid color in argb or just getting a null? I am suspecting that your problem is earlier on, but the implicit type vars are obscuring the problem. I'd strongly recommend using the actual types instead of defining everything as vars when there is really no need to in most/all of this code. –  Oct 03 '16 at 21:50
  • @AgapwIesu The argb is working... it has the value `{Name=ff000000, ARGB=(255,0,0,0)}` in the debugger – Tim Long Oct 03 '16 at 21:58
  • What do you get when you exchange x and y loops? – L.B Oct 03 '16 at 22:06
  • 1
    Might be related to the 16bpp format, have a look at http://stackoverflow.com/questions/34649745/system-argumentexception-when-trying-to-use-setpixel-x-y-color – KMoussa Oct 03 '16 at 22:10
  • @L.B since the problem is happening at x=0, y=0 I can't see how exchanging the loops will make any difference...? – Tim Long Oct 03 '16 at 22:16
  • Hmm, this looks promising... https://social.msdn.microsoft.com/Forums/vstudio/en-US/10252c05-c4b6-49dc-b2a3-4c1396e2c3ab/writing-a-16bit-grayscale-image?forum=csharpgeneral – Tim Long Oct 03 '16 at 22:26
  • 1
    I think the problem might be that the 16bppGrayscale format might be choking on a full argb color. Try taking the pixel format out of the bitmat declaration, so just var bitmap = new Bitmap(xAxis, yAxis); –  Oct 03 '16 at 22:26
  • 1
    Just this code will give you the same problem `var bitmap = new Bitmap(100, 100, PixelFormat.Format16bppGrayScale); var argb = Color.FromArgb(0, 0, 0); bitmap.SetPixel(0, 0, argb);` –  Oct 03 '16 at 22:45
  • @AgapwIesu Right - that is pretty much the crux of the problem. SetPixel() will only take a Color() and the only way I can see to create a color is by using Color.FromArgb(). So that begs the question, what the hell does it expect me to do? Not exactly following the principle of least astonishment, are they? Anyway, as you will see from my answer, I have sidestepped the problem by converting my data to 16-bit values and then feeding that in directly as the bitmap. – Tim Long Oct 03 '16 at 23:01
  • @ScottChamberlain Exception _details_... if only there had been any, then maybe I could have figured it out for myself ;-) "It doesn't say which argument is invalid or why (thanks Microsoft)." – Tim Long Oct 03 '16 at 23:04
  • 1
    @TimLong There is still useful information that can be provided from the details even if it does not say which parameter, like the HResult code and the stack trace. get the details of the exception and post it in full in your question – Scott Chamberlain Oct 04 '16 at 00:06

1 Answers1

0

OK here is what worked in the end. Based on code taken from Mark Dawson's answer to this question: https://social.msdn.microsoft.com/Forums/vstudio/en-US/10252c05-c4b6-49dc-b2a3-4c1396e2c3ab/writing-a-16bit-grayscale-image?forum=csharpgeneral

private static Bitmap CreateBitmapFromBytes(byte[] pixelValues, int width, int height)
    {
    //Create an image that will hold the image data
    Bitmap pic = new Bitmap(width, height, PixelFormat.Format16bppGrayScale);
    //Get a reference to the images pixel data
    Rectangle dimension = new Rectangle(0, 0, pic.Width, pic.Height);
    BitmapData picData = pic.LockBits(dimension, ImageLockMode.ReadWrite, pic.PixelFormat);
    IntPtr pixelStartAddress = picData.Scan0;
    //Copy the pixel data into the bitmap structure
    System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, pixelStartAddress, pixelValues.Length);
    pic.UnlockBits(picData);
    return pic;
    }

So then I modified my own code to convert from the IEEE float data into 16 bit integers, and then create the bitmap directly from that, like so:

        var pixelReader = GetPixelReader(hdu.MandatoryKeywords.BitsPerPixel);
        var imageBytes = new byte[xAxis * yAxis * sizeof(Int16)];
        using (var outStream = new MemoryStream(imageBytes, writable: true))
        using (var writer = new BinaryWriter(outStream))
        using (var inStream = new MemoryStream(hdu.RawData, writable: false))
        using (var reader = new BinaryReader(inStream, Encoding.ASCII))
            for (var y = 0; y < yAxis; y++)
                {
                for (var x = 0; x < xAxis; x++)
                    {
                    writer.Write(pixelReader(reader));
                    }
                }
        var bitmap = CreateGreyscaleBitmapFromBytes(imageBytes, xAxis, yAxis);
        return bitmap;

This appears to also address the efficiency problem highlighted in the comments.

Tim Long
  • 13,508
  • 19
  • 79
  • 147
  • Once I tried to display the image I'd created, that didn't work. So I still have a ways to go on this problem yet. I'll update my answer when I find a better solution. – Tim Long Oct 06 '16 at 20:27