PNG File Reader

Apr 4, 2025 at 10:48pm
I have been on a quest to write some kind of PNG file reader, mostly just for myself, and I was wondering if there is any good resources on how to do binary file reading. I've been mostly trying to figure it out myself but I am finding it difficult to do which im assuming is a gap in my knowledge mostly. My main issue right now is trying to search for specific chunks in a PNG file, any help in anyway is appreciated. I know that there probably isn't much practical use in writing a PNG reader but I've been mostly doing it out of curiosity and intrigue but I find it near impossible to find good resources on how to do it. You can judge me for picking up a silly project like this but my brain wants me to do this so please help a fellow programmer out. Thank you :)
Apr 5, 2025 at 12:01am
Its unclear where you are in your adventure.
Its a *fine* project to learn something.

Where are you on these steps?
1) Python knowledge to simply read and write a binary file. Can you do this, like a float, an int, a string, write it out and read it back in, in binary?
2) learn a hex editor. Can you open the file you wrote in 1) above and read it in a hex editor to verify it, and to some extent see what is in the file?
3) study the png file format. Do you know what that looks like? How big is the image, where are the pixels, what time was it taken, ... etc?
take a look at https://en.wikipedia.org/wiki/PNG#File_format to start

4) start to work on the project, using 1,2, and 3 to do something? Start simple ... maybe read an image in, swap say all the blue and red in the pixels, write it back out, and look at it in an image program to see that you did indeed mess it up?

related, but make sure you understand how byte order works, and what byte order the file is in. Sometimes you have to convert the values from one to another (big and little endian) yourself to get the correct numbers.

If I may make a suggestion, consider the old .tga (targa) format using an uncompressed image. This is possibly the most simple image format of all time, with very little beyond like the dimensions & the RGB array data. Png isn't rocket science like jpeg but its still nontrivial if its your first time. You may have to poke around for an editor that displays tga these days, but anything with a lot of formats will, while minimal stuff like ms paint may not.
Last edited on Apr 5, 2025 at 12:16am
Apr 5, 2025 at 12:12am
Where are you on these steps?
1) Python knowledge to simply read and write a binary file. Can you do this, like a float, an int, a string, write it out and read it back in, in binary?
2) learn a hex editor. Can you open the file you wrote in 1) above and read it in a hex editor to verify it, and to some extent see what is in the file?
3) study the png file format. Do you know what that looks like? How big is the image, where are the pixels, what time was it taken, ... etc?
4) start to work on the project, using 1,2, and 3 to do something? Start simple ... maybe read an image in, swap say all the blue and red in the pixels, write it back out, and look at it in an image program to see that you did indeed mess it up?

As of now I have prior knowledge of Python and a bit about the fstream header, I understand how to open a file (in binary mode) extract bytes from the file.

I have been reading about the PNG file format for a little while now and I understand some of the basic format of what is in the file. I've been able to extract the IHDR chunk header using the fstream and gather various attributes of the PNG.

My main issue as of now is that I am not sure how to search the file for the specific chunk headers, as from what I've read the chunk headers (except for the IHDR and IEND chunks) can occur in any order. Any advice on how I would do that?
Apr 6, 2025 at 9:13am
my advice is, DON'T :)

Read the file once. Process what you encounter as you encounter it, even if 'process' means 'hold it for later' or even 'discard'. If you need to process the chunks out of order from the file, then read them as you go and put them into the order you want them as you do that.

If you insist on looking for the headers, you can jump around, but I don't know the format. Eg if you get a header, do you now know the size of its record? Then you can jump past all that to the next record and get its header. Many binary files have headers with the sizes in them for just that reason, but you need to be very sure you jump to the right place each time. Any mistake and you are reading junk as if it were a header.
Last edited on Apr 6, 2025 at 9:15am
Apr 7, 2025 at 11:39am
You will, in practice, never find chunks out of order.

Some chunks are unordered relative to each other — which is fine, it won’t affect your ability to read the image data.

The two main ways to read the file are:

❶ Read byte-by-byte using something like ifstream::get(). This is very easy to do and works just fine.

âť· Read the whole thing into a std::string or std::vector<std::byte> or something, then you can pick at the pieces at your leisure.

Both ways work just as well, and are ultimately equivalent, believe it or not (your reader can easily be written to work over both with maybe a single line of code different between the two).

