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