Создаем консольный фоторедактор
Используем циклы для создания пиксельных фильтров
В прошлых уроках этого раздела мы разобрались с циклом 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()