4

I am trying to read the bytes of a .png image using File.ReadAllBytes(string) method without success.

My images are of size 2464x2056x3 (15.197.952 bytes), but this method returns an array of about 12.000.000 bytes.

I tried with a white image of the same size, and I am getting a byte array of 25.549, and checking the byte array I can see all kind of values, that obviously is not correct because is a white image.

The code I am using is:

var frame = File.ReadAllBytes("C:\\workspace\\white.png");

I've also tried to open the image first as an Image object then get the byte array with the following:

using (var ms = new MemoryStream())
{
  var imageIn = Image.FromFile("C:\\workspace\\white.png");
  imageIn.Save(ms, imageIn.RawFormat);
  var array = ms.ToArray();
}

But the result is the same as before...

Any idea of what's happening?

How can I read the byte array?

wohlstad
  • 12,661
  • 10
  • 26
  • 39
juan carlos
  • 184
  • 1
  • 11
  • 3
    Are you expecting the file contents to be exactly 3 bytes per pixel? PNGs have built-in compression, and other metadata. – gunr2171 May 11 '22 at 13:34
  • `ReadAllBytes` works. You already read the byte array *of the file data*. Are you trying to read the pixel data? – Panagiotis Kanavos May 11 '22 at 13:37
  • What are you trying to do with that image? Why do you want the pixel values? It matters. `Image` and `Bitmap` were created for drawing on the screen and make life difficult when you try to apply transformations at the pixel level. On the other hand, other classes on the `System.Graphics` namespace allow you to specify transformations that are translated to Windows GDI+ commands, making them fast but restricted. For general image processing it's better to use eg ImageSharp – Panagiotis Kanavos May 11 '22 at 13:49
  • @PanagiotisKanavos yeah, what I am trying to read is the pixel data, an array of the size (2056x2464x3) with all the pixel values for RGB, maybe I am not calling things as they should be named. – juan carlos May 11 '22 at 14:17
  • @PanagiotisKanavos I need the array of pixel data to later convert it to an EmguCV Mat. Case is I have a real camera that provides me with that array, but I am trying to do a dummy camera for testing purposes, that simply reads images from disk and passes the array data as the real camera does. – juan carlos May 11 '22 at 14:19
  • @juancarlos in that case use ImageSharp, especially if you want your code to be cross-platform. System.Drawing is essentially a wrapper over Windows GDI+ APIs and uses limited GDI+ handles that need to be disposed. – Panagiotis Kanavos May 11 '22 at 15:04

1 Answers1

6

PNG is a compressed format.
See some info about it: Portable Network Graphics - Wikipedia.

This means that the binary representation is not the actual pixel values that you expect.

You need some kind of a PNG decoder to get the pixel values from the compressed data.

This post might steer you in the right direction: Reading a PNG image file in .Net 2.0. Note that it's quite old, maybe there are newer methods for doing it.

A side note: even a non compressed format like BMP has a header, so you can't simply read the binary file and get the pixel values in a trivial way.


Update: One way to get the pixel values from a PNG file is demonstrated below:

using System.Drawing.Imaging;

byte[] GetPngPixels(string filename)
{
    byte[] rgbValues = null;

    // Load the png and convert to Bitmap. This will use a .NET PNG decoder:
    using (var imageIn = Image.FromFile(filename))
    using (var bmp = new Bitmap(imageIn))
    {
        // Lock the pixel data to gain low level access:
        BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);

        // Get the address of the first line.
        IntPtr ptr = bmpData.Scan0;

        // Declare an array to hold the bytes of the bitmap.
        int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
        rgbValues = new byte[bytes];

        // Copy the RGB values into the array.
        System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

        // Unlock the pixel data:
        bmp.UnlockBits(bmpData);
    }

    // Here rgbValues is an array of the pixel values.
    return rgbValues;
}

This method will return a byte array with the size that you expect.
In order to use the data with opencv (or any similar usage), I advise you to enhance my code example and return also the image metadata (width, height, stride, pixel-format). You will need this metadata to construct a cv::Mat.

wohlstad
  • 12,661
  • 10
  • 26
  • 39
  • @JonasH after making a comment asking you about it I read the relevant documentation and understood what you just explained. Thanks ! – wohlstad May 11 '22 at 15:25
  • Thank you! It works. I had to change the `bmp.PixelFormat` to `PixelFormat.24bppRgb` because for some reason it is identifying the bmp as 32bpp, but the real image has 24bpp. – juan carlos May 11 '22 at 15:26