Предыдущая статья натолкнула меня на мысль написать ещё одну программу, генерирующую последовательности изображений с другим типом фракталов. В ней пришлось применить более сложную анимацию коэффициентов.
Введение
В предыдущей статье приведена программа на C++, генерирующая последовательности фрактальных изображений и сохраняющая их в файлы формата PNG. Напомню, что каждое изображение создается в результате многократных вызовов преобразования координат точки {x, y} в новые с использованием двух коэффициентов coef1 и coef2:
double coef1 = 1.0, coef2 = 0.0; //коэффициенты со значениями по умолчанию 1.0 и 0.0
double fx, fy;
uint32_t col;
for (int j = 0; j < 500; j++) {
fx <- поместить случайное число от 0.0 до 1.0
fy <- поместить случайное число от 0.0 до 1.0
col <- поместить случайный цвет из фиксированной таблицы в 8 цветов
for (int i = 0; i < 1000; i++) {
// получить целочисленные координаты точки {x, y},
// поместить пиксел в выходное изображение с цветом col, затем:
fx = frac(fx + sin(fy - coef2));
fy = frac(fy + cos(fx * coef1));
}
}
В результате экспериментов оказалось, что генерирование последовательностей изображений по такому алгоритму с плавным изменением значений коэффициентов дает интересные результаты. Например, если за 2000 шагов плавно изменять значение коэффициента coef1 в диапазоне от 1.0 до 5.0, картинка изменяется следующим образом (при просмотре лучше принудительно включить разрешение 4K):
В комментариях к статье мне любезно предоставили примеры изображений других фракталов с различными аттракторами. Я решил поискать алгоритмы, реализующие какие-нибудь из них.
Lozi map
В процессе поисков я нашел ресурс, где приведен пример кода на Python для генерирования изображения с помощью Lozi map. Итеративная формула для него выглядит следующим образом:
Взяв за основу программу из предыдущей статьи, я реализовал генерирование изображений по подобному алгоритму на C++. Псевдокод:
double B = -1.0, C = 0.5; //коэффициенты со значениями по умолчанию -1.0 и 0.5
double xlast, ylast, xnew, ynew;
uint32_t col;
for (int j = 0; j < 500; j++) {
xlast <- поместить случайное число от 0.0 до 1.0
ylast <- поместить случайное число от 0.0 до 1.0
col <- поместить случайный цвет из фиксированной таблицы в 8 цветов
for (int i = 0; i < 1000; i++) {
xnew = 1.0 + ylast - C * fabs(xlast);
ynew = B * xlast;
xlast = xnew;
ylast = ynew;
// получить целочисленные координаты точки {x, y} из {xnew, ynew},
// поместить пиксел в выходное изображение с цветом col
}
}
Как и в предыдущей программе, каждый пиксел с полученными по алгоритму координатами {x, y} не просто помещается в буфер с выходным изображением, а смешивается с цветом фона. При этом, для каждой точки подсчитывается, сколько раз в её координаты попал каждый цвет, и при смешивании с фоном учитывается вес каждого цвета. Это даёт более «мягкое» и приятное на глаз изображение.
Пример изображения, полученного по такому алгоритму, вынесен в заголовок статьи. Значения коэффициентов для него: B = -1.0, C = 0.5. Можно получить и совершенно другие изображения, например (B = 1.0, C = 1.0):
Далее, мне стало интересно, как изображение изменяется при плавном изменении значений коэффициентов.
Пробы анимации
Сначала я решил проверить, как изменяется картинка при изменении коэффициента B от значения 0 до -1 за 20 шагов:
Из тонкой горизонтальной линии на черном фоне картинка постепенно превращается в изображение, показанное в заголовке. Видно, что в начале диапазона картинка изменяется медленнее, чем в конце. Посмотрим, как происходит изменение коэффициента B от значения -0.9 до -1 за те же 20 шагов:
Здесь уже лучше видно, что изображение переходит к конечному из картинки, похожей на спиралевидную галактику (интересно, что в статье, где приведен пример когда, написано, что Lozi map — упрощенная версия Henon map, которая связана с движением звезд в галактиках). Очевидно, что чем ближе значение коэффициента B к -1.0, с тем более мелким шагом нужно его изменять, чтобы как следует рассмотреть «эволюцию» картинки. Я пришел к мысли, что нужно разбить диапазон изменения коэффициента B от 0 до -1 на несколько поддиапазонов, каждый из которых меньше предыдущего.
Несколько диапазонов анимации
В результате экспериментов я составил таблицу, в которой показано, за какое количество шагов и в каком диапазоне нужно изменять коэффициент B, чтобы получить интересное изменение изображения (значение коэффициента C при этом неизменно и равно 0.5):
Количество шагов | Коэффициент B (от) | Коэффициент B (до) |
250 | -0.01 | -0.6 |
514 | -0.6 | -0.75 |
500 | -0.75 | -0.95 |
500 | -0.95 | -0.99 |
500 | -0.99 | -1.0 |
То есть, теперь программа должна изменять коэффициент не за один проход, а за несколько последовательных. Я соответствующим образом изменил алгоритм, а также добавил возможность указать в командной строке текстовый файл, каждая строка в котором задает количество шагов в диапазоне и начальные/конечные значения коэффициентов B и C, разделенные пробелами. Формат файла:
{количество шагов} {значение B от} {значение B до} {значение C от} {значение C до}
пример (изменение B от -0.01 до -0.6 за 250 шагов, значение C равно 0.5):
250 -0.01 -0.6 0.5 0.5
Допустим, коэффициент B теперь можно оставить в значении -1.0, и далее изменять только коэффициент C. Здесь также после ряда экспериментов я получил следующую таблицу:
Количество шагов | Коэффициент C (от) | Коэффициент C (до) |
500 | 0.5 | 0.1 |
250 | 0.1 | 0.01 |
500 | 0.01 | 0 |
Полное содержимое текстового файла, задающего все перечисленные выше диапазоны изменений коэффициентов B и C:
250 -0.01 -0.6 0.5 0.5
514 -0.6 -0.75 0.5 0.5
500 -0.75 -0.95 0.5 0.5
500 -0.95 -0.99 0.5 0.5
500 -0.99 -1.0 0.5 0.5
500 -1.0 -1.0 0.5 0.1
250 -1.0 -1.0 0.1 0.01
500 -1.0 -1.0 0.01 0
1 -1.0 -1.0 0 0
О том, откуда взялось значение 514, я напишу ниже. Последняя строка, в которой всего одно значение коэффициентов (B = -1, C = 0), порождает последнее изображение, в котором довольно сложная картинка вырождается в то, что для меня было несколько неожиданным (как, впрочем, и пример выше с прямоугольным треугольником):
Интереснее всё это выглядит в динамике. При просмотре рекомендую принудительно включить разрешение 4К:
Выкладывать безмолвное видео мне было неинтересно. Чтобы некоторые моменты в изменении картинки точнее подогнать к музыке, пришлось подбирать количество шагов в анимации коэффициентов. Отсюда и взялось «странное» значение 514 в текстовом файле, описывающем диапазоны анимации.
Как и в случае с предыдущей программой, лучше всего смотреть не видео на YouTube, а исходную последовательность сгенерированных PNG-файлов (собранная для Windows программа имеется в репозитории, см. ниже). Для просмотра лучше всего воспользоваться быстрой программой для просмотра изображений FastStone Image Viewer.
Дополнительное ускорение программы
В предыдущей статье я рассказал, что для ускорения работы в программе реализован многопоточный алгоритм, позволяющий распараллелить генерирование последовательности изображений в нескольких потоках одновременно. В комментариях к предыдущей статье мне справедливо указали на то, что использование ассоциативного контейнера для хранения пар величин «координаты точки → набор цветов» несколько избыточно.
Вместо того, чтобы помещать пару координат {x, y} и цвет точки в контейнер, можно сразу смешивать цвет точки с фоном в буфере выходной картинки, используя некоторый весовой коэффициент. Профилирование программы показало, что помещение в контейнер действительно занимает значительное время. Я попробовал реализовать и вариант алгоритма, в котором цвет точки сразу смешивается с фоном. Это и правда ускоряет работу в несколько раз, но лично я не могу сказать, что этот вариант по виду получаемой картинки мне нравится больше. Поэтому, я оставил возможность генерирования изображений как с ассоциативным контейнером, так и без него — это задается дополнительным параметром в командной строке: ‑mix — см. ниже.
Готовая программа
Код программы на C++ я выложил в репозиторий. Создана под Windows в Microsoft Visual Studio 2019, также успешно собрана и проверена в Ubuntu 22.04.4 LTS (под WSL). Для сборки требует C++20 и библиотеку libpng.
Запускается из командной строки со следующими параметрами:
lozianim.exe [опции]
-help | показать подсказку по опциям в командной строке | |
-width {N} | ширина выходной картинки в пикселах (1280 по умолчанию) | |
-height {N} | высота выходной картинки в пикселах (720 по умолчанию) | |
-mix | если этот параметр задан, то при генерировании изображения цвет точки сразу смешивается с цветом фона, а не помещается в ассоциативный контейнер (ускоряет работу программы) | |
-outfolder {path} | путь к выходной папке для сохранения изображений | |
-steps {N} | количество выходных изображений/шагов анимации, 1 по умолчанию | |
-coef1 {v} | значение (float) коэффициента B, -1.0 по умолчанию | |
-coef2 {v} | значение (float) коэффициента C, 0.5 по умолчанию | |
-coef1end {v} | конечное значение (float) коэффициента B, -1.0 по умолчанию | |
-coef2end {v} | конечное значение (float) коэффициента C, 1.0 по умолчанию | |
-coefin {file} | брать диапазоны изменений коэффициентов из указанного текстового файла | |
-threads {N} | количество потоков исполнения программы | |
-threads half | использовать половину количества ядер ЦП в качестве количества потоков (по умолчанию) | |
-threads max | использовать полное количество ядер ЦП | |
При вызове справки -help также показывается полное количество доступных ядер ЦП |
Примеры вызова:
lozianim.exe -outfolder D:\tmp\png -coef1 -1.0 -coef2 0.1
Создает одну картинку 'D:\tmp\png\image.png' размерами 1280x720, используя коэффициенты: B=-1.0 и C=0.1
lozianim.exe -outfolder D:\tmp\png -steps 1000 -width 1920 -height 1080 -coef1 -0.9 -coef1end -1.0 -coef2 0.5 -coef2end 0.5 -threads max
Создает 1000 изображений размерами 1920x1080 в папке 'D:\tmp\png' с исполнением на максимальном количестве ядер ЦП. Используется анимация только коэффициента B от -0.1 до -1.0, коэффициент C установлен в 0.5.
В репозитории имеется файл lozi.txt — в нем задана анимация параметров B/C, использованная для генерирования последовательности изображений, из которых собрано видео. Запуск с использованием этого файла, создает изображения размерами 1920×1080:
lozianim.exe -outfolder D:\tmp\png -width 1920 -height 1080 -coefin lozi.txt
Во время исполнения программа показывает, какое по счету изображение сейчас генерируется:
Нажатие ‘q’ прерывает исполнение работы программы. При этом завершается сохранение тех изображений, которые уже генерируются, поэтому выход может занимать некоторое время.
В той же папке в репозитории имеется файл lozi.html — если его сохранить и открыть в браузере, запускается генерирование одной картинки по тому же алгоритму:
Значения коэффициентов B и C можно изменять. Следует заметить, что в браузере изображения на вид несколько отличаются от сгенерированных программой.
В подпапке win имеется архив с собранной программой для Windows, чтобы желающие могли её запустить самостоятельно без сборки.
Ссылки по теме
Статья «Ещё о красоте в простой формуле»
Алгоритм на Python для генерирования Lozi map
Пример видео в разрешении 4К с анимацией фрактала Lozi map
Репозиторий с исходным кодом программы lozianim
Библиотека libpng
Быстрая программа для просмотра изображений FastStone Image Viewer