Создаем консольный фоторедактор

Используем циклы для создания пиксельных фильтров

В прошлых уроках этого раздела мы разобрались с циклом while и установили библиотеку pillow. Теперь нам предстоит использовать все это для создания попиксельных фильтров. Что значит попиксельных? Такие фильтры будут по очереди менять каждый пикселей картинки по одному и тому же алгоритму, например, увеличивать яркость красного цвета у каждого пикселя.

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

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

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

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

# цикл по всем пикселям
# img.width - ширина картинки
# img.height - высота картинки
i = 0
while i < img.width:
    j = 0
    while j < img.height:
        # получаем цвет
        r, g, b = img.getpixel((i, j))

        #
        # как-либо меняем цвет
        #

        # сохраняем пиксель обратно
        img.putpixel((i, j), (r, g, b))
        j += 1
    i += 1

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

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

А теперь несколько челенджей для тебя: попробуй сделать все задания ниже по-отдельности, а затем их можно будет объединить в один большой фоторедактор.

Бонус! Как вставить картинку?

https://pillow.readthedocs.io/en/stable/handbook/tutorial.html#cutting-pasting-and-merging-images

Картинка Смайлик

Для вставки изображения можно использовать метод paste(). Обрати внимание, что для того, чтобы картинка с прозрачностью вставилась правильно, ее нужно передать как первым, так и третьим аргументом (маска прозрачности).

Второй аргумент - координаты левой верхней точки для вставки картинки в формате (X, Y).

from PIL import Image

# открываем картинку
family_image = Image.open("faces.png")

# открываем смайлик
smile = Image.open("smile.png")

print("Family image size:", family_image.width, family_image.height)
print("Smile image size:", smile.width, smile.height)

# вставляем в левый верхний угол
family_image.paste(smile, (0, 0), smile)
# вставляем в правый верхний угол
family_image.paste(smile, (family_image.width - smile.width, 0), smile)
# вставляем в левый нижний угол
family_image.paste(smile, (0, family_image.height - smile.height), smile)
# вставляем в правый нижний угол
family_image.paste(smile, (family_image.width - smile.width, family_image.height - smile.height), smile)

# сохраняем картинку
family_image.save("faces_with_smiles.png")

Бонус! Как вырезать часть картинки?

Для вырезания фрагмента можно использовать метод .crop().

from PIL import Image

# открываем картинку
family_image = Image.open("faces.png")

# вырезаем прямоугольник, левый верхний угол которого в точке (240, 42), а нижний (450, 312)
one_face = family_image.crop((240, 42, 450, 312))

# можно сохранить вырезанный курсок отдельнол
one_face.save("one_face.png")

# а можно, например, повернуть и вставить обратно
rotated_face = one_face.transpose(Image.ROTATE_180)
family_image.paste(rotated_face, (240, 42, 450, 312))

family_image.save("family_with_rotated_face.png")

Бонус! Матричные фильтры

Изучите материал про матричные фильтры на Хабре.

https://habr.com/ru/post/142818/

Если док не открывается - вот pdf.

Соединяем все вместе!

Отлично, теперь у тебя готовы фильтры по отдельности, и их нужно объединить в один редактор. Редактор должен позволять открыть файл, применить к нему один или сразу несколько фильтров, а затем сохранить под новым названием.

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

import random

def color_noise(img):
    for i in range(img.width):
        for j in range(img.height):
            r, g, b = img.getpixel((i, j))

            r += random.randint(-150, 150)
            g += random.randint(-150, 150)
            b += random.randint(-150, 150)

            img.putpixel((i, j), (r, g, b))

def grayscale(img):
    for i in range(img.width):
        for j in range(img.height):
            r, g, b = img.getpixel((i, j))

            average = (r + g + b) // 3

            img.putpixel((i, j), (average, average, average))

# тут будут другие фильтры

Теперь мы можем сделать файл editor.py с основным кодом нашего редактора.

from PIL import Image
from filters import grayscale, color_noise

print("Добро пожаловать в консольный фоторедактор.")

# путь к файлу
path = input("Введите путь к файлу: ")

# открываем изображение и на всякий случай преобразуем его в RGB - чтобы работало с png и gif
img = Image.open(path).convert("RGB")

print("Какой фильтр вы хотите применить?")
print("1 - градации серого")
print("2 - цветовой шум")

# запрашиваем номер фильтра
choice = input("Выберите фильтр: ")

# если нажали 1 - применяем градации серого
if choice == "1":
    grayscale(img)

# если нажали 2 - шум
if choice == "2":
    color_noise(img)

# спрашиваем куда сохранить результат
save_path = input("Куда сохранить: ")

# сохраняем
img.save(save_path)

Что осталось сделать?

  • Перенести все остальные готовые фильтры в filters.py.
  • Придумать и добавить собственные фильтры.
  • Сделать так, чтобы после применения фильтра программа спрашивала, применить ли еще один фильтр?
  • Еще можно добавить в возможность после завершения работы с картинкой сразу же открыть новую.

Бинго! Третий проект почти готов! 🔥

Бонус! Графический интерфейс

Ниже пример графического приложения, которое умеет открывать файлы с помощью файлового диалога и поворачивать их. Обрати внимание, что мы храним в глобавльных переменных как изображение в формате PIL (чтобы его можно было редактировать), так и изображение в формате tkinter (чтобы оно не удалилось из памяти и не исчезло с экрана).

from tkinter import *
from tkinter import filedialog
from PIL import Image, ImageTk

current_image = None
current_tk_image = None

def open_file():
    global current_image
    global current_tk_image

    path = filedialog.askopenfilename()
    current_image = Image.open(path)
    current_tk_image = ImageTk.PhotoImage(current_image)
    place_for_image.configure(image=current_tk_image)

def rotate():
    global current_image
    global current_tk_image

    if current_image:
        current_image = current_image.transpose(Image.ROTATE_90)
        current_tk_image = ImageTk.PhotoImage(current_image)
        place_for_image.configure(image=current_tk_image)

window = Tk()

place_for_image = Label(window)
place_for_image.pack()

Button(window, text="Открыть файл", command=open_file).pack(pady=20, padx=20)
Button(window, text="Повернуть", command=rotate).pack(pady=20, padx=20)

mainloop()