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!
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:
Like:
0 0 1
0 2 3
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.
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.
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