web-dev-qa-db-fra.com

Comment puis-je lire manuellement les fichiers PNG en C++?

Aperçu des graphiques de réseau portables

La disposition générale de tout fichier PNG donné ressemble à ceci:

En-tête de fichier : Une signature de 8 octets.

Morceaux : Morceaux de données allant des propriétés de l'image à l'image elle-même.


Le problème

Je veux lire des fichiers PNG en C++ sans utiliser de bibliothèques externes. Je souhaite faire cela pour mieux comprendre le format PNG et le langage de programmation C++.

J'ai commencé à utiliser fstream pour lire les images octet par octet, mais je ne peux pas dépasser l'en-tête d'un fichier PNG. J'essaie d'utiliser read( char*, int ) pour mettre les octets dans des tableaux char, mais read échoue pour chaque octet après l'en-tête.

Comme indiqué ci-dessus, je pense que mon programme est toujours pris dans l'octet 1A de fin de fichier. Je développe sur Windows 7 pour Windows 7 et les machines Linux.


Une partie de mon (ancien) 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;
}

La sortie est toujours similaire à ceci:

Attempting to open image.png
File size: 1768222
Data size: 0

La taille du fichier est correcte, mais la taille des données est clairement incorrecte. Notez que j’essaie d’éviter l’en-tête (éviter le caractère de fin de fichier) et d’en rendre compte également lors de la déclaration de la taille de char* data.

Voici quelques valeurs de taille de données lorsque je modifie la ligne file.seekg( ... ); en conséquence:

file.seekg( n );             data size
----------------             ---------
0                            8
1                            7
2                            6
...                          ...
8                            0
9                            0
10                           0

Une partie de mon nouveau 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;
}

Je viens essentiellement de modifier la ligne Data size:. Il convient de noter que la sortie de la ligne Data size: est toujours très proche de la valeur maximale de la variable type que j’ai convertie à file.tellg().

9
user3745189

Votre (nouveau) code contient deux erreurs essentiel:

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

Tout d'abord, vous voulez lire les données sans le préfixe de 8 octets et allouer la bonne quantité d'espace (pas vraiment, voir plus loin). Mais à ce stade, size contient toujours la total octets du fichier, y compris le préfixe de 8 octets. Puisque vous demandez de lire size octets et qu'il ne reste que size-8 octets, l'opération file.read échoue. Vous ne vérifiez pas les erreurs et vous ne remarquez donc pas que file est invalidée à ce stade. Avec une vérification d'erreur, vous devriez avoir vu ceci:

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

Étant donné que file n'est plus valide à partir de ce moment, toutes les opérations telles que votre dernière file.tellg() renvoient -1.

La deuxième erreur est data[size] = '\0'. Votre tampon n'est pas si grand; ce devrait être data[size-8] = 0;. Actuellement, vous écrivez en mémoire au-delà de ce que vous aviez alloué précédemment, ce qui provoque un comportement indéfini et peut entraîner des problèmes ultérieurement.

Mais cette dernière opération montre clairement que vous pensez en termes de chaînes de caractères. Un fichier PNG n'est pas une chaîne, c'est un flux binaire de données. L'attribution de +1 pour sa taille et le paramétrage de cette valeur sur 0 (avec la méthode de pensée inutile "avec les caractères", avec '\0') n'est utile que si le fichier d'entrée est de type chaîne - disons, un fichier texte brut.

Voici une solution simple à vos problèmes actuels (et d'ajouter une vérification d'erreur pour toutes vos opérations sur les fichiers):

file.read( data, size-8 );

Cependant, je vous conseillerais fortement de commencer par regarder un format de fichier beaucoup plus simple. Le format de fichier PNG est compact et bien documenté; mais il est également polyvalent, compliqué et contient des données hautement compressées. Pour un débutant c'est trop difficile.

Commencez avec un format d'image plus simple. ppm est un format volontairement simple, bon pour commencer. tga , vieux mais simple, vous présente plusieurs autres concepts tels que les profondeurs de bits et le mappage de couleurs. La bmp de Microsoft présente quelques jolies mises en garde, mais peut toujours être considérée comme adaptée aux débutants. Si vous êtes intéressé par la compression simple, le codage de longueur de cycle de base d'une pcx est un bon point de départ. Une fois que vous avez maîtrisé votre projet, vous pouvez consulter le format gif , qui utilise la compression LZW, beaucoup plus dure.

Si vous parvenez à implémenter des analyseurs syntaxiques, vous voudrez peut-être consulter à nouveau PNG.

4
usr2564301

Si vous voulez savoir combien de données vous avez lues dans le fichier, utilisez à nouveau 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;

Il y a aussi une erreur dans votre code lors de la lecture des données. Vous lisez à sizesize est la taille du fichier, soit 8 octets de plus que nécessaire, puisque vous ignorez l'en-tête. Le bon code est

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

Solution 1:

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

Encore plus facile: Solution 2:

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

file.readsome () renvoie le nombre d'octets lus.

0
cdonat