如何在C ++中手动读取PNG文件?

时间:2015-06-26 18:34:02

标签: c++ file-io png fstream ifstream

便携式网络图形概述

任何给定PNG文件的总体布局如下所示:

文件标头:8字节签名。

Chunks :数据块,从图像属性到实际图像本身。

问题

我想在不使用任何外部库的情况下用C ++读取PNG文件。我想这样做是为了更深入地理解PNG格式和C ++编程语言。

我开始使用fstream逐字节读取图像,但我无法通过任何PNG文件的标头。我尝试使用read( char*, int )将字节放入char数组,但read在标题后的每个字节都失败。

如上所示,我认为我的程序总是被文件结束1A字节所占用。我正在Windows 7上为Windows 7和Linux机器开发。

我的一些(旧)代码

#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:行的输出总是非常接近于我type投射file.tellg()的最大值。

3 个答案:

答案 0 :(得分:4)

您的(新)代码包含两个基本错误:

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字节前缀。由于您要求读取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

第二个错误是data[size] = '\0'。你的缓冲区不是那么大;它应该是data[size-8] = 0;。目前,您正在写入超出之前分配的内存,这会导致未定义的行为,并可能在以后导致问题。

上一次操作清楚地表明您正在考虑字符串。 PNG文件不是字符串,而是二进制数据流。为其大小分配+1并将此值设置为0(使用不必要的“字符方式”思维方式,使用'\0')仅在输入文件为字符串时才有用输入 - 比方说,一个纯文本文件。

针对当前问题的一个简单修复就是这样(好了,并为所有文件操作添加错误检查):

file.read( data, size-8 );

但是,我强烈建议您先查看一个更简单的文件格式。 PNG文件格式紧凑且记录良好;但它也是多功能的,复杂的,并包含高度压缩的数据。对于初学者来说,太难了

从更简单的图像格式开始。 ppm是一种刻意简单的格式,很好开头。 tga,古老而简单,向您介绍了几个概念,如位深度和颜色映射。微软的bmp有一些不错的注意事项,但仍然可以被认为是'初学者友好'。如果您对简单压缩感兴趣,pcx的基本运行长度编码是一个很好的起点。掌握之后,您可以查看gif格式,该格式使用更难的LZW压缩。

只有成功实现了解析器,您才可能想再次查看PNG。

答案 1 :(得分:1)

如果您想知道从文件中读取了多少数据,请再次使用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;
}

答案 2 :(得分:0)

解决方案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()返回读取的字节数。