Skip to content

Latest commit

 

History

History
217 lines (132 loc) · 25 KB

File metadata and controls

217 lines (132 loc) · 25 KB
〖EN〗 【RU】

PyPNM - модуль чтения и записи графических форматов PPM и PGM на чистом Python

PyPI - Downloads

Обзор и предпосылки

PyPNM представляет собой чистый пытоновый модуль для

  • чтения файлов изображений PPM и PGM (как 8, так и 16 бит на канал) в 3D-список для дальнейшего редактирования;

  • отображение полученных 3D-списков путём превращения оных на лету в байт-объекты в памяти, пригодные для отрисовки средствами Tkinter; и в завершение

  • превращение отредактированных 3D списков в памяти в PPM или PGM-файлы на диске, бинарные или ASCII.

Графические форматы PPM (Portable Pixel Map) и PGM (Portable Gray Map) (частные случаи группы форматов PNM) представляют собой простейшие открытые форматы хранения изображений в пространствах RGB и L (серый), соответственно. Как ни странно, эта простота приводит к нежелательным побочным явлениям:

  • Отсутствие чёткой официальной спецификации. Вместо этого описание формата изобилует словами типа "обычно". Не нужно быть пророком, чтобы предсказать, что кто-нибудь в рамках этой расплывчатости напишет файлы ненаказуемым, но извращённым способом.

  • Явное нежелание многих фирменных разработчиков включать поддержку открытого простого формата в свои продукты. От разработчиков Adobe Photoshop потребовались годы, чтобы заметить, что народ качает третьесторонние плагины, то есть зачем-то народу этот формат нужен, после чего, наконец-то взявшись за дело, разработчики незамедлительно поступили вышеуказанным способом, напхав в заголовок схему разделителей, которую не использует больше никто.

    Что касается поддержки PNM в Python, например, таких популярных библиотек, как Pillow, ситуация может быть стабильно описана как "ни в сказке сказать, ни при дамах произнести".

По всей видимости, профессиональные программисты считают, что задача слишком проста, чтобы руки марать. Что ж, пусть считают и дальше. Спасение утопающих - дело рук самих утопающих; поскольку мне нужны модули для чтения и записи графических форматов, желательно не использующие третьесторонних библиотек с их собственными глюками и конфликтами версий, пришлось написать этот небольшой модуль на чистом Python, обеспечивающий чтение и запись изображений 16 бит/канал и 8 бит/канал в пространствах RGB (Portable Pixel Map), серый (L) (Portable Gray Map), а также чтение однобитового формата (Portable Bit Map). Для простоты сведения о совместимости сведены в таблицу ниже.

Совместимость форматов

Текущая версия модуля PyPNM обеспечивает чтение и запись следующих версий форматов группы Netpbm:

Image format File format Read Write
16 бит/канал RGB P6 двоичный PPM
16 бит/канал RGB P3 текстовый PPM
8 бит/канал RGB P6 двоичный PPM
8 бит/канал RGB P3 текстовый PPM
16 бит/канал L P5 двоичный PGM
16 бит/канал L P2 текстовый PGM
8 бит/канал L P5 двоичный PGM
8 бит/канал L P2 текстовый PGM
1 бит (краска есть/нет) P4 двоичный PBM
1 бит (краска есть/нет) P1 текстовый PBM

Совместимость с Python

Текущая версия протестирована под Python 3.10 и выше. Однако существует версия PyPNM для Python 3.4, успешно протестированная с Python 3.4 под Windows XP 32-bit.

Представление изображения

Поскольку целью данного модуля является не передвигание битов по диску, а обеспечение возможности работы с изображениями, например, их редактирования, модуль должен выдавать и принимать данные в виде какой-то логичным образом устроенной структуры. Представляется логичным представлять, например, RGB-изображение в виде трёхмерной структуры - картинки из рядов из пикселей из цветовых каналов. Поскольку в Python практически единственной изменяемой структурой данных для этого является список, предполагается представлять изображения в формате list(list(list(int))). Таким образом, модуль должен превращать данные из не пойми чего на диске в трёхмерные вложенные списки в памяти.

Заметим, что и для серых картинок структурой остаётся list(list(list(int))), при этом внутренний список состоит из одного элемента. С точки зрения профессиональных программистов это безобразие, но людям обыкновенным даёт возможность обрабатывать картинки в любом пространстве одним и тем же вложенным циклом по Y, X, Z, просто указывая количество каналов Z.

Заметим также, что, поскольку конечной целью является всё-таки обработка изображений, данный модуль обеспечивает поддержку 1-битовых форматов PBM только на чтение, при этом масштабирует цвет из 1 бита "краска есть/краски нет" в обычный яркостный L, т.е. инвертирует исходный сигнал (т.к. "краска есть" соответствует "яркости нет"), и умножает на 255.

Установка

В случае использования pip:

python -m pip install --upgrade PyPNM

после чего:

import pypnm

и далее используйте функции, как описано ниже, либо просто посмотрите следующий пример:

Пример

