Skip to content

Instantly share code, notes, and snippets.

@makeevrserg
Created June 4, 2025 08:36
Show Gist options
  • Select an option

  • Save makeevrserg/799415b0560716f52de9dcf10a559caf to your computer and use it in GitHub Desktop.

Select an option

Save makeevrserg/799415b0560716f52de9dcf10a559caf to your computer and use it in GitHub Desktop.

Revisions

  1. makeevrserg created this gist Jun 4, 2025.
    105 changes: 105 additions & 0 deletions pattern_generator.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,105 @@
    from PIL import Image, ImageDraw
    import os
    import random
    import math
    import numpy as np

    # === НАСТРОЙКИ ===
    ICONS_FOLDER = "icons"
    OUTPUT_IMAGE = "final_pattern.png"
    WIDTH, HEIGHT = 2000, 4000
    ICON_COUNT = 160
    MIN_SPACING = 15
    ROTATION_RANGE = 30
    SCALE_MIN, SCALE_MAX = 0.4, 1.0

    # === ФУНКЦИИ ===

    def rotate_and_paste(base, img, position, angle):
    rotated = img.rotate(angle, expand=True)
    x, y = position
    x -= rotated.width // 2
    y -= rotated.height // 2
    base.paste(rotated, (x, y), rotated)

    def check_overlap(x, y, w, h, placed, spacing):
    for px, py, pw, ph in placed:
    dx = x - px
    dy = y - py
    dist = math.hypot(dx, dy)
    if dist < ((w + pw) / 2 + spacing):
    return True
    return False

    def draw_decor(draw, kind, x, y, size):
    if kind == "circle":
    draw.ellipse([x - size//2, y - size//2, x + size//2, y + size//2], outline=(149, 149, 158,255), width = 2)
    elif kind == "square":
    draw.rectangle([x - size//2, y - size//2, x + size//2, y + size//2], outline=(149, 149, 158,255), width = 2)
    elif kind == "star":
    r = size / 2
    points = []
    for i in range(5):
    angle = math.radians(i * 144)
    sx = x + r * math.cos(angle)
    sy = y + r * math.sin(angle)
    points.append((sx, sy))
    draw.polygon(points, outline=(149, 149, 158, 255), width = 2)

    # === ЗАГРУЗКА ===
    canvas = Image.new("RGBA", (WIDTH, HEIGHT), (23, 23, 33, 255))
    icon_paths = [os.path.join(ICONS_FOLDER, f) for f in os.listdir(ICONS_FOLDER) if f.endswith(".png")]
    icons = [Image.open(p).convert("RGBA") for p in icon_paths]
    placed = []

    # === РАЗМЕЩЕНИЕ ИКОНКИ ===
    for _ in range(ICON_COUNT):
    for attempt in range(100):
    icon = random.choice(icons)
    scale = random.uniform(SCALE_MIN, SCALE_MAX)
    new_size = (int(icon.width * scale), int(icon.height * scale))
    resized = icon.resize(new_size, Image.Resampling.LANCZOS)
    angle = random.uniform(-ROTATION_RANGE, ROTATION_RANGE)

    w, h = resized.size
    cx = random.randint(w//2, WIDTH - w//2)
    cy = random.randint(h//2, HEIGHT - h//2)

    if not check_overlap(cx, cy, w, h, placed, MIN_SPACING):
    rotate_and_paste(canvas, resized, (cx, cy), angle)
    placed.append((cx, cy, w, h))
    break

    # === ДЕКОРАТИВНЫЕ ЭЛЕМЕНТЫ ===
    # draw = ImageDraw.Draw(canvas)
    # decor_kinds = ["circle", "square", "star"]

    # for _ in range(DECOR_COUNT):
    # for attempt in range(100):
    # size = random.randint(5, 14)
    # x = random.randint(size, WIDTH - size)
    # y = random.randint(size, HEIGHT - size)
    # if not check_overlap(x, y, size, size, placed, MIN_SPACING):
    # kind = random.choice(decor_kinds)
    # draw_decor(draw, kind, x, y, size)
    # placed.append((x, y, size, size))
    # break


    # Преобразуем в массив
    array = np.array(canvas)

    # Добавим зернистость
    noise = np.random.normal(0, 15, array.shape[:2]) # стандартное отклонение регулирует силу шума
    noise = np.clip(noise, -30, 30).astype(np.int16)

    # Применим шум к каждому каналу (R, G, B)
    for i in range(3): # не трогаем альфа-канал
    array[..., i] = np.clip(array[..., i].astype(np.int16) + noise, 0, 255)

    # Вернем в изображение
    canvas = Image.fromarray(array.astype(np.uint8), "RGBA")

    # === СОХРАНЕНИЕ ===
    canvas.save(OUTPUT_IMAGE)
    print(f"Готово: {OUTPUT_IMAGE}")