bufferpngzlibgmlgame-maker-studio-2

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


how are you?

I'm trying to make a PNG from scratch on Game Maker Studio 2 using Buffers, but I'm getting some errors using 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)

I'm posting the code below to show what I got so far. As you can see, I've managed to add the IHDR header, set the Bit Depth to 8, Color Type to 3 etc. and add all the other chunks of data.

I also managed to get the CRC field working by adding a CRC32 script in Game Maker to calculate the correct bytes.

What I'm trying to achieve is create a simple 2x2 image with 4 different colors using the colors from the PLTE chunk, but what I'm getting is a single red square (2x2) and also the errors above from pngcheck.

I think I'm missing the "compression" part of it, it would be very helpful if someone could help me with this.

// 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);

Thanks for your time!

SOLUTION

EDIT: please look at the comment from Mark Adler for the solution, there was a nice discussion there and he helped me through all the process.

The reason why my PNG was not being created was:

  1. It was missing a 1 byte value for each row in the data from the IDAT chunk, so it's 6 bytes of data instead of 4.

Like:

0 0 1
0 2 3
  1. It was missing the zlib compression in the data from the IDAT chunk. If you're using Game Maker, do not use the buffer_compress function, instead, use the zlib extension made by YellowAfterlife HERE.

  2. When I compressed the data, it added 8 bytes to the buffer. These bytes needs to be taken into consideration when you're calculating the LENGTH and CRC values of the IDAT chunk.


Solution

  • Indeed, you are not implementing the required compression. Also each row must be prefixed by a filter byte, so you will compressing six bytes, not four.

    The required compression for the IDAT chunks is the zlib format (a zlib header and trailer around deflate-compressed data). I do not know if or how zlib is made available in the environment you are programming in.

    Using a filter type of zero (no filtering) for both rows, the data you will compress is the six bytes 0 0 1 0 2 3. Once you get it all right, the result will be, in hex:

    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