Das allgemeine Layout einer PNG-Datei sieht folgendermaßen aus:
Datei-Header : Eine 8-Byte-Signatur.
Chunks : Datenblöcke, die von den Bildeigenschaften bis zum eigentlichen Bild reichen.
Ich möchte PNG-Dateien in C++ lesen, ohne externe Bibliotheken zu verwenden. Ich möchte dies tun, um ein tieferes Verständnis des PNG-Formats und der Programmiersprache C++ zu erlangen.
Ich habe fstream
verwendet, um Bilder Byte für Byte zu lesen, komme aber nicht über den Header einer PNG-Datei hinaus. Ich versuche, read( char*, int )
zu verwenden, um die Bytes in char
name__-Arrays zu setzen, aber read
schlägt bei jedem Byte nach dem Header fehl.
Wie oben zu sehen ist, bleibt mein Programm immer am 1A
-Byte am Ende der Datei hängen. Ich entwickle auf Windows 7 für Windows 7 und Linux-Maschinen.
#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;
}
Die Ausgabe ist immer ähnlich wie folgt:
Attempting to open image.png
File size: 1768222
Data size: 0
Die Dateigröße ist korrekt, aber die Datengröße ist eindeutig falsch. Beachten Sie, dass ich versuche, den Header zu überspringen (das Dateiendezeichen zu vermeiden) und dies auch bei der Angabe der Größe von char* data
zu berücksichtigen.
Hier sind einige Werte für die Datengröße, wenn ich die Zeile file.seekg( ... );
entsprechend ändere:
file.seekg( n ); data size
---------------- ---------
0 8
1 7
2 6
... ...
8 0
9 0
10 0
#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;
}
Ich habe im Wesentlichen nur die Zeile Data size:
geändert. Zu beachten ist, dass die Ausgabe der Zeile Data size:
immer nahe am Maximalwert des Werts liegt, für den type
name__Ich file.tellg()
umgewandelt habe.
Ihr (neuer) Code enthält zwei essential-Fehler:
data = new char[ size - 8 + 1 ];
file.seekg( 8 ); // skip the header
file.read( data, size ); // <-- here
data[ size ] = '\0'; // <-- and here
Zunächst möchten Sie die Daten ohne das 8-Byte-Präfix lesen und die richtige Menge an Speicherplatz zuweisen (nicht wirklich, siehe weiter). An diesem Punkt enthält size
jedoch noch die Anzahl der Bytes total der Datei, einschließlich des 8-Byte-Präfixes. Da Sie nach dem Lesen von size
-Bytes fragen und nur noch size-8
-Bytes vorhanden sind, schlägt die file.read
-Operation fehl. Sie prüfen nicht auf Fehler und bemerken nicht, dass file
an diesem Punkt ungültig wird. Bei einer Fehlerprüfung sollten Sie Folgendes gesehen haben:
if (file)
std::cout << "all characters read successfully.";
else
std::cout << "error: only " << file.gcount() << " could be read";
Da file
von diesem Punkt an ungültig ist, geben alle Vorgänge, wie beispielsweise Ihr späterer file.tellg()
, -1
zurück.
Der zweite Fehler ist data[size] = '\0'
. Dein Puffer ist nicht so groß; es sollte data[size-8] = 0;
sein. Derzeit schreiben Sie über den zuvor zugewiesenen Speicher hinaus in den Arbeitsspeicher, was undefiniertes Verhalten verursacht und später zu Problemen führen kann.
Aber die letzte Operation zeigt deutlich, dass Sie in Form von Zeichenketten denken. Eine PNG-Datei ist keine Zeichenfolge, sondern ein binärer Datenstrom. Die Zuweisung von +1
für seine Größe und das Festlegen dieses Werts auf 0
(mit der unnötigen "zeichenweisen" Denkweise mit '\0'
) ist nur nützlich, wenn die Eingabedatei vom Typ "String" ist, beispielsweise eine reine Textdatei.
Eine einfache Lösung für Ihre aktuellen Probleme ist dies (gut, und fügen Sie Fehlerüberprüfung für alle Dateivorgänge hinzu):
file.read( data, size-8 );
Ich würde Ihnen jedoch dringend raten, sich zunächst ein viel einfacheres Dateiformat anzusehen. Das PNG-Dateiformat ist kompakt und gut dokumentiert. Es ist aber auch vielseitig, kompliziert und enthält hochkomprimierte Daten. Für einen Anfänger ist es viel zu schwer.
Beginnen Sie mit einem einfacheren Bildformat. ppm
ist ein bewusst einfaches Format, gut zu beginnen. tga
, alt aber einfach, führt Sie in einige weitere Konzepte ein, wie Bittiefen und Farbzuordnung. Microsofts bmp
hat einige nette Vorbehalte, kann aber trotzdem als "Anfängerfreundlich" betrachtet werden. Wenn Sie an einer einfachen Komprimierung interessiert sind, ist die grundlegende Lauflängencodierung einer pcx
ein guter Ausgangspunkt. Nach dem Mastering können Sie sich das Format gif
anschauen, das die viel härtere LZW-Komprimierung verwendet.
Nur wenn es Ihnen gelingt, Parser für diese zu implementieren, möchten Sie möglicherweise erneut PNG betrachten.
Wenn Sie wissen möchten, wie viele Daten Sie aus der Datei gelesen haben, verwenden Sie einfach erneut tellg()
.
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;
Beim Lesen der Daten ist auch ein Fehler in Ihrem Code aufgetreten. Sie lesen in size
, wobei size
die Größe der Datei ist, die um 8 Byte größer ist als Sie benötigen, da Sie den Header überspringen. Der richtige Code lautet
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;
}
file.read( data, size );
Size_t data_size = file.tellg() - 8;
std::cout << "Data size: " << data_size << std::endl;
Size_t data_size = file.readsome( data, size );
std::cout << "Data size: " << data_size << std::endl;
file.readsome () gibt die Anzahl der gelesenen Bytes zurück.