#!/usr/bin/env python3

from tkinter import Button, PhotoImage, Tk

from pypnm import list2bin, list2pnm, pnm2list

X, Y, Z, maxcolors, image3D = pnm2list('example.ppm')  # Open "example.ppm"
list2pnm('binary.ppm', image3D, maxcolors, bin=True)  # Save as binary pnm
list2pnm('ascii.ppm', image3D, maxcolors, bin=False)  # Save as ascii pnm

main_window = Tk()
main_window.title('PyPNM demo')
preview_data = list2bin(image3D, maxcolors)  # Image list 🡢 preview bytes
preview = PhotoImage(data=preview_data)  # Preview bytes 🡢 PhotoImage object
preview_button = Button(main_window, text='Example\n(click to exit)', image=preview,
    compound='top', command=lambda: main_window.destroy())  # Showing PhotoImage
preview_button.pack()
main_window.mainloop()

Выше приведён пример минимальной программы, использующей все функции PyPNM: она читает файл PPM, записывает прочитанную картинку в виде двоичного PPM, записывает её же в виде ASCII PPM, а напоследок показывает её на экране средствами Tkinter.

Описание функций

Модуль PyPNM содержит 100% чистую реализацию на Python всего, что может понадобиться для работы с файлами PGM и PPM. Ввод/вывод реализован в форме максимально простых в обращении функций, использование которых описано ниже. Также использование функций описано в docstrings, так что потерять инструкцию к модулю у вас вряд ли получится.

  • pnm2list - читает RGB PPM, или L PGM, или 1-бит PBM-файл, и возвращает данные в форме вложенного трёхмерного списка целых чисел.
  • list2bin - получает изображение в форме вложенного трёхмерного списка целых чисел, и создаёт в памяти байтовую структуру типа PPM (P6) или PGM (P5). Полученная структура может быть легко использована для визуализации изображений с помощью Tkinter.
  • list2pnm - получает изображение в форме вложенного трёхмерного списка целых чисел, и записывает на диск либо двоичный PPM (P6) или PGM (P5) файл, либо ASCII PPM (P3) или PGM (P2), в зависимости от аргумента-переключателя.

Подробное описание аргументов функций приведено ниже,а также в docstring-ах модуля.

pnm2list

X, Y, Z, maxcolors, image3D = pypnm.pnm2list(in_filename)

чтение данных из файла PPM/PGM, где:

  • X, Y, Z - размеры изображения, int;
  • maxcolors - количество цветов на канал, int;
  • image3D - собственно данные пикселей, list(list(list(int)));
  • in_filename - имя файла PPM/PGM, str.

list2bin

image_bytes = pypnm.list2bin(image3D, maxcolors, show_chessboard)

Превращает изображение в форме вложенного трёхмерного списка целых чисел в байтовый объект типа PPM (P6) или PGM (P5) в памяти:

  • image3D - Y*X*Z список (изображение) списков (рядов) списков (пикселей) целых чисел (каналов);
  • maxcolors - количество цветов на канал, int;
  • show_chessboard - bool, при установке True генерирует превью LA и RGBA-картинок на фоне шахматной паттерны; False или отсутствие переменной приводит к простому игнорированию альфа-канала. Значение по умолчанию False для задней совместимости;
  • image_bytes - байты PNM.

Полученный объект image_bytes совместим с методом Tkinter PhotoImage(data=...) и предназначен для визуализации любых данных, похожих на картинку в виде 3D-списка.

Note

В случае списков с 2 или 4 каналами свежая версия list2bin может считать их LA или RGBA картинками, и генерировать для них превью на фоне шахматной паттерны (как Photoshop или GIMP). Поскольку форматы PNM не поддерживают прозрачности, данная картинка на самом деле имеет структуру L или RGB, а паттерну генерирует и подмешивает на лету сама функция list2bin. Данное поведение контролируется переменной show_chessboard; текущая установка False (просто выбрасывает альфа-канал) для совместимости со старыми версиями PyPNM, в которых такой опции не было.

list2pnm

pypnm.list2pnm(out_filename, image3D, maxcolors, bin)

Запись картинки в бинарный или ASCII файл:

  • image3D - Y*X*Z список (изображение) списков (рядов) списков (пикселей) целых чисел (каналов);
  • maxcolors - количество цветов на канал, int;
  • bin - переключатель (bool) между записью двоичного и ASCII файла. Текущее положение True, то есть запись двоичного.
  • out_filename - имя файла PNM.

Заметьте, что list2pnm представляет собой просто переключатель между внутренними функциями list2pnmbin and list2pnmascii, облегчающий написание функций типа "Save as...", особенно для графических диалогов - можно сохранять разные типы файлов одной функцией, просто передавая bin через lambda-функцию.

create_image

image3D = create_image(X, Y, Z)

Создаёт пустой трёхмерный список размеров X*Y*Z.

viewer.py

