Компьютерная графика → Как рисовать на 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(). 

После этого у вас получится такое изображение:

Как рисовать на Python с помощью библиотеки Pillow 

Нарисуем квадратики:

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)

И получим:

Как рисовать на Python с помощью библиотеки Pillow?

Нарисуем эллипсы и круги:

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 палитре. И мы получаем:

Как рисовать на Python с помощью библиотеки Pillow

Это было самое простое. Идем дальше. 

График функции и относительные координаты

Сейчас попробуем нарисовать график функции y=5*x^2+x^3-x^4. Гугл его нарисовал так:

Как рисовать на Python с помощью библиотеки Pillow

Смотрите, тут в гугле можно увеличивать и уменшать, двигать график в разные стороны. Это называется мастштабированием. Система координат на нашем изображении в 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я такое изображение:

Как рисовать на Python с помощью библиотеки Pillow

Возможно вы ничего не заметите, но в верхнем левом углу есть три красные точки )) Понятно, что не учитывя масштаба, не получится ничего нарисовать. 

Чтобы нарисовать график вам нужно мысленно попробовать наложить его на изображение:

Как рисовать на Python с помощью библиотеки Pillow

И возможно вы поймете как правильно рисовать.

Точка отсчета нашего изображения в левом верхнем углу, а у графика в центре. У нас ось 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")

Получилось:

Как рисовать на Python с помощью библиотеки Pillow

Оо, это больше похоже на график функции? Давайте попробуем соединить точки линиями и добавить оси координат.

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")

 И получим это: 

Как рисовать на Python с помощью библиотеки Pillow

Вот это наш график функции.

Мы написали универсальную функцию 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()

 

3501 0
Alisher Alikulov