Можно ли научить Python рисовать? Эта статья — не сухой туториал, а настоящая история экспериментов с генеративным искусством. В ней рассказывается о создании цифровых картин с помощью случайностей, математики, шума Перлина и любви к визуальному абсурду. Много кода, немного философии и никакой нейросети — только чистый Python и жажда выразительности.

Порой программисту хочется отдохнуть от API, CRUD и фреймворков, и попробовать что-то бессмысленное, но красивое. Вот как программисты становятся художниками. Кто-то берет Blender, кто-то — нейросети. А кто-то — открывает matplotlib и начинает играться со случайными числами.
Сначала всё выглядит как чёртов бардак. А потом вдруг — бах! — появляется текстура, от которой не отвести глаз. Именно так начался этот эксперимент. Ни плана, ни цели. Только желание рисовать с помощью кода.
Идея проста: сгенерировать изображение с нуля, используя только Python и немного хаоса. Никаких pre-trained моделей, никакого ML. Только математические функции и воображение. Это — код как кисть.
Что понадобится
-
Python 3.10+
-
Библиотеки:
numpy,matplotlib,Pillow,noise
pip install numpy matplotlib Pillow noise
Первый мазок: случайные цвета и шум
Цифровое полотно — это просто массив. Каждый пиксель — это точка с координатами и цветом. Начнем с создания цветной карты с использованием шума Перлина.
import numpy as np from PIL import Image from noise import pnoise2 width, height = 800, 800 scale = 100.0 image = Image.new("RGB", (width, height)) pixels = image.load() for x in range(width): for y in range(height): nx = x / scale ny = y / scale value = pnoise2(nx, ny, octaves=6) r = int((value + 0.5) * 255) % 256 g = int((0.5 - value) * 255) % 256 b = (x * y) % 256 pixels[x, y] = (r, g, b) image.save("perlin_art.png")
Шум Перлина позволяет избежать классической зернистости случайных чисел и создаёт «органичную» текстуру.
Углубляемся: цветовые поля с контролем хаоса
Иногда картине не хватает композиции. Добавим цветовые поля, ограниченные по форме, но заполненные шумом. Это похоже на эксперимент в стиле Марка Ротко, только для интровертов с клавиатурой.
def draw_colored_field(image, center, radius, base_color, scale=30.0): pixels = image.load() cx, cy = center for x in range(image.width): for y in range(image.height): dx, dy = x - cx, y - cy if dx * dx + dy * dy <= radius * radius: nx, ny = x / scale, y / scale noise_val = pnoise2(nx, ny, octaves=4) r = int((base_color[0] + noise_val * 50) % 256) g = int((base_color[1] + noise_val * 50) % 256) b = int((base_color[2] + noise_val * 50) % 256) pixels[x, y] = (r, g, b) image = Image.new("RGB", (800, 800), color="white") draw_colored_field(image, (400, 400), 250, (100, 50, 200)) image.save("field_art.png")
Можно нарисовать несколько полей с разными цветами — и получить сюрреалистичный пейзаж.
Штрихи — линии и повторения
Настоящий художник знает цену повтору. Программист может зациклить его. Добавим абстрактные линии, создающие ощущение ритма.
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(8, 8)) ax.set_xlim(0, 100) ax.set_ylim(0, 100) for i in range(50): x = np.linspace(0, 100, 1000) y = 50 + 20 * np.sin(x * 0.1 + i) ax.plot(x, y, alpha=0.1, color='black') ax.axis('off') plt.savefig("lines_art.png", bbox_inches='tight', pad_inches=0)
Дополнительно: фрактальные формы
Иногда простая рекурсия может нарисовать нечто удивительное. Пример — дерево, где каждая ветка порождает новые ветки:
from PIL import Image, ImageDraw import math def draw_branch(draw, x, y, angle, depth): if depth == 0: return x2 = x + int(math.cos(angle) * depth * 10.0) y2 = y + int(math.sin(angle) * depth * 10.0) draw.line((x, y, x2, y2), fill=(0, 100 + depth * 15, 0), width=depth) draw_branch(draw, x2, y2, angle - 0.3, depth - 1) draw_branch(draw, x2, y2, angle + 0.3, depth - 1) img = Image.new("RGB", (800, 600), (255, 255, 255)) draw = ImageDraw.Draw(img) draw_branch(draw, 400, 500, -math.pi / 2, 10) img.save("tree_fractal.png")
Дополнительно: генерация лабиринта
А что если рисовать нечто строгое, но всё ещё случайное? Например, лабиринт по алгоритму depth-first search.
import random from PIL import Image, ImageDraw width, height = 40, 40 cell_size = 20 maze = [[0] * width for _ in range(height)] stack = [(0, 0)] visited = set(stack) def neighbors(x, y): for dx, dy in [(0, -1), (1, 0), (0, 1), (-1, 0)]: nx, ny = x + dx, y + dy if 0 <= nx < width and 0 <= ny < height and (nx, ny) not in visited: yield nx, ny while stack: x, y = stack[-1] next_cells = list(neighbors(x, y)) if next_cells: nx, ny = random.choice(next_cells) visited.add((nx, ny)) stack.append((nx, ny)) maze[ny][nx] = 1 else: stack.pop() img = Image.new("RGB", (width * cell_size, height * cell_size), "white") draw = ImageDraw.Draw(img) for y in range(height): for x in range(width): if maze[y][x] == 0: draw.rectangle( [x * cell_size, y * cell_size, (x+1) * cell_size, (y+1) * cell_size], fill="black") img.save("maze.png")
Дополнительно: пиксельные узоры по формулам
Иногда достаточно просто поиграть с уравнениями. Например, построим картину из уравнений:
from PIL import Image import math w, h = 600, 600 img = Image.new("RGB", (w, h)) pixels = img.load() for x in range(w): for y in range(h): r = int((math.sin(x * 0.01) + 1) * 127) g = int((math.cos(y * 0.01) + 1) * 127) b = int((math.sin((x + y) * 0.01) + 1) * 127) pixels[x, y] = (r, g, b) img.save("formula_art.png")
Финальный аккорд: собрать всё вместе
Создание генеративного искусства — это как варка борща: важно не столько следовать рецепту, сколько пробовать на вкус. Хочется больше красного? Убавь шум. Не хватает контраста? Прогони через градиент.
Вот пример итогового подхода:
-
Генерируем текстуру шума — фон
-
Добавляем несколько цветовых пятен
-
Поверх — линии или фигуры
-
Всё — сохраняем и радуемся
Личное: а зачем вообще всё это?
Иногда очень полезно отвлечься от задач, которые «имеют смысл». Генеративное искусство — это способ побыть в потоке, поиграть с хаосом и логикой одновременно. Это момент, когда математика перестаёт быть скучной, а код — просто инструментом.
Кто-то скажет: «А зачем? Это же бесполезно». Но в том-то и дело. Это красиво. А значит — полезно.
Заключение
Программирование не обязано быть только про производительность и бенчмарки. Оно может быть про чувство. Про визуальный кайф. Про баг, который стал картиной.
Python — это не просто язык, это кисть. И как её использовать — зависит только от фантазии.
Если после этой статьи ты открыл PIL, попробовал сгенерировать хоть что-то, и на секунду почувствовал себя художником — значит, всё не зря.
P.S. Можно добавить генерацию GIF, анимацию, или даже использовать tkinter для интерактива. Но это уже совсем другая история.
ссылка на оригинал статьи https://habr.com/ru/articles/904672/
Добавить комментарий