Lozi map: B = -1.0, C = 0.5
Lozi map: B = -1.0, C = 0.5

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

Введение

В предыдущей статье приведена программа на 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):

Lozi map: B = -1.0, C = 1.0
Lozi map: 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), порождает последнее изображение, в котором довольно сложная картинка вырождается в то, что для меня было несколько неожиданным (как, впрочем, и пример выше с прямоугольным треугольником):

Lozi map: B = -1, C = 0
Lozi map: 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, чтобы желающие могли её запустить самостоятельно без сборки.

Ссылки по теме

Комментарии (10)


  1. agmike
    12.07.2024 12:33

    А музыка, музыка то откуда? Вот очень знакомая, а вспомнить, откуда - не могу (
    (хотя понятно, что speccy)

    udp. TETЯIS 2?


    1. voldemar_d Автор
      12.07.2024 12:33

      В Tetris 2 музыку написал тот же автор (собственно, и обе игры создал он же), но там музыка другая.


  1. voldemar_d Автор
    12.07.2024 12:33
    +1

    В комментариях к видео написано: Jet story

    У меня самого этой игры на Спекки не было, я эту музыку слышал в демке Song in lines.


  1. Tzimie
    12.07.2024 12:33
    +1

    Обожаю уходить в такое. На какое то время забываешь о внешнем мире и даже не хочется делать Роскомнадзор


    1. voldemar_d Автор
      12.07.2024 12:33
      +1

      Я сам в эти картинки долго залипал. Особенно, если их отрендерить 1920х1080 или больше и смотреть на полный экран прямо из PNG. Там столько мелких деталей интересных, ютуб это всё замыливает.


  1. qiper
    12.07.2024 12:33
    +2

    Да и в 4К тоже мыло и артефакты, Ютюб с такими видео несовместим


    1. voldemar_d Автор
      12.07.2024 12:33

      Я перепробовал разные варианты. Самый приемлемый, как ни странно - 1280х720 растягивается до 4К, потом сжимается в Motion-JPEG и заканчивается на ютуб. Ещё как-то похоже на оригинал. Поэтому я и рекомендую, чтобы увидеть всю красоту, запустить программу у себя и посмотреть исходные PNG. Лично меня прямо завораживает.


  1. Limpo444
    12.07.2024 12:33

    Это все на GPU? Если нет можно ли перевести вычисления на него?


    1. voldemar_d Автор
      12.07.2024 12:33

      Здесь всё на CPU. На GPU, наверное, можно перенести, но я пока этим не занимался.


  1. eketerinkatokare
    12.07.2024 12:33

    Очень затягивающее занятие. Долго могу время потратить на это)