DO make sure to open the file/stream in std::ios::binary mode!

To track where a chunk is just keep a std::vector<std::size_t> of offsets into the file/stream/memory as you encounter them. That way you don’t have to search around for any specific chunk more than once. The PNG reader doesn’t really need to do that, though.
Apr 7, 2025 at 9:45pm
The two main ways to read the file are:

❶ Read byte-by-byte using something like ifstream::get(). This is very easy to do and works just fine.

âť· Read the whole thing into a std::string or std::vector<std::byte> or something, then you can pick at the pieces at your leisure.

Both ways work just as well, and are ultimately equivalent, believe it or not (your reader can easily be written to work over both with maybe a single line of code different between the two).


Okay thank you ill try to figure these out and see how I do, another question though, with data in the std::byte vector if I want to store the data in a variable should I use a casting operator? Like for the width and height they are stored as 4 bytes should I cast each byte into a char and use an OR bitwise operation into an integer? I do know that I will have to switch the edianess of the values. (I've ran into that issue before haha) I hope I'm not asking too many questions, I've just been struggling to figure this stuff out the past week or 2 and well my mind wont let it go untill I do. I'm getting there I think...
Apr 7, 2025 at 11:28pm
yes, cast the bytes into the integer then use the built in tools for converting it to the other endian format. Intel processers have an instruction on the chip that does this very fast, and visual studio at least can tap it, rather than trying to DIY with swap operations.

you dont need to cast into a char at all, that has no purpose. Just cast the 4 bytes into the integer directly (reinterpret cast).

No offense but these are parts of 1 & 2 that I listed. Write a separate program that writes a couple of integers to a binary file and then read them out. Look at them in a hex editor. Edit the file and put one of them in with the reversed endian and then have your program correct it (it can be as simple as the number 1, which is like 00 00 00 01 vs 01 00 00 00). The value doesn't even matter .. pick something easy on the eyes like 00 ab cd ef and print it in hex, do the endian conversion, print it again...

I always keep a trash program around with a couple dozen of the most useful includes/modules so you can just try a few lines to figure out how something works before you try it in something big. Play with it, get it right, then use it.
Last edited on Apr 7, 2025 at 11:33pm
Apr 7, 2025 at 11:29pm
It is quick and easy to build the integer from four bytes. Since all multibyte integers in a PNG are in network byte order, just build it as such. You can even cheat with a pointer to the first byte.

1
2
3
4
5
6
7
8
9
10
std::uint32_t
bytes_to_integer(
  std::uint8_t * bytes,
  int            n )
{
  std::uint32_t result = 0;
  while (n--)
    result = (result << 8) | *bytes++;
  return result;
}

If you are writing code to read from stream, you can do it like this, for example (this is not the only way, but one I tend to like):

1
2
3
4
5
6
7
8
template <typename IntType>
std::istream & read_integer( std::istream & f, IntType & value )
{
  std::uint8_t bytes[8];
  if (f.read( reinterpret_cast <char *> (bytes), sizeof(value) ))
    value = bytes_to_integer( bytes, sizeof(value) );
  return f;
}

Use it thus:

1
2
3
4
5
std::ifstream pngf( "my.png", std::ios::binary );
...
std::uint32_t chunk_length;
read_integer( pngf, chunk_length );
...

And so on.


You know, I always forget about why I dislike the automatic size selection. Don’t do that: be specific.

1
2
3
4
5
6
7
8
template <typename IntType>
std::istream & read_integer( std::istream & f, IntType & value, std::size_t n )
{
  std::uint8_t bytes[8];
  if (f.read( reinterpret_cast <char *> (bytes), n ))
    value = bytes_to_integer( bytes, n );
  return f;
}
1
2
3
4
5
std::ifstream pngf( "my.png", std::ios::binary );
...
std::uint32_t chunk_length;
read_integer( pngf, chunk_length, 4 );
...

Sorry to have given bad advice. Fixed.
Last edited on Apr 8, 2025 at 5:16am
Apr 8, 2025 at 1:54am
Ah okay thank you for the help I will see what I can do :). I was mainly asking about the cast since I know that often times people say to avoid it but yeah it makes sense why you would in this case. thanks again I'll keep this stuff in mind for the future and try to work on some practice things first!
Registered users can post here. Sign in or register to post.