任何给定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()
的最大值。
答案 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)
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()返回读取的字节数。