Search code examples
c#arraysimagepng

File.ReadAllBytes doesn't read the PNG image pixels properly


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?


Solution

  • 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.