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

Network Graphics Overview

The general layout of any PNG file is as follows:

File Header : 8-byte signature.

Chunks : pieces of data ranging from image properties to the actual image.


Problem

I want to read PNG files in C ++ without using any external libraries. I want to do this in order to gain a deeper understanding of the PNG format and the C ++ programming language.

I started using fstream to read images byte-by-bye, but I can’t get past the header of any PNG file. I am trying to use read( char*, int ) to put bytes in char arrays, but read does not work in every byte after the header.

As you can see above, I think my program always ends up at the end of byte 1A . I am developing Windows 7 for Windows 7 and Linux.


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 the data size is clearly incorrect. Please note that I am trying to skip the header (avoid the end-of-file character), and also take this into account when declaring a char* data size.

Below are some data size values ​​when changing the line file.seekg( ... ); :

 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 changed the line Data size: It should be noted that the output of the string Data size: always really close to the maximum value of any type that I used file.tellg() for.

+6
source share
3 answers

Your (new) code contains two significant errors:

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

First, you want to read data without an 8-byte prefix, and you allocate the right amount of space (actually don't look any further). But at this point, size still contains the total number of bytes of the file, including an 8-byte prefix. Since you are asking to read size bytes and there is only size-8 bytes left, the file.read operation fails. You do not check for errors, and therefore you do not notice that file not valid at this point. With error checking, you should have seen this:

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

Since file invalid from this point, all operations, such as your later file.tellg() , return -1 .

The second error is data[size] = '\0' . Your buffer is not so big; it should be data[size-8] = 0; . You are currently writing to memory beyond what you previously assigned, which causes Undefined Behavior and can lead to problems later.

But this last operation clearly shows what you think of character strings. A PNG file is not a string, it is a binary data stream. Highlighting +1 for its size and setting this value to 0 (with an unnecessary "figurative" way of thinking using '\0' ) is only useful if the input file is of a string type β€” say, a plain text file.

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

 file.read( data, size-8 ); 

However, I would strongly recommend looking at a simpler file format first. The PNG file format is compact and well-documented; but it is also versatile, complicated and contains compressed data. For beginners, this is too complicated.

Start with a simpler image format. ppm is an intentionally simple format to get you started. tga , old but simple, presents you with a few more concepts, such as bit depths and color matching. Microsoft bmp has some nice little things, but it can still be considered a "newbie." If you are interested in simple compression, the basic pcx path length encoding is a good starting point. After mastering, you can watch in gif format, which uses much more complex LZW compression.

Only if you manage to implement parsers for them, you can look again at PNG.

+4
source

If you want to know how much data you read from a file, just use tellg() again.

 data = new char[ size - 8 + 1 ]; file.seekg( 8 ); // skip the header file.read( data, size ); data[ size ] = '\0'; if(file.good()) // make sure we had a good read. std::cout << "Data size: " << file.tellg() - 8 << std::endl; 

There is an error reading the data in the code. You read size , where size is the size of the file, which is 8 bytes larger than you need, since you are missing the header. Correct code

 const char* INPUT_FILENAME = "ban hammer.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); 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 - 8); data[size] = '\0'; std::cout << "Data size: " << file.tellg() << std::endl; cin.get(); return 0; } 
+1
source

Solution 1:

 file.read( data, size ); Size_t data_size = file.tellg() - 8; std::cout << "Data size: " << data_size << std::endl; 

Even simpler: Solution 2:

 Size_t data_size = file.readsome( data, size ); std::cout << "Data size: " << data_size << std::endl; 

file.readsome () returns the number of bytes read.

0
source

Source: https://habr.com/ru/post/989829/


All Articles