Как создать PNG (8-битные индексированные цвета) с использованием буферов? Получение «контрольной суммы заголовка сжатия не выполняется»

#buffer #png #zlib #gml #game-maker-studio-2

#буфер #png #zlib #gml #game-maker-studio-2

Вопрос:

как дела?

Я пытаюсь создать PNG с нуля в Game Maker Studio 2, используя буферы, но я получаю некоторые ошибки при использовании 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, установить разрядность на 8, тип цвета на 3 и т.д. и добавить все остальные фрагменты данных.

Мне также удалось заставить поле CRC работать, добавив скрипт CRC32 в Game Maker для вычисления правильных байтов.

Чего я пытаюсь добиться, так это создать простое изображение 2×2 с 4 разными цветами, используя цвета из фрагмента PLTE, но я получаю один красный квадрат (2×2), а также ошибки, указанные выше, из 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) amp; 255);
buffer_write(Buffer, buffer_u8, (IHDRLength >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (IHDRLength >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (IHDRLength amp; 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) amp; 255);
buffer_write(Buffer, buffer_u8, (Width >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (Width >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (Width amp; 255));

// Height
var Height = 2;
buffer_write(Buffer, buffer_u8, (Height >> 24) amp; 255);
buffer_write(Buffer, buffer_u8, (Height >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (Height >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (Height amp; 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) amp; 255);
buffer_write(Buffer, buffer_u8, (IHDRCRC >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (IHDRCRC >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (IHDRCRC amp; 255));


// --------------------------------------------------
// PLTE
// --------------------------------------------------
// Length (Colors*3)
var PLTELength = 4*3;
buffer_write(Buffer, buffer_u8, (PLTELength >> 24) amp; 255);
buffer_write(Buffer, buffer_u8, (PLTELength >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (PLTELength >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (PLTELength amp; 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) amp; 255);
buffer_write(Buffer, buffer_u8, (PLTECRC >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (PLTECRC >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (PLTECRC amp; 255));


// --------------------------------------------------
// IDAT
// --------------------------------------------------
// Length (Pixels*3)
var IDATLength = Width*Height;
buffer_write(Buffer, buffer_u8, (IDATLength >> 24) amp; 255);
buffer_write(Buffer, buffer_u8, (IDATLength >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (IDATLength >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (IDATLength amp; 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) amp; 255);
buffer_write(Buffer, buffer_u8, (IDATCRC >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (IDATCRC >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (IDATCRC amp; 255));


// --------------------------------------------------
// IEND
// --------------------------------------------------
// Length
var IENDLength = 0;
buffer_write(Buffer, buffer_u8, (IENDLength >> 24) amp; 255);
buffer_write(Buffer, buffer_u8, (IENDLength >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (IENDLength >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (IENDLength amp; 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) amp; 255);
buffer_write(Buffer, buffer_u8, (IENDCRC >> 16) amp; 255);
buffer_write(Buffer, buffer_u8, (IENDCRC >> 8) amp; 255);
buffer_write(Buffer, buffer_u8, (IENDCRC amp; 255));

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

// Delete Buffers
buffer_delete(Buffer);
 

Спасибо за ваше время!

РЕШЕНИЕ

РЕДАКТИРОВАТЬ: пожалуйста, посмотрите на комментарий Марка Адлера для решения, там была хорошая дискуссия, и он помог мне пройти весь процесс.

Причина, по которой мой PNG не создавался, была:

  1. Для каждой строки в данных из блока IDAT отсутствовало значение в 1 байт, поэтому это 6 байт данных вместо 4.

Нравится:

 0 0 1
0 2 3
 
  1. В данных из фрагмента IDAT отсутствовало сжатие zlib. Если вы используете Game Maker, не используйте функцию buffer_compress, вместо этого используйте расширение zlib, созданное YellowAfterlife ЗДЕСЬ .
  2. Когда я сжал данные, они добавили 8 байт в буфер. Эти байты необходимо учитывать при вычислении значений ДЛИНЫ и CRC блока IDAT.

Ответ №1:

Действительно, вы не выполняете требуемое сжатие. Также каждая строка должна иметь префикс байта фильтра, поэтому вы будете сжимать шесть байтов, а не четыре.

Требуемое сжатие для фрагментов IDAT — формат zlib (заголовок zlib и трейлер вокруг данных, сжатых с помощью deflate). Я не знаю, доступен ли или как 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 
 

Комментарии:

1. Привет @Mark Adler, спасибо за ответ. Итак, для каждой строки я должен добавить 2 значения U8 (2 байта), правильно? Поскольку я использую тип цвета 3 (индексированный цвет), разве это не должно быть 3 байта вместо 6? (2 для фильтра, 1 для индекса цвета?). К сожалению, у меня нет доступа к библиотекам в Game Maker, но есть функция «buffer_compress», которую мне нужно просмотреть, чтобы убедиться, что она работает в первую очередь. Ссылка

2. Данные блока IDAT представляют собой zlib steam. Более правильно, если имеется несколько блоков IDAT, конкатенация данных блока IDATs представляет собой единый поток zlib. Эти данные представляют собой не только пиксели, но и один байт фильтра в начале каждой строки.

3. В начале каждой строки один байт фильтра. Для типа цвета 3 каждый пиксель равен одному байту, который является индексом в палитре. Таким образом, изображение 2×2 будет составлять шесть байтов.

4. Вычислите CRC для «IDAT» сжатых данных. Используйте длину сжатых данных для длины фрагмента.

5. Да! Я сделал все, что вы мне сказали, и теперь он создает 8-битный PNG с индексированными цветами, как и ожидалось, большое вам спасибо! Я только что проверил некоторые документы и увидел в них ваше имя, я не знал, что вы один из создателей формата PNG, какая честь! Извлеченные уроки: не забывайте о фильтре в 1 байт в каждой строке данных в IDAT, обращайте внимание на длину и правильно вычисляйте CRC (учитывайте сжатие), и если вы используете Game Maker, используйте расширение ZLIB от YellowAfterlife для сжатия данных IDAT вместо использованияфункция «buffer_compress».

Ответ №2:

Я хотел бы добавить, что GMS2 buffer_compress вызывает zlib compress , поэтому его следует использовать, если вы знаете о двух байтах заголовка и конечных 4 байтах CRC32. В прошлом я использовал его для создания ZIP-файлов.