我的目标是从起始位图图像写入 png 图像。我知道很多库的存在,但我需要从头开始编写。
我实现的第一个功能是“applyNoneFilter”
uchar* applyNoneFilter(const uchar* input, int width, int height)
{
uint8_t* output = new uchar[width * (height + 1)];
for (int y = 0; y < height; y++)
{
output[y * (width + 1)] = 0;
memcpy(output + y * (width + 1), input + y * width, width);
}
return output;
}
我通过
img.ptr()
传递图像数据,即通过 OpenCV 加载的图像的原始数据。该函数只是在每条扫描线前添加一个 0,实现了 png 创建的第一步:过滤。
我将过滤后的输出传递给我编写的
writeCompressedDataToPNG
函数:
void writeCompressedDataToPNG(const uint8_t* input, const std::string& filename, uint32_t width, uint32_t height) {
std::ofstream outFile(filename, std::ios::binary);
if (!outFile) {
throw std::runtime_error("Failed to open file for writing");
}
// PNG Header
const unsigned char pngHeader[8] = { '\211', 'P', 'N', 'G', '\r', '\n', '\032', '\n' };
outFile.write(reinterpret_cast<const char*>(pngHeader), 8);
// IHDR Chunk
unsigned char ihdrChunk[25] = {
0x00, 0x00, 0x00, 0x0D, // Length of IHDR data
'I', 'H', 'D', 'R',
0x00, 0x00, 0x00, 0x00, // Width placeholder
0x00, 0x00, 0x00, 0x00, // Height placeholder
0x08, // Bit depth: 8
0x00, // Color type: 0 (grayscale)
0x00, // Compression method: Deflate
0x00, // Filter method: No filtering
0x00, // Interlace method: 0
0x00, 0x00, 0x00, 0x00 // CRC placeholder
};
intToBigEndian(width, &ihdrChunk[8]);
intToBigEndian(height, &ihdrChunk[12]);
uint32_t crc = crc32(0, ihdrChunk + 4, 17);
intToBigEndian(crc, &ihdrChunk[21]);
outFile.write(reinterpret_cast<const char*>(ihdrChunk), 25);
// IDAT chunk
uLongf compression_size = compressBound(width * (height + 1));
std::vector<uchar> compressed(compression_size);
int result = compress(compressed.data(), &compression_size, input, width * (height + 1));
if (result != Z_OK)
{
std::cerr << "Compression failed" << std::endl;
exit(-1);
}
compressed.resize(compression_size);
uint32_t chunkLength = compressed.size();
unsigned char chunkLengthBytes[4];
intToBigEndian(chunkLength, chunkLengthBytes);
outFile.write(reinterpret_cast<const char*>(chunkLengthBytes), 4);
unsigned char chunkType[4] = { 'I', 'D', 'A', 'T' };
outFile.write(reinterpret_cast<const char*>(chunkType), 4);
uint32_t crc_idat = crc32(0, chunkType, 4); // Include "IDAT" chunk type
outFile.write(reinterpret_cast<const char*>(compressed.data()), compressed.size());
crc_idat = crc32(crc_idat, compressed.data(), compressed.size());
unsigned char crcBytes[4];
intToBigEndian(crc_idat, crcBytes);
outFile.write(reinterpret_cast<const char*>(crcBytes), 4);
// IEND Chunk
const unsigned char iendChunk[12] = {
0x00, 0x00, 0x00, 0x00,
'I', 'E', 'N', 'D',
0xAE, 0x42, 0x60, 0x82
};
outFile.write(reinterpret_cast<const char*>(iendChunk), 12);
outFile.close();
}
函数
intToBigEndian
就是这个,它根据png规范将整数转换为bigendian表示。
void intToBigEndian(uint32_t value, unsigned char* buffer) {
buffer[0] = (value >> 24) & 0xFF;
buffer[1] = (value >> 16) & 0xFF;
buffer[2] = (value >> 8) & 0xFF;
buffer[3] = value & 0xFF;
}
它创建了一个我可以在 Windows 图像可视化器中正确可视化的图像,当我尝试使用 OpenCV 通过代码加载它时
imread
我收到错误 libpng error: bad adaptive filter value
。
我知道我搞乱了 IDAT 块的写入,特别是过滤器。但我看不到问题所在。我还使用 png-file-chunk-inspector 检查了 crc,一切似乎都很好。那么,我的错误在哪里?
问题源于这一行:
memcpy(output + y * (width + 1), input + y * width, width);
applyNoneFilter 的更正实现:
uchar* applyNoneFilter(const uchar* input, int width, int height)
{
uint8_t* output = new uchar[height * (width + 1)];
for (int y = 0; y < height; y++)
{
output[y * (width + 1)] = 0; // Set filter type to 0 for "None"
memcpy(output + y * (width + 1) + 1, input + y * width, width);
// Copy the scanline starting from the second byte of the output buffer
}
return output;
}
过滤输出中每个扫描线的第一个字节必须是过滤器类型(在本例中,0 表示无过滤器)。 在您的原始代码中,memcpy 会覆盖为过滤器类型设置的
0
,因为您从 output + y * (width + 1)
开始复制,而不是 output + y * (width + 1) + 1
。