web-dev-qa-db-ja.com

C ++でPNGファイルを手動で読み取るにはどうすればよいですか?

ポータブルネットワークグラフィックスの概要

特定のPNGファイルの一般的なレイアウトは次のようになります。

ファイルヘッダー:8バイトの署名。

Chunks:画像のプロパティから実際の画像自体に及ぶデータのチャンク。


問題

外部ライブラリを使用せずにC++でPNGファイルを読みたい。これを行って、PNG形式とC++プログラミング言語の両方をより深く理解したいと思います。

fstreamを使用して画像をバイト単位で読み取ることから始めましたが、PNGファイルのヘッダーを通過できません。 read( char*, int )を使用してバイトをchar配列に入れようとしましたが、ヘッダーの後のすべてのバイトでreadが失敗します。

上記のように、私のプログラムは常にファイルの終わりの_1A_バイトに追いつくと思います。 Windows 7およびLinuxマシン用にWindows 7で開発しています。


私の(古い)コードの一部

_#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;
}
_

出力は常に次のようになります。

_Attempting to open image.png
File size: 1768222
Data size: 0
_

ファイルサイズは正しいですが、データサイズは明らかに正しくありません。 _char* data_のサイズを宣言するときは、ヘッダーをスキップして(ファイルの終わり文字を避けます)、これも考慮していることに注意してください。

file.seekg( ... );行を適宜変更したときのデータサイズの値をいくつか次に示します。

_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;
}
_

基本的には、_Data size:_行を変更しただけです。注意すべきことは、_Data size:_行の出力は常に、file.tellg()をキャストしたtypeの最大値に常に近いということです。

13
user3745189

(新しい)コードに2つのessentialエラーが含まれています:

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

最初に、8バイトのプレフィックスなしでデータを読み取り、適切な量のスペースを割り当てます(実際にはそうではありません)。ただし、その時点では、sizeは、8バイトのプレフィックスを含む、ファイルのtotalバイトの量を保持しています。 sizeバイトの読み取りを要求し、_size-8_バイトしか残っていないため、_file.read_操作は失敗します。エラーをチェックしないので、その時点でfileが無効になっていることに気付かないでしょう。エラーチェックを使用すると、これを確認できたはずです。

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

その時点からfileは無効であるため、後のfile.tellg()などのすべての操作は_-1_を返します。

2番目のエラーは_data[size] = '\0'_です。バッファはそれほど大きくありません。 _data[size-8] = 0;_である必要があります。現在、以前に割り当てたものを超えてメモリに書き込んでいるため、未定義の動作が発生し、後で問題が発生する可能性があります。

しかし最後の操作は、文字列の観点から考えていることを明確に示しています。 PNGファイルは文字列ではなく、データのバイナリストリームです。サイズに_+1_を割り当て、この値を_0_に設定する(不要な「文字ごとの」考え方で、_'\0'_を使用)は、入力ファイルが文字列の場合にのみ役立ちますタイプ–たとえば、プレーンテキストファイル。

現在の問題の簡単な修正は次のとおりです(まあ、すべてのファイル操作のエラーチェックを追加します)。

_file.read( data, size-8 );
_

ただし、最初にもっと単純なファイル形式を確認することを強くお勧めします。 PNGファイル形式はコンパクトで、十分に文書化されています。ただし、用途が広く、複雑で、高度に圧縮されたデータが含まれています。初心者にとっては、方法が難しすぎる

より簡単な画像フォーマットから始めます。 ppm は、故意に単純な形式であり、最初は適切です。 tga 古くて簡単ですが、ビット深度やカラーマッピングなど、いくつかの概念を紹介しています。 Microsoftの bmp には、ちょっとした警告がいくつかありますが、「初心者にやさしい」と見なすことができます。単純な圧縮に関心がある場合は、 pcx の基本的なランレングスエンコーディングが出発点として適しています。習得したら、 gif 形式を調べます。この形式では、より困難なLZW圧縮が使用されます。

これらのパーサーの実装に成功した場合にのみ、PNGをもう一度確認することをお勧めします。

5
usr2564301

ファイルから読み取ったデータの量を知りたい場合は、もう一度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;

データの読み取りにもコードにエラーがあります。 sizeを読み取っています。ここで、sizeはファイルのサイズであり、ヘッダーをスキップしているため、必要なサイズより8バイト多くなります。正しいコードは

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

解決策1:

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

さらに簡単:ソリューション2:

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

file.readsome()は、読み取られたバイト数を返します。

0
cdonat