Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I manually read PNG files in C++?

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.

like image 396
user3745189 Avatar asked Jun 26 '15 18:06

user3745189


People also ask

How do I view a PNG image?

Instead, you can use the advanced operator filetype. For instance, filetype:png will search Google for PNG images. Once you enter this and search, the filetype text will disappear, but the page will update with only images of that type.

What program can view PNG files?

There are a lot of apps out there that can be used for the PNG file opening. Some of the commonly used apps on windows 10 for PNG file opening are; Microsoft Office, Paint, Picasa Photo Viewer, Windows Photo Viewer, and Photos. All applications are capable of opening PNG files and have different capabilities.

How is PNG data encoded?

PNG uses DEFLATE, a non-patented lossless data compression algorithm involving a combination of LZ77 and Huffman coding. Permissively-licensed DEFLATE implementations, such as zlib, are widely available.


1 Answers

Your (new) code contains two essential errors:

data = new char[ size - 8 + 1 ];
file.seekg( 8 ); // skip the header
file.read( data, size );  // <-- here
data[ size ] = '\0';      // <-- and here

First off, you want to read the data without the 8 byte prefix, and you allocate the right amount of space (not really, see further). But at that point, size still holds the total amount of bytes of the file, including the 8 byte prefix. Since you ask to read size bytes and there are only size-8 bytes remaining, the file.read operation fails. You don't check for errors and so you do not notice file is invalidated at that point. With an error check you should have seen this:

if (file)
  std::cout << "all characters read successfully.";
else
  std::cout << "error: only " << file.gcount() << " could be read";

Because file is invalid from that point on, all operations such as your later file.tellg() return -1.

The second error is data[size] = '\0'. Your buffer is not that large; it should be data[size-8] = 0;. Currently, you are writing into memory beyond what you allocated earlier, which causes Undefined Behavior and may lead to problems later on.

But that last operation clearly shows you are thinking in terms of character strings. A PNG file is not a string, it is a binary stream of data. Allocating +1 for its size and setting this value to 0 (with the unnecessary "character-wise" way of thinking, with '\0') is only useful if the input file is of a string type – say, a plain text file.

A simple fix for your current issues is this (well, and add error checking for all your file operations):

file.read( data, size-8 );

However, I would strongly advise you to look at a much simpler file format first. The PNG file format is compact and well documented; but it is also versatile, complicated, and contains highly compressed data. For a beginner it is way too hard.

Start with an easier image format. ppm is a deliberately simple format, good to start with. tga, old but easy, introduces you to several more concepts such as bit depths and color mapping. Microsoft's bmp has some nice little caveats but can still be considered 'beginner friendly'. If you are interested in simple compression, the basic Run Length Encoding of a pcx is a good starting point. After mastering that you could look in to the gif format, which uses the much harder LZW compression.

Only if you succeed in implementing parsers for these, you may want to look at PNG again.

like image 139
Jongware Avatar answered Oct 12 '22 20:10

Jongware