it-swarm.com.de

Wie kann ich PNG-Dateien in C++ manuell lesen?

Portable Network Graphics - Übersicht

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.


Das Problem

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 fstreamverwendet, 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 charname__-Arrays zu setzen, aber readschlä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.


Ein Teil meines (alten) Codes

#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

Einige meiner neuen Codes

#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 typename__Ich file.tellg() umgewandelt habe.

9
user3745189

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.

4
usr2564301

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;
}
1
NathanOliver

Lösung 1:

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

Noch einfacher: Lösung 2:

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.

0
cdonat