Множество Мандельброта в dlib

В одной из статей, посвященных D, я уже описывал множество Мандельброта, которое выглядит очень впечатляюще, но я очень хотел повторить эксперимент еще раз. Такое желание возникло из-за того, что меня не очень устроили результаты прошлого раза: множество получилось перевернутым и к тому же не раскрашенным.

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

Начнем с того, что в качестве графической библиотеки мы возьмем dlib, что позволит практически сразу получить файл с картинкой вне зависимости от того, под какой операционной системой (Windows или Linux) будет работать приложение. Также, сам проект будет собираться с помощью универсальной системы сборки для D — dub.

Создав пустой проект и указав в качестве зависимости dlib, получим обычную заглушку для проекта. В полученный в ходе процедуры инициализации файл app.d в папке source проекта необходимо будет поместить уже рассматривавшийся нами код загрузки палитр Matlab в проект с dlib. Также предварительно нужно будет скачать сами палитры в формате СSV (comma-separated value), ссылка на загрузку которых приведена в этой статье.

Код, который необходимо добавить выглядит так:

import std.complex;

import std.algorithm;
import std.conv;
import std.math;
import std.range;
import std.stdio;
import std.string;
import std.file;

import dlib.image;

Color4f[] getPalette(string filename)
{
    Color4f[] palette;

    Color4f extractField(string triplet)
    {
        Color4f color;
        auto content = triplet.split(";");

        color.r = parse!float(content[0]) / 255.0f;
        color.g = parse!float(content[1]) / 255.0f;
        color.b = parse!float(content[2]) / 255.0f;

        return color;
    }

    palette = (cast(string)(read(filename)))
                                            .splitLines
                                            .map!(a => extractField(a))
                                            .array;

    return palette;
}

После описания функции загрузки палитр, опишем само построение множества Мандельброта в виде итеративного процесса (количества итераций мы можем регулировать так, как считаем нужным, но я поставил уже некоторую «константу»). Построение множества очень просто и уже описывалось ранее, но на всякий случай повторюсь: будем вести расчеты с комплексными числами и возьмем стартовое значение 0+0i, а затем каждый раз будем возводить в квадрат это значение и прибавлять некоторую константу. Константа будет менять последовательно свои действительные и мнимые значения в промежутке от -2 до 2,  а стартовое значение после процедуры с возведением во вторую степень и прибавлением числа, будет заменяться на новое. В ходе такого процесса, который для каждого значения константы выглядит по новому, мы будем проверять, не уходит ли в бесконечность в ходе конечного количества итераций полученное стартовое значение: для этого мы используем доказанный математический факт о том, что если модуль нового стартового значения больше 2, то произошел «уход в бесконечность» и точка явно не принадлежит множеству.

В этот раз, мы немного оптимизирум код, который почти аналогичен тому, что приводился в предыдущей статье про множество Мандельброта, но вместо вычисления модуля, я решил воспользоваться просто вычислением суммы квадратов действительной и мнимой части (до модуля недостает только извлечения полученного значения квадратного корня) и сравнением полученной суммы с 4 (т.е с 2 в квадрате). Такой подход сэкономит приличную часть времени на вычисления, а их тут будет действительно немало.

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

Выделение проверки в отдельный вычислительный блок позволило также реализовать следующую идею: допустим, мы имеем загруженную палитру из 256 цветов (все палитры в файле с палитрами из статьи, которую я упоминал, именно столько цветов и содержат), тогда, если стартовое значение в ходе некоторого количества итераций ушла в бесконечность (т.е точка не попала во множество), то количество итераций может быть использовано в качестве индекса для указания номера цвета в палитре. Именно так можно осуществить раскраску множества Мандельброта: введем дополнительную переменную вне цикла итераций для хранения цвета, и если точка принадлежит множеству, то покрасим ее в цвет минимального значения (это цвет с индексом 0 в палитре), и если не принадлежит — то в цвет, индекс которого в палитре равен количеству итераций.

И вот на этом моменте нам надо предусмотреть следующий момент: если количество итераций будет установлено больше, чем 255, то быть беде — произойдет выход за границы массива, в котором и хранятся цвета палитры. Чтобы этого не произошло воспользуемся замечательным шаблоном сlamp из std.complex, который переводит некотрое значение из стороннего диапазона (диапазон в смысле области значений) в некоторый новый. Данный шаблон принимает число, которое необходимо поместить в новую область значений, минимальный элемент и максимальный элемент нового диапазона значений, и возвращает уже пересчитанный результат.

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

Вдобавок ко всему этому, делается приведение икса и игрека к целочисленному значению, поскольку именно значениями такого типа описываются координаты пикселя на изображении в dlib.

Полный код, при испытании которого не забудьте подставить свой путь к интересующей палитре и получаемому изображению:

import std.complex;

import std.algorithm;
import std.conv;
import std.math;
import std.range;
import std.stdio;
import std.string;
import std.file;

import dlib.image;

Color4f[] getPalette(string filename)
{
    Color4f[] palette;

    Color4f extractField(string triplet)
    {
        Color4f color;
        auto content = triplet.split(";");

        color.r = parse!float(content[0]) / 255.0f;
        color.g = parse!float(content[1]) / 255.0f;
        color.b = parse!float(content[2]) / 255.0f;

        return color;
    }

    palette = (cast(string)(read(filename)))
                                            .splitLines
                                            .map!(a => extractField(a))
                                            .array;

    return palette;
}

auto drawMandelbrotSet(SuperImage superImage, Color4f[] palette, float step)
{
  SuperImage newImage = image(superImage.width, superImage.height);
  
for (float i = -2.0f; i < 2.0f; i += step)
  {
    for (float j = -2.0f; j < 2.0f; j += step) { enum NUMBER_OF_ITERATION = 1024; bool isMandelbrotPoint = true; auto c = complex(i,j); auto z0 = complex(0.0f, 0.0f); Color4f color; bool isBelongToSet(typeof(c) zn) { if (((zn.re ^^ 2) + (zn.im ^^ 2)) > 4)
          {
             return false;
          }
          else
          {
            return true;
          }
      }

      for (size_t k = 0; k < NUMBER_OF_ITERATION; k++)
      {

        auto zn = (z0 * z0) + c;

        if (isBelongToSet(zn))
        {
            z0 = zn;
            color = palette[0];
        }
        else
        {
            isMandelbrotPoint = false;
            size_t index = clamp(k, 0, 255);
            color = palette[index];
            break;
        }
      }

    
        auto X = cast(int) (1024 + 512 * i);
        auto Y = cast(int) (1024 + 512 * j);
        newImage[X, Y] = color;
    }
  }
  return newImage;
}
 
 
void main(string[] args)
{
    auto img = image(2048, 2048);
    auto jetPalette = getPalette("/home/aquareji/Загрузки/Palette/jet.csv");
    img
        .drawMandelbrotSet(jetPalette, .0005f)
        .savePNG("mandelbrot.png");
}

А теперь наглядный результат:

Красивое и занимательное зрелище, которое вы можете сделать еще интереснее, просто поиграв с параметрами шага отрисовки, количеством итераций и координатами стартовой точки (см. определение переменной z0).

А еще, мне при написании этой статьи пришла в голову мысль: а что будет если тоже самое попробовать не в комплексных числах, а скажем в дуальных, а ? Но об этом я может быть расскажу в следующий раз…

Добавить комментарий