Программа viewer.py представляет собой небольшой иллюстративный пример использования PyPNM: с помощью PyPNM она читает разные форматы файлов PGM и PPM, и позволяет сохранять их в другом формате PGM/PNM, например, позволяет прочитать текстовый PPM и записать изображение в двоичный PPM, или наоборот. Также эта программа отображает изображения с помощью PyPNM и Tkinter. Это не ошибка - программа не скармливает PPM-файл Tkinter напрямую; вместо этого с помощью PyPNM она раскрывает изображение в трёхмерный список, затем с помощью PyPNM же генерирует из этого списка байты PNM preview_data = pypnm.list2bin(image3D, maxcolors), а только после этого передаёт эти байты в Tkinter preview = PhotoImage(data=preview_data) (именно data=, а не file=). Таким образом программа отображает, например, текстовые PNM, которые сам Tkinter не поддерживает.

Рис. 1. Пример отрисовки текстового .ppm с помощью Viewer.py
Пример отрисовки текстового .ppm с помощью Viewer.py

Помимо минималистического GUI с использованием мышки а-ля Photoshop, viewer.py также в состоянии переварить аргументы командной строки

python viewer.py filename.ppm

для открытия файлов. В принципе, вы можете даже зарегистрировать его в качестве системного смотрела для файлов PPM, PGM и PBM, и стать первым человеком на этой планете с системным смотрелом, написанном на Python.

Note

Начиная с версии 2.21.22.23 Viewer снабжен пунктом "Export via Tkinter..." а основном меню, позволяющим сохранить открытое изображение с помощью 100% натуральных экологически чистых механизмов Tkinter PhotoImage.write. Данная опция включена исключительно для иллюстрации ограничений Tkinter. Например, изображение с цветовой глубиной 16 бит на канал после сохранения таким способом превращается в 8 бит на канал, потому что иначе Tkinter не умеет. Это несколько объясняет одну из причин, по которым я был вынужден написать свой собственный модуль, которому всё равно, что у вас за картинка - он обработает её правильно.

Заключение

Использование PyPNM и Tkinter позволяет легко визуализировать любые данные, которые могут быть представлены в виде L/RGB изображений, и в первую очередь собственно картинки.

PyPNM обеспечивает чтение и запись файлов PPM и PGM с цветовой глубиной как 8б так и 16 бит на канал, а также чтение однобитовых PBM.

Используемая PyPNM структура данных (вложенные списки) облегчает написание алгоритмов обработки изображений.

Ссылки

  1. Описание форматов Netpbm.

  2. PyPNM на PyPI - установка PyPN с помощью pip. Не включает примеров и т.п., только голый пакет ввода/вывода, зато pip обеспечивает автоматизацию обновлений.

  3. PyPNM на Github - содержит пример приложения для просмотра, иллюстрирующий применение list2bin для визуализации данных с помощью Tkinter `PhotoImage(data=...), и конверсию изображений между форматами.

  4. PyPNM ver.34 на Github - то же, что и п.3, но работает под Python 3.4.

  5. PyPNM docs (PDF). Текущая версия документации относится к выпуску 9 мая 2025 года "Victory", но, поскольку капитальная модернизация, проведённая в версии 2 сентября 2025 года "Victory II", не затрагивает структуру ввода и вывода, документация действительна и для новых версий. Новая версия делает всё то же самое, просто старается жрать меньше ресурсов.

Иллюстрации

PixelArtScaling - пример применения, масштабирование изображений методами Scale2x и Scale3x на чистом Python, ввод/вывод PNG основан на PyPNG, а PNM - на PyPNM, что делает приложения кросс-платформенными.

«Averager» - программа фильтрования изображений методом адаптивного усреднения (написана, поскольку именно такой алгоритм был нужен для специфической цели, а в больших программах типа Photoshop он отсутствует), превью "до" и "после" основано на коде PyPNM list2bin и на классе Tkinter PhotoImage(data=...).

Рис. 2. Приложение для фильтрования изображений, изрядно основанное на использовании PyPNM
Пример приложения для фильтрования изображений с применением PyPNM

Собственно фильтр построен на простых вложенных циклах и map(), благодаря создаваемой PyPNM структуре вложенных списков обеспечивающих обработку картинок в разных цветовых пространствах одним незатейливым алгоритмом. Таким образом, представлено небольшое, но полноценное интерактивное приложение для фильтрования изображений, реализованное исключительно на Python.

Модуль imin для билинейной и барицентрической интерполяции изображений, written entirely in Python. Sample applications are largely based on PyPNM.

Рис. 3. Приложение для фильтрования/деформации изображений, изрядно основанное на использовании PyPNM
Пример приложения для фильтрования изображений с применением PyPNM

Как и в случае предыдущих программ, модуль использует тот факт, что структура изображений как вложенных списков, создаваемая PyPNM, позволяет обрабатывать картинки с любым количеством каналов одной функцией map(); программы-оболочки показывают превью "до" и "после" с помощью кода PyPNM list2bin и класса Tkinter PhotoImage(data=...).


Dnyarri website - больше программ на Python и остальной товар от Жабы Огромной Умственной Силы.