Компьютерная графика → Как рисовать на Python с помощью библиотеки Pillow?
В питоне есть библиотека PIL(Python Image Library) и его близнец Pillow(расширенная версия PIL).
Эта библиотека позволяют по разному работать с изображениями, в том числе и самому рисовать их. Если захотите вы можете с помощью этой библиотеки написать свой Photoshop.
В этой статье я кратко расскажу как создавать свои изображения и рисовать графики на них.
Не во всех установках питона есть в комплекте библиотека Pillow. Поэтому, вам придется его самому установить. Как установить вы можете прочитать в интернете.
Создание нового изображения и сохранение в файл
Для начала мы попробуем создать пустое изображение размером 400x300 пикселей и сохраним его в файл с названием empty.png чтобы можно было его открыть и посмотреть.
Создайте новый .py файл и импортируйте следующие файлы:
from PIL import Image
from PIL import ImageDraw
Image - это файл в котором есть все что нужно для открытия, создания и сохранения изображений. А ImageDraw - для рисования в изображениях.
Теперь создадим новое изображение с типом "RGB" и размером (400, 300) и сохраним в файл empty.png:
image = Image.new("RGB", (400, 300))
image.save("empty.png", "PNG")
Как вы поняли у функции Image.new два параметра: первый - тип изображения и второй размеры. Размеры задаются в виде кортежа из двух значений.
Если вы запустите эту программу, вы получите в той же папке новый файл empty.png c черным фоном без ничего.
Рисуем линии и квадратики
Давайте попробуем нарисовать линию от точки (0,0) - верхняя левая и до точки (400,300) - нижняя правая.
image = Image.new("RGB", (400, 300))
draw = ImageDraw.Draw(image)
draw.line((0, 0, 400, 300))
image.save("empty.png", "PNG")
Получилось?
Функция line принимает один параметр, в котором должен быть кортеж из четырех значений: первые два - координаты начальной точки, последние два - координаты конечной точки.
Теперь нарисуем красную горизонтальную решетку c 10 линиями:
from PIL import Image, ImageColor
from PIL import ImageDraw
width = 400
height = 300
image = Image.new("RGB", (width, height))
draw = ImageDraw.Draw(image)
draw.line((0, 0, width, height))
for i in range(10):
x = int(width/10 * i)
draw.line((x, 0, x, height), fill=ImageColor.getrgb("red"))
image.save("empty.png", "PNG")
Так как, ширину и высоты мы будем часто использовать в вычислениях, а они могут меняться, я записал их в переменные width, height.
Если вы заметили я импортировал файл ImageColor, которая помогает работе с цветами. Например чтобы получить цвет по имени нужно вызвать метод ImageColor.getrgb().
После этого у вас получится такое изображение:
Нарисуем квадратики:
draw.rectangle((20, 20, 50, 100), fill=ImageColor.getrgb("cyan"))
draw.rectangle((50, 101, 80, 120), fill=ImageColor.getrgb("magenta"), outline=ImageColor.getrgb("blue"))
draw.rectangle((120, 120, 300, 250), outline=ImageColor.getrgb("yellow"), width=5)
И получим:
Нарисуем эллипсы и круги:
draw.ellipse((200, 20, 250, 100), outline=ImageColor.getcolor("#F19033", "RGB"), width=2)
draw.ellipse((140, 140, 220, 220), fill=ImageColor.getcolor("#FF0000", "RGB"))
Здесь я использовал функцию ImageColor.getcolor() чтобы получить цвет по его HEX значения в RGB палитре. И мы получаем:
Это было самое простое. Идем дальше.
График функции и относительные координаты
Сейчас попробуем нарисовать график функции y=5*x^2+x^3-x^4. Гугл его нарисовал так:
Смотрите, тут в гугле можно увеличивать и уменшать, двигать график в разные стороны. Это называется мастштабированием. Система координат на нашем изображении в python и система координат этого графика разные.
Давайте попробуем в лоб нарисовать график в нашей системе координат, не думаю о масштабах.
Создаем функцию для функции графика:
def function1(x):
return 5 * (x ** 2) + x ** 3 - x ** 4
Рисуем график точками:
for x in range(width): # проходим по всей ширине
y = function1(x)
draw.point((x, y), fill=ImageColor.getrgb("red"))
Получитcя такое изображение:
Возможно вы ничего не заметите, но в верхнем левом углу есть три красные точки )) Понятно, что не учитывя масштаба, не получится ничего нарисовать.
Чтобы нарисовать график вам нужно мысленно попробовать наложить его на изображение:
И возможно вы поймете как правильно рисовать.
Точка отсчета нашего изображения в левом верхнем углу, а у графика в центре. У нас ось Y увеличивается вниз, а у графика вверх. Крайние точки по оси Х у нас 0 и 400, а у графика -3 и 3. У нас нет отрицательных координат, а у графика есть. 😫🤪
Что делать?
1. Определим область для рисования графика и вычислим коэффициенты масштабрирования(наложения).
2. Все вычисления будем делать в системе координат графика
3. Когда нужно нарисовать уже, координаты графика будем конвертировать в наши коориданы используя коэффициенты масштабирования.
from PIL import Image, ImageColor
from PIL import ImageDraw
width = 400
height = 300
image = Image.new("RGB", (width, height))
draw = ImageDraw.Draw(image)
# Заполним все изображение белым цветом
draw.rectangle((0, 0, width, height), fill=ImageColor.getrgb("white"))
def function1(x):
return 5 * (x ** 2) + x ** 3 - x ** 4
class Point: # создал класс для удобства
def __init__(self, x, y):
self.x = x
self.y = y
# задаем область значений функции
start_x = -4
end_x = 4
start_y = -15
end_y = 15
points = []
x = start_x
step_x = 0.1
while x <= end_x: # проходим по всей ширине
y = function1(x) # вычисляем значение функции для текущего значения x
points.append(Point(x, y)) # добавляем эту точки в список точек
x += step_x # увеличиваем x
# И так у нас есть список точек функции в заданном регионе. Как таблица значений функции в школе.
# Все точки в системе координат функции.
def convert(point):
# Напишем функцию которая будет конвертировать точку в системе координат функции, в нашу систему координат
# У нас вся ширина 400, а графика функции в нашей области от -4 до 4 - 8 единиц
# Поэтому вычислим масштаб по x и по y: т.е. во сколько раз ширина изображения больше ширины графика
scale_x = width / (end_x - start_x)
scale_y = height / (end_y - start_y)
# Зная мастшаб, мы вычисляем координаты в нашей системе координат для текущей точки
local_x = point.x * scale_x
local_y = point.y * scale_y
# Помните что, у нас центры разные? Перемещаем центр точек Так как начало графика по x -4, мы должны
# умножить -4 на машстаб и сдвинуть точки на 4 значения правее (поэтому с минусом).
# и точно также сдвигаем по y
local_x = (-start_x * scale_x) + local_x
local_y = (-start_y * scale_y) + local_y
# Создаем новую точку в нашей системе координат. И переворичваем Y. Сами догадайтесь почему.
return Point(local_x, height - local_y)
# рисуем на экране все точки.
for point in points:
p = convert(point)
draw.point((p.x, p.y), fill=ImageColor.getrgb("red"))
image.save("graphic.png", "PNG")
Получилось:
Оо, это больше похоже на график функции? Давайте попробуем соединить точки линиями и добавить оси координат.
from PIL import Image, ImageColor
from PIL import ImageDraw
width = 400
height = 300
image = Image.new("RGB", (width, height))
draw = ImageDraw.Draw(image)
# Заполним все изображение белым цветом
draw.rectangle((0, 0, width-1, height-1), fill=ImageColor.getrgb("white"), outline=ImageColor.getrgb("grey"))
def function1(x):
return 5 * (x ** 2) + x ** 3 - x ** 4
class Point: # создал класс для удобства
def __init__(self, x, y):
self.x = x
self.y = y
# задаем область значений функции
start_x = -4
end_x = 4
start_y = -15
end_y = 15
points = []
x = start_x
step_x = 0.1
while x <= end_x: # проходим по всей ширине
y = function1(x) # вычисляем значение функции для текущего значения x
points.append(Point(x, y)) # добавляем эту точки в список точек
x += step_x # увеличиваем x
# И так у нас есть список точек функции в заданном регионе. Как таблица значений функции в школе.
# Все точки в системе координат функции.
def convert(point):
# Напишем функцию которая будет конвертировать точку в системе координат функции, в нашу систему координат
# У нас вся ширина 400, а графика функции в нашей области от -4 до 4 - 8 единиц
# Поэтому вычислим масштаб по x и по y: т.е. во сколько раз ширина изображения больше ширины графика
scale_x = width / (end_x - start_x)
scale_y = height / (end_y - start_y)
# Зная мастшаб, мы вычисляем координаты в нашей системе координат для текущей точки
local_x = point.x * scale_x
local_y = point.y * scale_y
# Помните что, у нас центры разные? Перемещаем центр точек Так как начало графика по x -4, мы должны
# умножить -4 на машстаб и сдвинуть точки на 4 значения правее (поэтому с минусом).
# и точно также сдвигаем по y
local_x = (-start_x * scale_x) + local_x
local_y = (-start_y * scale_y) + local_y
# Создаем новую точку в нашей системе координат. И переворичваем Y. Сами догадайтесь почему.
return Point(local_x, height - local_y)
# рисуем оси координат
start_hor = convert(Point(start_x, 0))
end_hor = convert(Point(end_x, 0))
draw.line((start_hor.x, start_hor.y, end_hor.x, end_hor.y), fill=ImageColor.getrgb("grey"))
start_ver = convert(Point(0, start_y))
end_ver = convert(Point(0, end_y))
draw.line((start_ver.x, start_ver.y, end_ver.x, end_ver.y), fill=ImageColor.getrgb("grey"))
# рисуем на экране все точки.
last_point = convert(points[0])
for point in points:
current_point = convert(point)
draw.line((last_point.x, last_point.y, current_point.x, current_point.y), fill=ImageColor.getrgb("blue"))
last_point = current_point
image.save("graphic.png", "PNG")
И получим это:
Вот это наш график функции.
Мы написали универсальную функцию convert, которую можно использовать в любых случаях когда нужно масштабировать отображение. Теперь меняя переменные start_x, end_x, start_y, end_y вы можете двигать, увеличивать и уменьшать график!
Показать на экране
Чуть не забыл. Вы можете открыть изображение сразу, используя метод show()
image.show()
Или Через tKinter:
from PIL import Image, ImageTk
import tkinter as tk
window = tk.Tk()
tk_image = ImageTk.PhotoImage(image)
tk.Label(window, image=tk_image).pack()
window.mainloop()