通过 zlib 压缩后用 C++ 从头开始编写 png 图像

问题描述 投票:0回答:1

我的目标是从起始位图图像写入 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,一切似乎都很好。那么,我的错误在哪里?

c++ compression png zlib
1个回答
0
投票

问题源于这一行:

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

© www.soinside.com 2019 - 2024. All rights reserved.