Portable Network Graphics Overview
The general layout of any given PNG file looks like this:
File Header: An 8-byte signature.
Chunks: Chunks of data ranging from image properties to the actual image itself.
The Problem
I want to read PNG files in C++ without using any external libraries. I want to do this to gain a deeper understanding of both PNG format and the C++ programming language.
I started off using fstream
to read images byte-by-byte, but I can't get past the header of any PNG file. I try using read( char*, int )
to put the bytes into char
arrays, but read
fails on every byte after the header.
As seen above, I think my program always gets caught up on the end-of-file 1A
byte. I'm developing on Windows 7 for Windows 7 and Linux machines.
Some of My (Old) Code
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstddef>
const char* INPUT_FILENAME = "image.png";
int main()
{
std::ifstream file;
size_t size = 0;
std::cout << "Attempting to open " << INPUT_FILENAME << std::endl;
file.open( INPUT_FILENAME, std::ios::in | std::ios::binary | std::ios::ate );
char* data = 0;
file.seekg( 0, std::ios::end );
size = file.tellg();
std::cout << "File size: " << size << std::endl;
file.seekg( 0, std::ios::beg );
data = new char[ size - 8 + 1 ];
file.seekg( 8 ); // skip the header
file.read( data, size );
data[ size ] = '\0';
std::cout << "Data size: " << std::strlen( data ) << std::endl;
}
The output is always similar to this:
Attempting to open image.png
File size: 1768222
Data size: 0
The file size is correct, but data size is clearly incorrect. Note that I try to skip the header (avoid the end-of-file character) and also account for this when declaring the size of char* data
.
Here are some data size values when I modify the file.seekg( ... );
line accordingly:
file.seekg( n ); data size
---------------- ---------
0 8
1 7
2 6
... ...
8 0
9 0
10 0
Some of My New Code
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstddef>
const char* INPUT_FILENAME = "image.png";
int main()
{
std::ifstream file;
size_t size = 0;
std::cout << "Attempting to open " << INPUT_FILENAME << std::endl;
file.open( INPUT_FILENAME, std::ios::in | std::ios::binary | std::ios::ate );
char* data = 0;
file.seekg( 0, std::ios::end );
size = file.tellg();
std::cout << "File size: " << size << std::endl;
file.seekg( 0, std::ios::beg );
data = new char[ size - 8 + 1 ];
file.seekg( 8 ); // skip the header
file.read( data, size );
data[ size ] = '\0';
std::cout << "Data size: " << ((unsigned long long)file.tellg() - 8) << std::endl;
}
I essentially just modified the Data size:
line. A thing to note is the output of the Data size:
line is always really close to the maximum value of whatever type
I cast file.tellg()
to.