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

Как вы?

Я пытаюсь создать 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 для вычисления правильных байтов.

Чего я пытаюсь добиться, так это создать простое изображение 2x2 с 4 разными цветами, используя цвета из фрагмента PLTE, но я получаю один красный квадрат (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);

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

РЕШЕНИЕ

Обновлено: пожалуйста, посмотрите на комментарий Марка Адлера для решения, там была хорошая дискуссия, и он помог мне на протяжении всего процесса.

Причина, по которой мой PNG не создавался, заключалась в следующем:

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

Нравиться:

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

  2. Когда я сжимал данные, в буфер добавлялось 8 байт. Эти байты необходимо учитывать при расчете значений LENGTH и CRC фрагмента IDAT.

Почему в Python есть оператор &quot;pass&quot;?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
234
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Необходимым сжатием для фрагментов 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 

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

IuriNery 18.12.2020 23:48

Кроме того, когда вы говорите «сжатие для фрагментов IDAT», вы имеете в виду весь фрагмент или только данные пикселей? Нужно ли применять сжатие zlib ко всему блоку IDAT, включая длину, тип, данные и CRC?

IuriNery 19.12.2020 00:28

Данные фрагмента IDAT представляют собой поток zlib. Точнее, если есть несколько фрагментов IDAT, объединение данных фрагмента IDAT представляет собой один поток zlib. Эти данные представляют собой не только пиксели, но и один байт фильтра в начале каждой строки.

Mark Adler 19.12.2020 00:40

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

Mark Adler 19.12.2020 00:46

Хорошо, я сделал то, что вы сказали о добавлении 1 байта для фильтра для каждой строки, а также попытался использовать функцию «buffer_compress», предоставленную Game Maker, но она не сработала, поэтому я нашел расширение от YellowAfterlife на Marketplace (zlib сжатие) и вроде сработало! Я сжал данные из IDAT (уровень 8). Я могу передать сообщение «сбой контрольной суммы заголовка сжатия», и теперь оно говорит: «zlib: дефлированный, окно 32 КБ, максимальное сжатие», так что на данный момент мы можем предположить, что сжатие сработало? Проблема в том, что теперь я получаю ошибку CRC в чанке IDAT :(

IuriNery 19.12.2020 01:51

Кстати, это журнал checkpng: File: [01;37mtest.png[0m (123 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 13 zlib: deflated, 32K window, maximum compression CRC error in chunk IDAT (computed d668a65c, expected ef6c8c7d) [01;31mERRORS DETECTED[0m in test.png

IuriNery 19.12.2020 01:52

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

Mark Adler 19.12.2020 07:47

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

IuriNery 19.12.2020 16:36

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

Другие вопросы по теме