Найти в Дзене

PNG своими руками. Пишу файл на Python. Как устроен PNG.

Оглавление

Предисловие

Png - простой формат изображений, знакомый всем. Я расскажу общие вещи о его начинке, покажу как создать png файл в пару десятков строк кода на Python. Весь код примера по ссылке здесь и в конце статьи. Для создания файла используются функции модуля zlib. Я буду писать вот эту картинку (Изображение увеличено в двести раз).

Самодельная картинка x 200.
Самодельная картинка x 200.

Из чего состоит png

Png файл состоит из подписи и блоков данных кодирующих информацию. Изображение PNG должно содержать блок IHDR , один или несколько блоков IDAT и блок IEND . О других блоках читайте спецификацию, пункт 4.2 , ссылка в конце.

Подпись png

Подпись файла - 8 байт сообщающих тип файла. Больше можно узнать в спецификации, пункт 12.12 .

  • 137 80 78 71 13 10 26 10 (десятичные числа)
  • 89 50 4e 47 0d 0a 1a 0a (шестнадцатеричные числа)

Макет блока

  • Длинна 4 байта - целочисленная длинна исключительно данных блока, может быть нулём.
  • Имя 4 байта - тип блока , используются символы a-z, A-Z .Больше можно узнать в спецификации, пункт 3.3 .
  • Данные - байты данных, соответствующие типу блока, если есть, может быть нулевой длины.
  • CRC 4 байта - CRC (Cyclic Redundancy Check), вычисленный для байтов в блоке, включая имя и данные, но не включая поле длины. Для расчета использую функцию zlib.crc32 .

Блок IHDR

Данные блока IHDR содержат 4 байта ширины картинки, 4 байта высоты картинки, байт - значение глубины цвета, байт - тип цвета, 3 байта - нули, означают метод сжатия, метод фильтрации, метод развёртки (0 - одно возможное значение для них).

Пример :

>>> IHDRdata = (
b'\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00'
)
>>> # Тип цвета 02 - RGB
>>> # О других типах читайте спецификацию
>>> # пункт 4.1.1

Блок IEND

Блок IEND не содержит данных, его длинна равна нулю.

Блок IDAT

Данные блока IDAT - сжатый макет изображения, для сжатия используется функция zlib.compress .

>>> import zlib
>>> IDATdata = (
b'\x00\xdd\x88\xdd\x44\xff\xff\x00\x44\xff\xff\xdd\x88\xdd'
)
>>> IDATdata = zlib.compress(IDATdata)
>>> # тест сжатия
>>> zlib.decompress(IDATdata)
b'\x00\xdd\x88\xddD\xff\xff\x00D\xff\xff\xdd\x88\xdd'
>>> # \x44 -> D
>>> b'\x44' == b'D'
True

Макет изображения

Каждый пиксель цветной картинки кодируется тремя числами, на три цветовых канала : красный, зелёный, и синий.

Цветовые каналы изображения.
Цветовые каналы изображения.

Для лучшего сжатия применяются различные методы обработки, я не использую эти методы, поэтому перед каждой строкой пикселей ставлю нулевой байт (0 - методы не применены), о других флагах и методах читайте в спецификации.

Добавлены нулевые байты.
Добавлены нулевые байты.

Данные изображения представляются строкой состоящей из строк картинки.

Строки картинки в одной строке.
Строки картинки в одной строке.

Собираем общую строку данных.

0 221 136 221 68 255 255 0 68 255 255 221 136 221

Переводим в шестнадцатеричную систему.

>>> layout = (
b'\x00\xdd\x88\xdd\x44\xff\xff\x00\x44\xff\xff\xdd\x88\xdd'
)

Сборка картинки

Ссылка на код.

filename = 'firstpng.png'
import zlib
with open(filename, 'wb') as file:
signature = b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
IHDRname = b'IHDR'
IHDRdata = b'\x00\x00\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00'
IHDRlen = len(IHDRdata).to_bytes(4, 'big')
IHDRcrc = zlib.crc32(IHDRname + IHDRdata).to_bytes(4, 'big')
IHDR = IHDRlen + IHDRname + IHDRdata + IHDRcrc
IDATname = b'IDAT'
IDATdata = zlib.compress(
b'\x00\xdd\x88\xdd\x44\xff\xff\x00\x44\xff\xff\xdd\x88\xdd'
)
IDATlen = len(IDATdata).to_bytes(4, 'big')
IDATcrc = zlib.crc32(IDATname + IDATdata).to_bytes(4, 'big')
IDAT = IDATlen + IDATname + IDATdata + IDATcrc
IENDname = b'IEND'
IENDlen = b'\x00\x00\x00\x00'
IENDcrc = zlib.crc32(IENDname).to_bytes(4, 'big')
IEND = IENDlen + IENDname + IENDcrc
bytecode = signature + IHDR + IDAT + IEND
file.write(bytecode)

Ссылки

Спецификация png

Github - код и картинка