Сообщество Engee

Папоротник Барнсли

Автор
avatar-maximsidorovmaximsidorov
Notebook

Папоротник Барнсли

Рассматривается реализация известного фрактального алгоритма "Папоротник Барнсли", который генерирует изображение папоротника с помощью системы итерируемых функций.

Введение

Алгоритм "Папоротник Барнсли" — это пример использования системы итерируемых функций (Iterated Function System, IFS) для генерации фракталов. Он был предложен математиком Майклом Барнсли и позволяет создать реалистичное изображение листа папоротника, применяя простые аффинные преобразования с определённой вероятностью.

Фрактал строится путём многократного применения случайных аффинных преобразований к точке на плоскости. Каждое из четырёх преобразований соответствует определённой части растения (стебель, левая ветвь, правая ветвь и верхушка), а вероятности выбора этих преобразований задают характерный вид папоротника.

Этот алгоритм широко используется как учебный пример в теории фракталов и компьютерной графике благодаря своей наглядности и математической красоте.

# Установка необходимого пакета
import Pkg; Pkg.add("Images")
using Images

Основная часть

Определение структуры BarnsleyFern

mutable struct определяет изменяемую структуру данных, в которой будут храниться все параметры и текущее состояние нашего фрактала

mutable struct BarnsleyFern
    # размер изображения по ширине и высоте в пикселях
    width::Int
    height::Int
    # цвет, которым будет рисоваться папоротник
    color::RGB
    # текущие координаты точки x и y
    x::Float64
    y::Float64
    # двумерный массив (матрица) пикселей изображения
    fern::Matrix{RGB}

    # Конструктор структуры. Вызывается при создании нового объекта BarnsleyFern()
    function BarnsleyFern(width, height, color = RGB(0.0, 1.0, 0.0), bgcolor = RGB(0.0, 0.0, 0.0))
        # Создаём пустое изображение размером width на height,
        # заполняя его фоновым цветом bgcolor
        img = [bgcolor for x in 1:width, y in 1:height]
        
        # Вычисляем начальные координаты центральной точки в пикселях:
        # cx вычисляется с учётом диапазона значений x от -2.182 до 2.6558 (примерно 4.8378)
        cx = Int(floor(2.182 * (width - 1) / 4.8378) + 1)
        # cy вычисляется с учётом диапазона значений y от 0 до 9.9983
        cy = Int(floor(9.9983 * (height - 1) / 9.9983) + 1)
        
        # Устанавливаем первый пиксель в начальной точке
        img[cx, cy] = color
        
        # Создаём новый экземпляр структуры BarnsleyFern 
        # с указанными параметрами и инициализированной матрицей изображения
        return new(width, height, color, 0.0, 0.0, img)
    end
end

Структура BarnsleyFern содержит информацию о размерах изображения, текущем положении точки, цвете отрисовки и самой матрице пикселей. Конструктор создаёт чёрный фоновый прямоугольник и устанавливает начальную точку роста папоротника в центральном нижнем участке области отрисовки.

Определение трансформации точки

Функция, которая выполняет одну итерацию алгоритма: выбирает одно из четырех аффинных преобразований и применяет его к текущим координатам точки (f.x, f.y), после чего переводит новые вещественные координаты в дискретные пиксельные и закрашивает соответствующий пиксель на изображении.

