Как подружить графику и python?

Постановка задачи

В этой главе нашей задачей станет разработка собственного фоторедактора, который сможет обрабатывать наши фотографии авторскими и классическими фотофильтрами. Конечно, создать полноценный собственный фотошоп мы пока готовы, но консольный инструмент для повседневного использования - без проблем!

Прежде чем мы сможем приступить к реализации проекта, как и в случае с текстовым квестом, нам понадобится некоторая теоретическая основа. Мы уже успели разобраться, как устроен компьютер и как мы можем использовать языки программирования для управления им, узнали, что такое переменные, арифметические операции и условия, и даже слегка коснулись функций и модулей. Для фоторедактора нам потребуется в общих чертах понять, как компьютер кодирует и хранит изоражения, и как их можно менять, используя циклы и модуль Pillow (который потребуется дополнительно установить через pip).

Иными словами, вырисовывается следующий план действий.

  • Как устроено изображение? Растровая и векторная графика, пиксели, модель RGB.
  • Как компьютер хранит изображения? Объем картинки и разные форматы сжатия.
  • Какие бывают фотофильтры? Цветовые и матричные фильтры.
  • Как открыть картинку через Python? Установка сторонних библиотек.
  • Как обработать сразу все пиксели? Знакомимся с циклами и тренируемся их использовать.
  • Пишем классические фильтры.
  • Пишем авторские фильтры и собираем все в один большой редактор.

Как устроена картинка?

В компьютере существует два принципиально разных вида графики:

  • растровая - картинка состоит из точек разного цвета (реалистичные изображения и фотографии, качество снижается при увеличении размера);
  • векторная - картинка состоит из геометрических фигур - примитивов (рекламная продукция, чертежи, логотипы, качество не портится при увеличении размера).

Чаже всего мы имеем дело именно с растровой графикой (фотографии), а поэтому далее рассмотрим именно ее.

Растровое изображение - это таблица из точек, каждая из которых имеет цвет. Каждая точка называется пикселем.

Если пиксель имеет цвет, то этот цвет надо его как-то записать. Это можно сделать по-разному, например, просто перечислить все возможные цвета и раздать им номера. Если цветов в пределах десятка, то это проще всего. Но вот в более реалистичных изображениях, чтобы передать лицо человека или пейзаж, нам нужно гораздо больше цветов, каждому и которых сложно придумать название. Поэтому обычно используют классический трюк художников - использовать палитру - т.е. взять несколько базовых цветов и смешивать их между собой.

Чаще всего цвет задают, как комбинацию трех основных цветов - красного, зеленого и синего. Такая модель называется RGB (Red/Green/Blue). В модели RGB цвет каждой точки задается тремя числами в диапазонее от 0 до 255 - яркостью красного, зеленого и синего цветов в этом пикселе.

Иногда пиксель содержит еще и четвертое число - прозрачность (тоже от 0 до 255), тогда такая модель называется RGBA (RGB + alpha канал). Но на практике такое бывает только в файлах формата .png и .gif.

Рассмотрим примеры:

  • (255, 0, 0) - красный цвет;
  • (0, 255, 0) - зеленый цвет;
  • (0, 0, 255) - синий цвет;
  • (0, 0, 0) - все яркости на нуле, черный цвет;
  • (255, 255, 255) - белый цвет.

Сколько весит изображение?

Итак, картинка состоит из пикселей, а каждый пиксель можно записать числами. Здорово, значит мы можем перевести это все в двоичную систему и последовательно записать на жесткий диск (а перед этим записать техническую информацию про файл и его размер). А сколько все это будет весить?

Картинка имеет ширину и высоту, измеряемые в числе пикселей. Значит, если мы знаем размер одного пикселя, то сможем вычислить размер всей картинки, умножив вес пикселя на площадь. Осталось понять, сколько весит пиксель.

Вспомним, что пиксель - это три числа, каждое в диапазоне от 0 до 255. Если перевести крайние значения в двоичную систему, получится от 0 до 11111111. Выходит, что для записи одного цвета достаточно 8 бит (1 байта), а значит для трех цветов нужно 3 байта. Значит, если у нас есть картинка 100 на 100 пикселей, она должна весить около 30000 байт (~30кб).

Проверим наши теоретические изыскания на практике, создав такое изображение в фоторедакторе.

При сохранении у нас спрашивают формат. Пока не знаем, что это, для эксперимента попробуем взять несколько, например, 24-битный bmp и png.

А теперь попробуем спросить у системы, сколько весят наши картинки...

Что мы видим? Картинка в bmp весит 30056 байт (30000 байт пикселей + 56 байт технических данных о картинке), а вот картинка в png весит в три раза меньше, всего 12 кб. Что произошло, куда делись две трети картинки?

