如何使用缓冲区制作 PNG(8 位索引颜色)?获取 "compression header fails checksum"

How to make PNG (8-bit Indexed Colors) using buffers? Getting "compression header fails checksum"

你好吗?

我正在尝试使用缓冲区在 Game Maker Studio 2 上从头开始制作 PNG,但我在使用 pngcheck 时遇到了一些错误:

File: [01;37mtest.png[0m (85 bytes)
  chunk [40;33mIHDR[0m at offset 0x0000c, length 13
    2 x 2 image, 8-bit palette, non-interlaced
  chunk [40;33mPLTE[0m at offset 0x00025, length 12: 4 palette entries
  chunk [40;33mIDAT[0m at offset 0x0003d, length 4
    zlib: compression header fails checksum
    zlib: inflate error = -3 (data error)

我正在发布下面的代码以显示我到目前为止得到的结果。 如您所见,我已成功添加 IHDR header,将位深度设置为 8,将颜色类型设置为 3 等,并添加所有其他数据块。

我还通过在 Game Maker 中添加 CRC32 脚本来计算正确的字节,设法使 CRC 字段正常工作。

我想要实现的是使用 PLTE 块中的颜色创建一个具有 4 种不同颜色的简单 2x2 图像,但我得到的是一个红色方块 (2x2) 以及上面的错误pngcheck.

我想我遗漏了其中的“压缩”部分,如果有人可以帮助我,那将非常有帮助。

// Variables
var File;

File = argument0;

// Loop Variables
var i;

// Create Buffer
var Buffer = buffer_create(1024, buffer_grow, 1);

// Signature
var Signature = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
for(i=0; i<8; i++){ buffer_write(Buffer, buffer_u8, Signature[i]); }


// --------------------------------------------------
// IHDR
// --------------------------------------------------
// Length
var IHDRLength = 13;
buffer_write(Buffer, buffer_u8, (IHDRLength >> 24) & 255);
buffer_write(Buffer, buffer_u8, (IHDRLength >> 16) & 255);
buffer_write(Buffer, buffer_u8, (IHDRLength >> 8) & 255);
buffer_write(Buffer, buffer_u8, (IHDRLength & 255));

// CRC Position
var IHDRCRCPos = buffer_tell(Buffer);

// Type
var IHDRType = ["I", "H", "D", "R"];
for(i=0; i<4; i++){ buffer_write(Buffer, buffer_u8, ord(IHDRType[i])); }

// Data (Bytes): Width (4), Height (4), Bit Depth (1), Color Type (1), Compression Method (1), Filter Method (1), Interlace Method (1)
// Width
var Width = 2;
buffer_write(Buffer, buffer_u8, (Width >> 24) & 255);
buffer_write(Buffer, buffer_u8, (Width >> 16) & 255);
buffer_write(Buffer, buffer_u8, (Width >> 8) & 255);
buffer_write(Buffer, buffer_u8, (Width & 255));

// Height
var Height = 2;
buffer_write(Buffer, buffer_u8, (Height >> 24) & 255);
buffer_write(Buffer, buffer_u8, (Height >> 16) & 255);
buffer_write(Buffer, buffer_u8, (Height >> 8) & 255);
buffer_write(Buffer, buffer_u8, (Height & 255));

// Bit Depth
var BitDepth = 8;
buffer_write(Buffer, buffer_u8, BitDepth);

// Color Type
var ColorType = 3;
buffer_write(Buffer, buffer_u8, ColorType);

// Compression
var Compression = 0;
buffer_write(Buffer, buffer_u8, Compression);

// Filter
var Filter = 0;
buffer_write(Buffer, buffer_u8, Filter);

// Interlace
var Interlace = 0;
buffer_write(Buffer, buffer_u8, Interlace);

// CRC (Type+Data Bytes)
var IHDRCRC = CRC32(Buffer, IHDRCRCPos, 17);
buffer_write(Buffer, buffer_u8, (IHDRCRC >> 24) & 255);
buffer_write(Buffer, buffer_u8, (IHDRCRC >> 16) & 255);
buffer_write(Buffer, buffer_u8, (IHDRCRC >> 8) & 255);
buffer_write(Buffer, buffer_u8, (IHDRCRC & 255));


// --------------------------------------------------
// PLTE
// --------------------------------------------------
// Length (Colors*3)
var PLTELength = 4*3;
buffer_write(Buffer, buffer_u8, (PLTELength >> 24) & 255);
buffer_write(Buffer, buffer_u8, (PLTELength >> 16) & 255);
buffer_write(Buffer, buffer_u8, (PLTELength >> 8) & 255);
buffer_write(Buffer, buffer_u8, (PLTELength & 255));

// CRC Position
var PLTECRCPos = buffer_tell(Buffer);
var ColorBytes = 0;

// Type
var PLTEType = ["P", "L", "T", "E"];
for(i=0; i<4; i++){ buffer_write(Buffer, buffer_u8, ord(PLTEType[i])); }

// Data (Bytes): R (1), G (1), B (1)
// PLTELength div Colors
// Color 1
buffer_write(Buffer, buffer_u8, 255);
buffer_write(Buffer, buffer_u8, 55);
buffer_write(Buffer, buffer_u8, 55);
ColorBytes += 3;

// Color 2
buffer_write(Buffer, buffer_u8, 55);
buffer_write(Buffer, buffer_u8, 255);
buffer_write(Buffer, buffer_u8, 55);
ColorBytes += 3;

// Color 3
buffer_write(Buffer, buffer_u8, 55);
buffer_write(Buffer, buffer_u8, 55);
buffer_write(Buffer, buffer_u8, 255);
ColorBytes += 3;

// Color 4
buffer_write(Buffer, buffer_u8, 0);
buffer_write(Buffer, buffer_u8, 0);
buffer_write(Buffer, buffer_u8, 0);
ColorBytes += 3;

// CRC
var PLTECRC = CRC32(Buffer, PLTECRCPos, 4+ColorBytes);
buffer_write(Buffer, buffer_u8, (PLTECRC >> 24) & 255);
buffer_write(Buffer, buffer_u8, (PLTECRC >> 16) & 255);
buffer_write(Buffer, buffer_u8, (PLTECRC >> 8) & 255);
buffer_write(Buffer, buffer_u8, (PLTECRC & 255));


// --------------------------------------------------
// IDAT
// --------------------------------------------------
// Length (Pixels*3)
var IDATLength = Width*Height;
buffer_write(Buffer, buffer_u8, (IDATLength >> 24) & 255);
buffer_write(Buffer, buffer_u8, (IDATLength >> 16) & 255);
buffer_write(Buffer, buffer_u8, (IDATLength >> 8) & 255);
buffer_write(Buffer, buffer_u8, (IDATLength & 255));

// CRC Position
var IDATCRCPos = buffer_tell(Buffer);
var IDATBytes = 0;

// Type
var IDATType = ["I", "D", "A", "T"];
for(i=0; i<4; i++){ buffer_write(Buffer, buffer_u8, ord(IDATType[i])); }

// Data (Bytes): Color Index (1)
// Colors (Width > Height)
buffer_write(Buffer, buffer_u8, 0);
IDATBytes += 1;
buffer_write(Buffer, buffer_u8, 1);
IDATBytes += 1;
buffer_write(Buffer, buffer_u8, 2);
IDATBytes += 1;
buffer_write(Buffer, buffer_u8, 3);
IDATBytes += 1;

// CRC
var IDATCRC = CRC32(Buffer, IDATCRCPos, 4+IDATBytes);
buffer_write(Buffer, buffer_u8, (IDATCRC >> 24) & 255);
buffer_write(Buffer, buffer_u8, (IDATCRC >> 16) & 255);
buffer_write(Buffer, buffer_u8, (IDATCRC >> 8) & 255);
buffer_write(Buffer, buffer_u8, (IDATCRC & 255));


// --------------------------------------------------
// IEND
// --------------------------------------------------
// Length
var IENDLength = 0;
buffer_write(Buffer, buffer_u8, (IENDLength >> 24) & 255);
buffer_write(Buffer, buffer_u8, (IENDLength >> 16) & 255);
buffer_write(Buffer, buffer_u8, (IENDLength >> 8) & 255);
buffer_write(Buffer, buffer_u8, (IENDLength & 255));

// CRC Position
var IENDCRCPos = buffer_tell(Buffer);

// Type
var IENDType = ["I", "E", "N", "D"];
for(i=0; i<4; i++){ buffer_write(Buffer, buffer_u8, ord(IENDType[i])); }

// CRC
var IENDCRC = CRC32(Buffer, IENDCRCPos, 4);
buffer_write(Buffer, buffer_u8, (IENDCRC >> 24) & 255);
buffer_write(Buffer, buffer_u8, (IENDCRC >> 16) & 255);
buffer_write(Buffer, buffer_u8, (IENDCRC >> 8) & 255);
buffer_write(Buffer, buffer_u8, (IENDCRC & 255));

// Save Buffer
buffer_save(Buffer, "test.png");

// Delete Buffers
buffer_delete(Buffer);

感谢您的宝贵时间!

解决方案

编辑:请查看 Mark Adler 对解决方案的评论,那里进行了很好的讨论,他帮助我完成了整个过程。

未创建我的 PNG 的原因是:

  1. 来自 IDAT 块的数据中的每一行都缺少 1 个字节的值,因此它是 6 个字节的数据而不是 4 个。

喜欢:

0 0 1
0 2 3
  1. IDAT 块中的数据缺少 zlib 压缩。如果您使用的是 Game Maker,请不要使用 buffer_compress 函数,而是使用 YellowAfterlife HERE.

    制作的 zlib 扩展
  2. 当我压缩数据时,它向缓冲区添加了 8 个字节。在计算 IDAT 块的长度和 CRC 值时需要考虑这些字节。

确实,您没有实施所需的压缩。此外,每一行都必须以过滤字节为前缀,因此您将压缩六个字节,而不是四个字节。

IDAT 块所需的压缩是 zlib 格式(围绕压缩数据的 zlib 头和尾)。我不知道 zlib 是否或如何在您编程的环境中可用。

对两行都使用零过滤器类型(无过滤),您将压缩的数据是六个字节 0 0 1 0 2 3。一旦一切正常,结果将是十六进制:

89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52
00 00 00 02 00 00 00 02 08 03 00 00 00 45 68 fd
16 00 00 00 0c 50 4c 54 45 ff 37 37 37 ff 37 37
37 ff 00 00 00 5d 2a e4 7e 00 00 00 0e 49 44 41
54 78 9c 63 60 60 64 60 62 06 00 00 11 00 07 9e
a2 2a 12 00 00 00 00 49 45 4e 44 ae 42 60 82 

我想补充一点,GMS2 buffer_compress 确实调用了 zlib compress,所以只要您知道两个 header 字节和尾随 4 就应该很好用CRC32 字节。我过去用它 generate ZIP files.