function transform(f::BarnsleyFern)
    # Генерируем случайное число от 0 до 99 (включительно)
    r = rand(0:99)
    
    # В зависимости от значения r выбирается одно из 4 аффинных преобразований:
    # Вероятности подобраны так, чтобы получился реалистичный вид папоротника:
    # 1% — вертикальное сжатие (ствол)
    # 85% — крупные листья сверху (главное преобразование)
    # 7% — маленькие листья слева
    # 7% — маленькие листья справа
    
    if r < 1
        # Превращение точки в корневую часть ствола
        f.x = 0.0
        f.y = 0.16 * f.y
    elseif r < 86
        # Главное преобразование: формирование основной массы листвы
        f.x = 0.85 * f.x + 0.04 * f.y
        f.y = -0.04 * f.x + 0.85 * f.y + 1.6
    elseif r < 93
        # Формирование левой стороны листвы
        f.x = 0.2 * f.x - 0.26 * f.y
        f.y = 0.23 * f.x + 0.22 * f.y + 1.6
    else
        # Формирование правой стороны листвы
        f.x = -0.15 * f.x + 0.28 * f.y
        f.y = 0.26 * f.x + 0.24 * f.y + 0.44
    end

    #########################################################################
    # Переход от декартовых координат к индексам матрицы изображения
    # Так как область значений (x ∈ [-2.182, 2.6558], y ∈ [0, 9.9983])
    # нужно правильно смасштабировать координаты и перевести их в номера пикселей

    # Для координаты x:
    # минимальное значение x=-2.182 должно соответствовать первому столбцу (индекс 1)
    # максимальное значение x=2.6558 → ширина-1
    cx = Int(floor((f.x + 2.182) * (f.width - 1) / 4.8378) + 1)

    # Для координаты y:
    # в системе координат экрана ось Y направлена вниз, поэтому необходимо 
    # выполнить преобразование координат (отражение относительно горизонтальной оси)
    # y=0 → последняя строка height
    # y=9.9983 → первая строка 1
    cy = Int(floor((9.9983 - f.y) * (f.height - 1) / 9.9983) + 1)

    # Проверяем границы изображения перед установкой пикселя
    if 1 <= cx <= f.width && 1 <= cy <= f.height
        # Устанавливаем пиксель по новым координатам в заданный цвет
        f.fern[cx, cy] = f.color
    end
end
transform (generic function with 1 method)

Функция transform() моделирует случайный процесс IFS. На каждом шаге она случайным образом выбирает одно из четырех аффинных преобразований, изменяет координаты текущей точки и записывает эту точку на изображении.

Чтобы сохранить пропорции при переходе от области определения функций () к матрице пикселей, используются масштабирующие множители и сдвиги:

  • Для X: cx = floor((x + 2.182) × (ширина − 1) ÷ 4.8378)
  • Для Y: cy = floor((9.9983 − y) × (высота − 1) ÷ 9.9983), где происходит зеркальное отображение

Выполнение итераций и вывод результата

Создаем экземпляр нашего фрактала размером 500×500 пикселей

const fern = BarnsleyFern(500, 500)
WARNING: redefinition of constant Main.fern. This may fail, cause incorrect answers, or produce other errors.
BarnsleyFern(500, 500, RGB{Float64}(0.0,1.0,0.0), 0.0, 0.0, RGB[RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0) … RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0); RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0) … RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0); … ; RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0) … RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0); RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0) … RGB{Float64}(0.0,0.0,0.0) RGB{Float64}(0.0,0.0,0.0)])

Запускаем миллион итераций – каждая шаг применяет одно из аффинных преобразований и добавляет точку в соответствующий пиксель изображения

for _ in 1:1000000
    transform(fern)
end

Передаем обратно результат как транспонированную матрицу пикселей. Это связано с различием ориентации осей в экранной и математической системах координат

fern.fern'
No description has been provided for this image

Данный блок кода запускает генерацию фрактального изображения. Проходит миллион шагов итераций, затем мы обращаемся к содержимому поля .fern и транспонируем матрицу для корректного отображения в графических библиотеках.

Заключение

Мы подробно разобрали реализацию алгоритма «Папоротник Барнсли» на языке программирования Julia. Он демонстрирует применение системы итерируемых функций (IFS) и позволяет наглядно увидеть фрактальную природу самоподобных объектов.

Код создает цветное изображение в виде матрицы RGB-значений, которое может быть в дальнейшем использовано или визуализировано различными способами.

Такой подход полезен в образовательных целях, для понимания принципов работы фракталов, а также в практическом программировании при работе с рекурсивными графическими объектами и моделировании природных структур.

Пример разработан с использованием материалов Rosetta Code