Дело в том, что [bmp](https://ru.wikipedia.org/wiki/BMP#targetText=BMP%20(%D0%BE%D1%82%20%D0%B0%D0%BD%D0%B3%D0%BB.%20Bitmap%20Picture,%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B%20Windows%20%D0%B8%20OS%2F2.) - это формат хранения без сжатия, файл в этом формате весит ровно столько, сколько картинка в оперативном памяти. А вот png перед сохранением сжимает изображение.

Сжатие, кстати, бывает двух видов: с потерями и без. Сжатие без потерь (png в частности) делает картинку меньше, но не изменяет ее, а вот сжатие с потерями может сделать картинку еще меньше, но немного (или даже сильно) изменяет исходное изображение. Проще всего проследить этот эффект на примере формата JPEG, меняя степень сжатия.

Зачем об этом помнить программисту фоторедакторов? Если мы хотим реализовать программу, работающую с графикой совсем с нуля, придется разбираться, как внутри устроены все возможные виды графических форматов.

Неужели правда придется копаться в техническом описании всех форматов сжатия? На самом деле нет, ведь нам сильно повезло: мы работаем на питоне, языке с очень развитым сообществом, а значит, кто-то уже наверняка решал такую задачу и поделился своим решение с нами в виде готовой библиотеки.

Далее мы научимся работать со сторонними библиотеками и установим Pillow - самую известную библиотеку для работы с графикой на питоне.

Модули в Python

"Ничего нельзя придумать. Все, что ты придумываешь, либо было придумано до тебя, либо происходит на самом деле." - известная цитата из широкоизвестного произведения под названием "Хромая судьба" Братьев Стругацких.

Эту цитату часто приводят, когда речь идет о творческой работе, а программирование как раз можно отнести к таковым. Большинство задач, с которыми сталкивается современный программист уже решены полностью или, по крайней мере, частично. В отличие от искусства, в программировании повторное использование чужого кода не просто не возбраняется, а даже наоброт. Такой подход позволяет существенно ускорить разработку новых приложений, а значит и облегчает нам жизнь.

Популярные модули Python:

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

В качестве модуля мы даже можем использовать файлы наших старых проектов. Пусть у вас, например, у вас есть игра, которая содержится у вас в одном файле game.py, а внутри у вас есть функция проигрывающая страшный звук screamer(). Тогда в новом к проекте вы сможете при необходимости использовать файл game.py в качестве модуля и использовать screamer() повторно и не тратить времени на ее написание.

Аналогично и с чужими модулями. Подключив чужой проект к своему вы можете обратится к любой части его содержимого.

Установка сторонних модулей

Если нам не хватает операций языка Python, а также встроенных модулей (библиотек), наступает тот самый момент, когда пора воспользоваться сторонними модулями.

Для установки модулей необходимо использовать программу pip, которая устанавливается на ваш компьютер вместе в питоном. Единственный нюанс, pip работает только через командную строку. Для ее вызова необходимо нажать Win+R, а затем в окне "Выполнить" ввести cmd и нажать Enter.

Для Mac вызов командной строки устроен аналогично, только вместо Win+R нужно использоватьл комбинацию Command + пробел, а затем в поле Spotlight ввести terminal.

Чтобы вызвать установщик пакетов, достаточно написать в консоли pip (или pip3, если не работает команда pip) и нажать Enter. Вы увидите что то похоже на...

Если команда pip не найдена, скорее всего при установке python вы не активировали галочку "Add to PATH".

Есть два варианта решения проблемы: переустановить python или добавить путь к питону в PATH вручную.

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

В ответ мы получим длинный список из двух колонок, как на картинке выше. Слева пишется имя модуля, а справа текущая версия этого модуля. Если хотим посмотреть, установлен ли у нас какой-то конкретный модуль, то нужно ввести pip show <название модуля>. Для примера посмотрим, есть ли у меня модуль pillow, который мы будем будем использовать для работы с графикой.

Как видишь, уже есть. Более того, в информации указана версия, автор и прочая информация. Если же модуль не установлен или при вводе названия допущена ошибка, то информация не будет выведена.

Наличие больших и маленьких букв в названии модуля роли не играет. Даже если ввести pip show PiLloW, pip все равно вывел бы информацию о нем. Если какой-то модуль нам больше не нужен или возникла какая-то неполадка в его работе, то мы можем его удалить, введя pip uninstall <название модуля>.

Ничего сложного! А если я хочу обратно его установить, то мне нужно ввести pip install <название модуля>.

Вот и все. После установки, можно спокойно импортировать модули так же, как это было показано в предыдущих параграфах.

Мы перечислили некоторые самые важные возможности pip, хотя это далеко не полный их перечень. Для более подробного знакомства обратитесь к документации pip.

Поиск подходящих вам модулей можно осуществлять не только на в поисковике (Google, Yandex и т.д.), в качестве базы для поиска можно использовать Pypi, LDF или GitHub. Pypi содержит в себе поисковик, который позволяет отыскать не только модули, но и любые их модификации. После их можно установить либо по называнию pip install <название модулся>, либо скачать файл *.whl, который содержит ваш модуль.

Установка из файла аналогичена установке по названию. Сначала вам нужно вам нужно переместится в папку, где хранится файл *.whl, используя команду cd <путь к вашей папке>, а затем применить команду pip install <название файла.whl>. На LDF хранятся модули в формате *.whl, некоторые из которых уже нельзя найти на Pypi.

Также необходимый модуль можно установить из исходников с помощью команды pip install https://github.com/<логин владельца репозитория>/<название репозитория>/<ветвь.zip>. Так например, мы можем поставить pillow вот такой командой: pip install https://github.com/python-pillow/Pillow/master.zip.

Работаем с графикой с помощью Pillow

Итак, удобнее всего для обработки графики через питон использовать библиотеку Pillow - современную версию PIL (Python Image Library), которая сейчас уже не поддерживается. Всю информацию по работе с этой библиотекой можно найти на ее сайте.

https://pillow.readthedocs.io/

Сперва библиотеку нужно установить. Для этого в консоли нужно выполнить команду:

pip3 install pillow

Если консоль попросит права администратора, можно установить библиотеку только для своего пользователя.

pip3 install --user pillow

Для начала попробуем открыть файл и показать изображение на экране (оно откроется в стандартном приложении для просмотра фотографий):

# импортируем библиотеку
from PIL import Image

# открываем файл (если файл лежит в папке с программой, то достаточно указать его имя.расширение, например, image.jpg)
img = Image.open("путь к файлу")

# показываем результат
img.show()

Если файл лежит в той же папке, что и программа, и называется image.jpg, то код будет таким.

# импортируем библиотеку
from PIL import Image

img = Image.open("image.jpg")

# показываем результат
img.show()

Теперь попробуем покрасить в зеленый цвет левый верхний пиксель. Изображение с точки зрения Pillow - координатная плоскость, в которой ось Y развернута вниз, т.е. точка (0, 0) находится в левом верхнем углу. Тут стоит опять вспомнить про морской бой, а еще про сетку tkinter, с которой мы имели дело в прошлой главе.

# импортируем библиотеку
from PIL import Image

# открываем файл
img = Image.open("путь к файлу")

# красим пиксель по координатам (0, 0) в цвет (0, 255, 0)
img.putpixel((0, 0), (0, 255, 0))

# показываем результат
img.show()

# или сохраняем его в файл
img.save("путь к файлу")

В коде выше метод img.putpixel(точка, цвет) на самом деле принимает два параметра, но мы указали странную конструкцию со скобочками. На самом деле мы передали туда два кортежа - перечисления нескольких чисел. Подробнее мы поговорим о них в следующей главе, а пока можно просто пользоваться для описания точек и цветов. Первый кортеж (0, 0) описывает точку с нулевыми отступами от левого и верхнего края, а вторая тройка - зеленый цвет.

Теперь посмотрим на картинку, которую покажет питон. На первый взгляд может показаться, что ничего не изменилось, но если увеличить изображение, то видно, что пиксель в левом верхнем углу правда поменялся.

Попробуем не полностью изменить цвет пикселя, а слегка подкрасить его - добавить синего к текущему цвету.

# импортируем библиотеку
from PIL import Image

# открываем файл
img = Image.open("путь к файлу")

# берем пиксель и раскладываем его цвет по трем переменным
r, g, b = img.getpixel((0, 0))

# добавляем 50 к переменной, отвечающий за синий цвет
# изменилась только переменная, картинка пока не меняется
b = b + 50

# а теперь меняем цвет левого верхнего пикселя на новый
img.putpixel((0, 0), (r, g, b))

# показываем результат
img.show()

# или сохраняем его в файл
img.save("путь к файлу")

Отлично, теперь мы умеем подкрашивать изображения! Осталось только понять, как подкрасить сразу все пиксели? Получается, что потребуется поменять каждый пиксель из 1000000 (если размер картинки 1000 на 1000 пикселей), т.е. скопировать код миллион раз?

Чтобы сделать это автоматически, нам пригодится управляющая конструкция цикл, которая позволяет несколько раз выполнять участок кода с небольшими изменениями. Ней мы посвятим весь следующий урок.