https://github.com/dnyarri/pypnm
Pure Python PGM and PPM image files read/write support, both 8 and 16 bits per channel.
https://github.com/dnyarri/pypnm
bitmap image image-format pgm pgm-format pgm-image pnm pnm-format pnm-image ppm ppm-format ppm-image python python-3 python-library python3
Last synced: 3 months ago
JSON representation
Pure Python PGM and PPM image files read/write support, both 8 and 16 bits per channel.
- Host: GitHub
- URL: https://github.com/dnyarri/pypnm
- Owner: Dnyarri
- License: unlicense
- Created: 2024-12-02T07:55:15.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2026-02-23T17:19:59.000Z (4 months ago)
- Last Synced: 2026-02-24T00:59:19.290Z (3 months ago)
- Topics: bitmap, image, image-format, pgm, pgm-format, pgm-image, pnm, pnm-format, pnm-image, ppm, ppm-format, ppm-image, python, python-3, python-library, python3
- Language: Python
- Homepage: https://dnyarri.github.io/pypnm.html
- Size: 822 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.RU.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
| [〖EN〗](README.md "Приблизительно тот же трёп, но по-английски") | 【RU】 |
| ---- | ---- |
# PyPNM - модуль чтения и записи графических форматов PPM и PGM на чистом Python
[](https://pypi.org/project/PyPNM/)
## Обзор и предпосылки
PyPNM представляет собой чистый пытоновый модуль для
- чтения файлов изображений PPM и PGM (как 8, так и 16 бит на канал) в 3D-список для дальнейшего редактирования;
- отображение полученных 3D-списков путём превращения оных на лету в байт-объекты в памяти, пригодные для отрисовки средствами Tkinter; и в завершение
- превращение отредактированных 3D списков в памяти в PPM или PGM-файлы на диске, бинарные или ASCII.
Графические форматы PPM ([Portable Pixel Map](https://netpbm.sourceforge.net/doc/ppm.html)) и PGM ([Portable Gray Map](https://netpbm.sourceforge.net/doc/pgm.html)) (частные случаи группы форматов PNM) представляют собой простейшие открытые форматы хранения изображений в пространствах RGB и L (серый), соответственно. Как ни странно, эта простота приводит к нежелательным побочным явлениям:
- Отсутствие чёткой официальной спецификации. Вместо этого описание формата изобилует словами типа "обычно". Не нужно быть пророком, чтобы предсказать, что кто-нибудь в рамках этой расплывчатости напишет файлы ненаказуемым, но извращённым способом.
- Явное нежелание многих фирменных разработчиков включать поддержку открытого простого формата в свои продукты. От разработчиков Adobe Photoshop потребовались годы, чтобы заметить, что народ качает третьесторонние плагины, то есть зачем-то народу этот формат нужен, после чего, наконец-то взявшись за дело, разработчики незамедлительно поступили вышеуказанным способом, напхав в заголовок схему разделителей, которую не использует больше никто.
Что касается поддержки PNM в Python, например, таких популярных библиотек, как Pillow, ситуация может быть стабильно описана как "ни в сказке сказать, ни при дамах произнести".
По всей видимости, профессиональные программисты считают, что задача слишком проста, чтобы руки марать. Что ж, пусть считают и дальше. Спасение утопающих - дело рук самих утопающих; поскольку мне нужны модули для чтения и записи графических форматов, желательно не использующие третьесторонних библиотек с их собственными глюками и конфликтами версий, пришлось написать этот небольшой модуль на чистом Python, обеспечивающий чтение и запись изображений 16 бит/канал и 8 бит/канал в пространствах RGB ([Portable Pixel Map](https://netpbm.sourceforge.net/doc/ppm.html)), серый (L) ([Portable Gray Map](https://netpbm.sourceforge.net/doc/pgm.html)), а также чтение однобитового формата ([Portable Bit Map](https://netpbm.sourceforge.net/doc/pbm.html)). Для простоты сведения о совместимости сведены в таблицу ниже.
## Совместимость форматов
Текущая версия модуля 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](https://github.com/Dnyarri/PyPNM/tree/py34/), успешно протестированная с 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:
```console
python -m pip install --upgrade PyPNM
```
после чего:
```python
import pypnm
```
и далее используйте функции, как описано ниже, либо просто посмотрите следующий пример:
## Пример
```python
#!/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**](https://github.com/Dnyarri/PyPNM/blob/main/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* |
| :---: |
| [](https://dnyarri.github.io/pypnm.html) |
Помимо минималистического GUI с использованием мышки а-ля Photoshop, *viewer.py* также в состоянии переварить аргументы командной строки
```console
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](https://netpbm.sourceforge.net/doc/).
2. [PyPNM на PyPI](https://pypi.org/project/PyPNM/) - установка PyPN с помощью `pip`. Не включает примеров и т.п., только голый пакет ввода/вывода, зато `pip` обеспечивает автоматизацию обновлений.
3. [PyPNM на Github](https://github.com/Dnyarri/PyPNM/) - содержит пример приложения для просмотра, иллюстрирующий применение `list2bin` для визуализации данных с помощью Tkinter `PhotoImage(data=...), и конверсию изображений между форматами.
4. [PyPNM ver.34 на Github](https://github.com/Dnyarri/PyPNM/tree/py34) - то же, что и п.3, но работает под Python 3.4.
5. [PyPNM docs (PDF)](https://dnyarri.github.io/pypnm/pypnm.pdf). Текущая версия документации относится к выпуску 9 мая 2025 года "Victory", но, поскольку капитальная модернизация, проведённая в версии 2 сентября 2025 года "Victory II", не затрагивает структуру ввода и вывода, документация действительна и для новых версий. Новая версия делает всё то же самое, просто старается жрать меньше ресурсов.
## Иллюстрации
[PixelArtScaling](https://dnyarri.github.io/scalenx.html) - пример применения, масштабирование изображений методами Scale2x и Scale3x на чистом Python, ввод/вывод PNG основан на [PyPNG](https://gitlab.com/drj11/pypng), а PNM - на [PyPNM](https://pypi.org/project/PyPNM/), что делает приложения кросс-платформенными.
[«Averager»](https://dnyarri.github.io/povthread.html#averager) - программа фильтрования изображений методом адаптивного усреднения (написана, поскольку именно такой алгоритм был нужен для специфической цели, а в больших программах типа Photoshop он отсутствует), превью "до" и "после" основано на коде PyPNM `list2bin` и на классе Tkinter `PhotoImage(data=...)`.
| Рис. 2. *Приложение для фильтрования изображений, изрядно основанное на использовании PyPNM* |
| :---: |
| [](https://dnyarri.github.io/povthread.html#averager) |
Собственно фильтр построен на простых вложенных циклах и `map()`, благодаря создаваемой PyPNM структуре вложенных списков обеспечивающих обработку картинок в разных цветовых пространствах одним незатейливым алгоритмом. Таким образом, представлено небольшое, но полноценное интерактивное приложение для фильтрования изображений, реализованное исключительно на Python.
[Модуль **imin** для билинейной и барицентрической интерполяции изображений](https://dnyarri.github.io/imin.html), written entirely in Python. Sample applications are largely based on PyPNM.
| Рис. 3. *Приложение для фильтрования/деформации изображений, изрядно основанное на использовании PyPNM* |
| :---: |
| [](https://dnyarri.github.io/imin.html "Пример приложения для фильтрования изображений с применением PyPNM") |
Как и в случае предыдущих программ, модуль использует тот факт, что структура изображений как вложенных списков, создаваемая PyPNM, позволяет обрабатывать картинки с любым количеством каналов одной функцией `map()`; программы-оболочки показывают превью "до" и "после" с помощью кода PyPNM `list2bin` и класса Tkinter `PhotoImage(data=...)`.
---
[Dnyarri website - больше программ на Python](https://dnyarri.github.io/) и остальной товар от Жабы Огромной Умственной Силы.