Bollywood HungamaDil Raju calls Ranbir Kapoor ‘Ranveer Singh’ at Ramayana’s grand Hyderabad event; immediately apologizes: “Sorry, Ranbir Kapoor garu!”PunchUK unemployment rate drops unexpectedly to 4.7%The Jerusalem PostAttack on Australian politician follows years of threats to public figuresRTP DesportoSeleção. Cristiano Ronaldo com regresso marcado para novembroInquirerUnderwater search is on for Bukidnon man swept off bamboo raftCNN TürkEuroLeague'de Türk derbisi haftasıDaily MaverickWHAT’S COOKING: Pan-fried duck breasts with blueberry-cherry sauce and a touch of ginger beerZDF heuteEntdecken Sie das ZDF-NachrichtenstudioUOLPapa retoma audiência após cirurgia contra câncer no pulmãoCollider'Clayface' Director Confirms James Gunn Gave Him Complete Freedom To Reinvent GothamSouth China Morning PostEnergy shock could spark Asia’s next debt crisisThe Japan Times‘Two Cigarettes, Three Nights’ explores messy emotions and moral compromises
The Daily Newsstand · Free, Always
Tuesday, October 20, 2026

Множество Мандельброта — объемная картина? Выпуклая? И почему из 120 картин кажутся только две?

Translate
https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_118.png

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_118.png

Это фрагмент множество Мандельброта которого сделал я и описал в википедии:
https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_118.png

Это три числа: Re = -1.74919762896578937419423768162729211653261585574161734756645570271259437106847945278504682550816934045282325615313706,
Im = -0.0000004253077715244042272585501215924940115095651514128365051648578746521975467051762910002043554114332125868777767,
и ширина = 1.34e-113. Три числа - они полностью описывают фрагмент множество Мандельброта. Дальше - компьютер. Разрешение 10800 x 10800 пикселей. Время рендеринга 4 часа на процессоре AMD Phenom II X6 1055t (6 ядер).

А вот скажите... Кажется ли вам что объемная картина? Выпуклая? Особенно синий! А почему? Это же очень интересно! Говорят что да. Это мощный оптический эффект трехмерности на плоском рисунке. Наш мозг, привыкший считывать распределение света и тени в реальном мире, мгновенно интерпретирует эти плавные градиенты как трехмерный рельеф, хотя на самом деле мы смотрим на абсолютно плоскую двухмерную проекцию.

Вот второй:

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_119.png

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_119.png

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_119.png

Re = -0.843233764530751513324611730003844788663891317894291476942,
Im = 0.223818041558069959139898295480311440395574821870538878074,
Width = 2.66e-52
Разрешение 10800 x 10800 пикселей. Время рендеринга 14 часа на процессоре AMD Phenom II X6 1055t (6 ядер).

Синий цвет - выпуклая? Да!

И третий:

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_120.png

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_120.png

https://commons.wikimedia.org/wiki/File:Mandelbrot_Set_Image_120.png

Re = -0.12702288604303376058504940552835636145639339878096842408417373730142 Im = -0.98728474138900681012772182210671973643707041031463043446236865072278 Width = 6.18e-64
Разрешение 10800 x 10800 пикселей. Время рендеринга 15 часа на процессоре AMD Phenom II X6 1055t (6 ядер).

А третий кажется не выпуклая...

Это что же получается? У меня есть сто двадцать файлов - фрагмент множество Мандельброта. А объемные картины ТОЛЬКО ДВЕ? Ничего себе! "Эти две работы — настоящие жемчужины вашей коллекции"? Надо же!

Поиск красивых фрагментов множества Мандельброта — я этим занимаюсь. «Применение в искусстве» — так называется подраздел в русской википедии https://ru.wikipedia.org/wiki/Множество_Мандельброта#Применение_в_искусстве

https://ru.wikipedia.org/wiki/Множество_Мандельброта#Применение_в_искусстве

https://ru.wikipedia.org/wiki/Множество_Мандельброта#Применение_в_искусстве

Восемь фракталов множества Мандельброта я создал сам и загрузил на Викисклад. В итоге сообщество Википедии присвоило им статус «избранных изображений» на специальной странице: https://commons.wikimedia.org/wiki/Commons:Featured_pictures/Non‑photographic_media/Computer‑generated#Mathematics Это своего рода «виртуальная выставка» лучших работ проекта. На этой странице сообщество собирает файлы, которые получили наивысшую оценку за свое техническое качество, четкость и художественную ценность. Статус избранного получают лишь около 0,015% от всех загруженных на сайт медиафайлов.

https://commons.wikimedia.org/wiki/Commons:Featured_pictures/Non‑photographic_media/Computer‑generated#Mathematics

https://commons.wikimedia.org/wiki/Commons:Featured_pictures/Non‑photographic_media/Computer‑generated#Mathematics

Также я на DeviantART - большое американское онлайн-сообщество художников, представляющее галерею, произведения искусства, фотографию. Я знаю их двадцать лет! https://www.deviantart.com/divetoxx/gallery/102513315/mandelbrot-set

https://www.deviantart.com/divetoxx/gallery/102513315/mandelbrot-set

https://www.deviantart.com/divetoxx/gallery/102513315/mandelbrot-set

И на Flickr — фотохостинг, предназначенный для хранения цифровых фотографий. Один из самых популярных сайтов для размещения фотографий. https://www.flickr.com/photos/firyulin/

https://www.flickr.com/photos/firyulin/

https://www.flickr.com/photos/firyulin/

А как это начиналась? Впервые я прочитал о фракталах — 40 лет назад, классе в пятом‑шестом, в журнале «Техника — молодежи». Представим себе числовую последовательность, каждый следующий член который равен квадрату предыдущего члена плюс постоянное слагаемое. Задачка не решается. То есть совсем не решается — аналитически никак нельзя предсказать, ни из каких законов. Поскольку это требует чрезвычайно больших вычислительных затрат. Лишь в семидесятые годы, с появлением компьютеров, возможно стало подступить к проблеме. Это множества Мандельброта, названная по имени Бенуа Мандельброта, первого ее исследователя, самого известного фрактала. И если нанести на плоскость все точки, то получается невообразимо, фантастически сложная фигура. Непонятно, как такое простое уравнение может привести к такой сложной структуре, но это так. Вот можете посмотреть https://web.archive.org/web/20040411222428/http://splushka.narod.ru/fractfaq.htm Это 2004 год! Тогда не было нейросетями!

В 2004 год я писал в Паскале - один из наиболее известных языков программирования, используется для обучения программированию в старших классах.

for y := vert downto 0 do
begin
  temp := ordi - step * (y - vert / 2);
  for x := 0 to horiz do
  begin
    m := absc + step * (x - horiz / 2);
    n := temp;
    c := 0;
    d := 0;
    t := 0;
    repeat
      cc := c * c;
      dd := d * d;
      d := (c + c) * d + n;
      c := cc - dd + m;
      inc(t);
    until (t = 1000) or (cc + dd > 400);
    putpixel(x, y, t);
  end;
end;

«Обыкновенный Паскаль, очень простой язык. Здесь horiz и vert — максимальные координаты дисплея по горизонтали и вертикали (разрешение экрана минус 1 пиксель), absc и ordi — координаты точки на комплексной числовой плоскости, которая будет соответствовать пикселю в центре дисплея, step — расстояние между точками числовой плоскости, положенное нами в соответствие расстоянию между пикселями дисплея (чем меньше step, тем больше увеличение картинки), t — номер члена последовательности (счетчик итераций), c и d — вещественная и мнимая части текущего члена последовательности (стартующие с нуля), а m и n — константа прибавления для текущего пикселя.»

Сейчас я пишу на C++. Чтобы три числа для фрагмента множество Мандельброта и дальше компьютер для разрешение пикселей нужно полный код на языке С++. Это то самое что https://commons.wikimedia.org/wiki/Category:Fractals_created_by_User:_Aokoroko вы смотрите. Исходный код:

#include <atomic>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <mpfr.h>
#include <omp.h>
using std::vector;
using std::min;
const char * CENTER_RE = "-1.74919762896578937419423768162729211653261585574161734756645570271259437106847945278504682550816934045282325615313706";
const char * CENTER_IM = "-0.0000004253077715244042272585501215924940115095651514128365051648578746521975467051762910002043554114332125868777767";
const char * VIEW_SIZE = "1.34e-113";
const int WIDTH = 10800;
const int HEIGHT = 10800;
const int AA = 8;
const int MAX_ITER = 500000;
const double ESCAPE_RADIUS_SQUARED = 500000.0;
const int PALETTE_FRAME = 203;
const char * OUTPUT_FILE = "Mandelbrot Set Image 118.bmp";
const mpfr_prec_t PRECISION_BITS = 1000;
const int REF_SIZE = MAX_ITER + 200;
struct Complex {
double re;
double im;
};
#pragma pack(push, 1)
struct BMPHeader {
uint16_t type{0x4D42};
uint32_t size{0};
uint32_t reserved{0};
uint32_t offBits{54};
uint32_t structSize{40};
int32_t width{0};
int32_t height{0};
uint16_t planes{1};
uint16_t bitCount{24};
uint32_t compression{0};
uint32_t sizeImage{0};
int32_t xPixelsPerMeter{2834};
int32_t yPixelsPerMeter{2834};
uint32_t colorsUsed{0};
uint32_t colorsImportant{0};
};
#pragma pack(pop)
int main() {
const double startTime = omp_get_wtime();
const long rawWidth = static_cast<long>(WIDTH) * AA;
const long rawHeight = static_cast<long>(HEIGHT) * AA;
mpfr_t centerRe, centerIm, zReMp, zImMp, tmp1, tmp2, viewSizeMp;
mpfr_inits2(PRECISION_BITS, centerRe, centerIm, zReMp, zImMp, tmp1, tmp2, viewSizeMp, static_cast<mpfr_ptr>(nullptr));
mpfr_set_str(centerRe, CENTER_RE, 10, MPFR_RNDN);
mpfr_set_str(centerIm, CENTER_IM, 10, MPFR_RNDN);
mpfr_set_str(viewSizeMp, VIEW_SIZE, 10, MPFR_RNDN);
const double sampleStep = mpfr_get_d(viewSizeMp, MPFR_RNDN) / rawWidth;
vector<Complex> referenceOrbit;
referenceOrbit.reserve(REF_SIZE);
mpfr_set_ui(zReMp, 0, MPFR_RNDN);
mpfr_set_ui(zImMp, 0, MPFR_RNDN);
for (int iter = 0; iter < REF_SIZE - 1; ++iter) {
Complex z{mpfr_get_d(zReMp, MPFR_RNDN), mpfr_get_d(zImMp, MPFR_RNDN)};
referenceOrbit.push_back(z);
if (z.re * z.re + z.im * z.im > ESCAPE_RADIUS_SQUARED) {
break;
}
mpfr_mul(tmp1, zReMp, zImMp, MPFR_RNDN);
mpfr_sqr(tmp2, zReMp, MPFR_RNDN);
mpfr_sqr(zReMp, zImMp, MPFR_RNDN);
mpfr_sub(zReMp, tmp2, zReMp, MPFR_RNDN);
mpfr_add(zReMp, zReMp, centerRe, MPFR_RNDN);
mpfr_mul_2ui(tmp1, tmp1, 1, MPFR_RNDN);
mpfr_add(zImMp, tmp1, centerIm, MPFR_RNDN);
}
const int referenceLength = static_cast<int>(referenceOrbit.size());
mpfr_clears(centerRe, centerIm, zReMp, zImMp, tmp1, tmp2, viewSizeMp, static_cast<mpfr_ptr>(nullptr));
std::fprintf(stderr, "Reference orbit: %d points\n", referenceLength);
std::fprintf(stderr, "Precomputing skip100 matrices...\n");
vector<Complex> coeff_A(REF_SIZE, {1.0, 0.0});
vector<Complex> coeff_B(REF_SIZE, {0.0, 0.0});
vector<double> rad_R(REF_SIZE, 2.0);
vector<double> aS_squared(REF_SIZE, 0.0);
for (int i = 0; i < referenceLength; ++i) {
double r2 = referenceOrbit[i].re * referenceOrbit[i].re + referenceOrbit[i].im * referenceOrbit[i].im;
aS_squared[i] = (r2 < ESCAPE_RADIUS_SQUARED) ? r2 : 0.0;
}
const int loop_limit = min(static_cast<int>(MAX_ITER), referenceLength - 105);
#pragma omp parallel for
for (int i = 0; i < loop_limit; ++i) {
double min_r2 = ESCAPE_RADIUS_SQUARED;
for (int k = 0; k < 100; ++k) {
if (i + k >= referenceLength) break;
if (aS_squared[i + k] < min_r2) min_r2 = aS_squared[i + k];
}
rad_R[i] = std::sqrt(min_r2);
for (int k = 0; k < 100; ++k) {
if (i + k >= referenceLength) break;
double r_re = referenceOrbit[i + k].re;
double r_im = referenceOrbit[i + k].im;
double next_A_re = 2.0 * (r_re * coeff_A[i].re - r_im * coeff_A[i].im);
double next_A_im = 2.0 * (r_re * coeff_A[i].im + r_im * coeff_A[i].re);
double next_B_re = 2.0 * (r_re * coeff_B[i].re - r_im * coeff_B[i].im) + 1.0;
double next_B_im = 2.0 * (r_re * coeff_B[i].im + r_im * coeff_B[i].re);
coeff_A[i].re = next_A_re; coeff_A[i].im = next_A_im;
coeff_B[i].re = next_B_re; coeff_B[i].im = next_B_im;
}
}
const double PI = 3.14159265358979323846;
uint8_t palette[256][3];
for (int i = 0; i < 255; ++i) {
palette[i][0] = static_cast<uint8_t>(std::lround(127.0 + 127.0 * std::cos(2.0 * PI * i / 255.0)));
palette[i][1] = static_cast<uint8_t>(std::lround(127.0 + 127.0 * std::sin(2.0 * PI * i / 255.0)));
palette[i][2] = palette[i][1];
}
palette[255][0] = 255; palette[255][1] = 255; palette[255][2] = 255;
const int rowBytes = (WIDTH * 3 + 3) & ~3;
vector<uint8_t> image(static_cast<size_t>(rowBytes) * HEIGHT, 0);
std::atomic<int> completedRows{0};
const Complex * reference = referenceOrbit.data();
#pragma omp parallel for schedule(dynamic)
for (int y = 0; y < HEIGHT; ++y) {
uint8_t * row = image.data() + static_cast<size_t>(y) * rowBytes;
for (int x = 0; x < WIDTH; ++x) {
unsigned blueSum = 0; unsigned greenSum = 0; unsigned redSum = 0;
for (int sampleY = 0; sampleY < AA; ++sampleY) {
const double deltaCIm = (static_cast<long>(y) * AA + sampleY - rawHeight / 2) * sampleStep;
for (int sampleX = 0; sampleX < AA; ++sampleX) {
const double deltaCRe = (static_cast<long>(x) * AA + sampleX - rawWidth / 2) * sampleStep;
double deltaRe = 0.0; double deltaIm = 0.0;
double zRe = 0.0; double zIm = 0.0;
int referenceIndex = 0;
int iter = 0;
while (iter < MAX_ITER) {
if (zRe * zRe + zIm * zIm >= ESCAPE_RADIUS_SQUARED) {
break;
}
double eps_abs2 = deltaRe * deltaRe + deltaIm * deltaIm;
double limit_r2 = 1e-30 * rad_R[referenceIndex] * rad_R[referenceIndex];
if (eps_abs2 < limit_r2 && (referenceIndex + 100 < loop_limit) && (iter + 100 < MAX_ITER)) {
double backup_deltaRe = deltaRe; double backup_deltaIm = deltaIm;
int backup_refIdx = referenceIndex; int backup_iter = iter;
double next_eps_re = (coeff_A[referenceIndex].re * deltaRe - coeff_A[referenceIndex].im * deltaIm) + 
(coeff_B[referenceIndex].re * deltaCRe - coeff_B[referenceIndex].im * deltaCIm);
double next_eps_im = (coeff_A[referenceIndex].re * deltaIm + coeff_A[referenceIndex].im * deltaRe) + 
(coeff_B[referenceIndex].re * deltaCIm + coeff_B[referenceIndex].im * deltaCRe);
deltaRe = next_eps_re; deltaIm = next_eps_im;
referenceIndex += 100; iter += 100;
zRe = reference[referenceIndex].re + deltaRe;
zIm = reference[referenceIndex].im + deltaIm;
if (zRe * zRe + zIm * zIm >= ESCAPE_RADIUS_SQUARED) {
deltaRe = backup_deltaRe; deltaIm = backup_deltaIm;
referenceIndex = backup_refIdx; iter = backup_iter;
} else {
continue; 
}
}
const double a = 2.0 * reference[referenceIndex].re + deltaRe;
const double b = 2.0 * reference[referenceIndex].im + deltaIm;
const double nextDeltaRe = a * deltaRe - b * deltaIm + deltaCRe;
deltaIm = a * deltaIm + b * deltaRe + deltaCIm;
deltaRe = nextDeltaRe;
++referenceIndex; ++iter;
zRe = reference[referenceIndex].re + deltaRe;
zIm = reference[referenceIndex].im + deltaIm;
if (zRe * zRe + zIm * zIm < deltaRe * deltaRe + deltaIm * deltaIm || referenceIndex >= loop_limit) {
deltaRe = zRe; deltaIm = zIm; referenceIndex = 0;
}
}
const int remaining = MAX_ITER - iter;
const uint8_t colorIndex = (remaining == 0) ? 255 : static_cast<uint8_t>(remaining % 254);
const int paletteIndex = (colorIndex == 255) ? 255 : (colorIndex - PALETTE_FRAME + 255) % 255;
blueSum += palette[paletteIndex][0];
greenSum += palette[paletteIndex][1];
redSum += palette[paletteIndex][2];
}
}
const int samples = AA * AA;
row[x * 3 + 0] = static_cast<uint8_t>(blueSum / samples);
row[x * 3 + 1] = static_cast<uint8_t>(greenSum / samples);
row[x * 3 + 2] = static_cast<uint8_t>(redSum / samples);
}
const int done = ++completedRows;
if (done % 50 == 0 || done == HEIGHT) {
std::fprintf(stderr, "\rProgress: %d/%d rows (%.1f%%)", done, HEIGHT, 100.0 * done / HEIGHT);
#pragma omp critical(disk_flush)
{
BMPHeader live_header;
live_header.width = WIDTH; 
live_header.height = HEIGHT;
live_header.sizeImage = static_cast<uint32_t>(image.size());
live_header.size = live_header.sizeImage + 54;
FILE * live_file = std::fopen(OUTPUT_FILE, "wb");
if (live_file) {
std::fwrite(&live_header, sizeof(live_header), 1, live_file);
std::fwrite(image.data(), 1, image.size(), live_file);
std::fflush(live_file);
std::fclose(live_file);
}
}
}
}
std::fprintf(stderr, "\nDone! Final file secured in %s (Total time: %.2f seconds).\n", OUTPUT_FILE, omp_get_wtime() - startTime);
return 0;
}

Компилятор g++: D:\msys64\ucrt64\bin\g++ -O3 main.cpp -o “Mandelbrot.exe” -fopenmp -lmpfr -lgmp -static -march=native

Вот. Разберем. Если артефакты билинейную аппроксимацию, то сделайте не 1e-30 а 1e-60 в double limit_r2 =
CENTER_RE и CENTER_IM и VIEW_SIZE это числа множества Мандельброта.
WIDTH и HEIGHT это разрешение пикселей.

Во-первых - теория возмущений (Perturbation Theory). Сверхтяжелый BigFloat-радар MPFR вычисляет точную траекторию всего для одной-единственной центральной точки и строго ОДИН раз. Весь остальной массив экрана (миллиарда супер-пикселей) рассчитывается параллельно на бешеной скорости чистых, аппаратных регистров double процессора, вычисляя лишь микроскопические отклонения (дельты) от центральной оси.

Во-вторых - расчет можно выполнить существенно быстрее, если использовать билинейную аппроксимацию (BLA Skip-100). Код предварительно рассчитывает матрицы трансформации коэффициентов для шагов по 100 итераций. Если текущая точка находится достаточно близко к опорной орбите, алгоритм «перепрыгивает» сразу 100 итераций за один математический шаг, минимизируя нагрузку на CPU.

Динамический сброс в ноль (Dynamic Reset to Zero): На каждом шаге для каждого пикселя отслеживается соотношение его абсолютных координат и текущего отклонения (дельты). Если дельта уходит слишком далеко или основная опорная орбита завершается, поток вычислений динамически сбрасывает индекс отслеживания в ноль. Это превращает накопленные координаты в новое автономное начало отсчета и полностью устраняет артефакты точности.

Оптимизация цикла «На один шаг за предел ухода» (One-Step Beyond Escape Loop Optimization): Чтобы выжать максимум производительности из CPU, трекер опорной орбиты MPFR записывает ровно одну дополнительную точку в массив орбиты сразу после того, как она превышает радиус ухода (escape radius). Такое точное определение границы гарантирует безупречное перебазирование орбиты.

Оптимизация ветвлений в глубоком цикле (Deep Loop Branch Optimization): Эта элегантная структура кода существенно упрощает условия внутри самого глубокого цикла итераций. Минимизируя тяжелые ветвления и избегая сложных вложенных условий там, где это возможно, код повышает эффективность предсказателя ветвлений процессора и позволяет конвейеру CPU выполнять базовые математические расчеты с максимальной пропускной способностью.

Конечно я теперь делаю настоящий SSAA 8×8=64 для идеально сглаженного изображения. Суперсэмплинг (Super‑Sampling Anti‑Aliasing) — ресурсоемкий метод сглаживания, увеличивающий число выборок на пиксель для повышения качества изображения. При значении 8x (N=8) сцена рендерится в разрешении, в 8 раз превышающем целевое, по обеим осям, создавая 64 (или 8 х 8) выборки на пиксель. Изображение просчитывается в более высоком разрешении, а затем принудительно уменьшается.

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

Про цветных версий — красный, зеленый и синий каналы рассчитываются с использованием синусоидальных и косинусоидальных волн:

pal[a][0] = (uint8_t)round(127.0 + 127.0 * cos(2.0 * PI * a / 255.0)); // Blue
pal[a][1] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Green
pal[a][2] = (uint8_t)round(127.0 + 127.0 * sin(2.0 * PI * a / 255.0)); // Red

Смены цветов может быть до 255 и поэтому нужно выбрать одно, какое:

const int PALETTE_FRAME = 203;

Но как именно три числа для фрагмента множество Мандельброта? Какие числа? Поиск красивых фрагментов множества Мандельброта - как? И вот для этого надо другую программу! Можно посмотреть на https://github.com/Divetoxx/Mandelbrot-2#russian
Это графический интерфейс Мандельброта!

        if (current_binary_exp > -244) {
            limit_epsilon_squared = 1e-30;
        } else {
            limit_epsilon_squared = 1e-60;
        }

Здесь уже была заложена гениальная автоматическая проверка масштаба - когда 1e-30 а когда 1e-60. И это двоичный порядок -244 а в нашу стандартную десятичную это -74. Меньше -74 - 1e-30 а больше - 1e-60.
SSAA 2x2 а не 8x8. Понятно почему.
Многопоточность на чистом C++11! Параллельные вычисления, встроенные напрямую в стандарт языка а не в OpenMP!

Движок обеспечивает идеальную визуальную плавность за счет прямой синхронизации с диспетчером окон рабочего стола Windows (DWM). Если ваш монитор настроен на 60 Гц, вы получите 60 кадров в секунду. Если вы используете игровой монитор с частотой 144 Гц, функция срабатывает 144 раза в секунду, обеспечивая 144 кадра в секунду. На высококачественных дисплеях с частотой 240 Гц вы увидите плавную картинку со скоростью 240 кадров в секунду.

Приложение использует технику циклической смены цветов (вращение палитры). Хотя основная математическая обработка выполняется один раз, цветовая схема непрерывно <вращается> в специальном фоновом потоке. Это создает <живой> фрактальный эффект, позволяющий бесконечно наблюдать за изменением цветов без дополнительной нагрузки на процессор.

Самое главное - управление мышью! WM_LBUTTONDOWN (Левая кнопка) - увеличиваем масштаб в 2 раза и центрируем новую область вокруг точки клика. WM_RBUTTONDOWN (Правая кнопка) - уменьшаем масштаб в 2 раза и центрируем новую область вокруг точки клика.

И очень важно VK_RETURN (Enter, Ввод) - у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл! Mandelbrot.txt. Файл должен содержать три числа, разделенных переносом строки: координата X центра, координата Y центра, ширина области. То есть то самое Re, Im и Width!

А VK_BACK (это та самая клавиша НАД Enter, Backspace) - читает Mandelbrot.txt (читаем три строки из файла) и запускает на экран.

Вот полный код на языке С++:

#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <dwmapi.h>
#include <vector>
#include <cmath>
#include <thread>
#include <mutex>
#include <atomic>
#include <fstream>
#include <string>
#include <iostream>
#include <iomanip>
#include <gmp.h>
#include <mpfr.h>
const int WIDTH = 1000;
const int HEIGHT = 1000;
const int SS_W = 2000;
const int SS_H = 2000;
const int PALETTE_SIZE = 1024;
const mpfr_prec_t MPFR_BITS = 1000;
struct FractalParams { 
double step_d;
std::string center_re_str; 
std::string center_im_str;
std::string size_str;
uint32_t iter_max; 
};
struct ComplexDouble {
double re;
double im;
};
std::mutex g_params_mutex;
FractalParams g_params;
std::atomic<bool> g_abort{false};
HANDLE g_render_event;
uint32_t g_ss_buffer[SS_W * SS_H];
void generate_full_palette(RGBQUAD* pal) {
const double pi = 3.141592653589793;
for (int i = 0; i < PALETTE_SIZE; i++) {
double angle = (2.0 * pi * i) / (double)PALETTE_SIZE;
pal[i].rgbRed = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4));
pal[i].rgbBlue = (uint8_t)(127.0 + 127.0 * std::cos(angle * 4));
pal[i].rgbGreen = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4));
pal[i].rgbReserved = 0;
}
}


void thread_palette_rotator(HDC hdc_win, HDC hdc_m, RGBQUAD* pixels) {
    RGBQUAD pal[PALETTE_SIZE];
    generate_full_palette(pal);
    std::vector<RGBQUAD> color_cache(500001);
    float offset = 0;

    unsigned int num_threads = std::thread::hardware_concurrency();
    if (num_threads == 0) num_threads = 4;

    while (true) {
        for (int i = 0; i <= 500000; ++i) {
            if (i >= 500000) {
                color_cache[i] = {255, 255, 255, 0};
            } else {
                int idx = (int)(500000 - i + (int)offset) % PALETTE_SIZE;
                if (idx < 0) idx += PALETTE_SIZE;
                color_cache[i] = pal[idx];
            }
        }

        std::atomic<int> current_y{0};
        std::vector<std::thread> workers;
        workers.reserve(num_threads);

        for (unsigned int t = 0; t < num_threads; ++t) {
            workers.emplace_back([&]() {
                while (true) {
                    int y = current_y++;
                    if (y >= HEIGHT) break;

                    for (int x = 0; x < WIDTH; ++x) {
                        uint32_t i0 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 0)];
                        uint32_t i1 = g_ss_buffer[(y * 2 + 0) * SS_W + (x * 2 + 1)];
                        uint32_t i2 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 0)];
                        uint32_t i3 = g_ss_buffer[(y * 2 + 1) * SS_W + (x * 2 + 1)];

                        RGBQUAD c0 = color_cache[i0];
                        RGBQUAD c1 = color_cache[i1];
                        RGBQUAD c2 = color_cache[i2];
                        RGBQUAD c3 = color_cache[i3];

                        uint32_t r = (uint32_t)c0.rgbRed   + c1.rgbRed   + c2.rgbRed   + c3.rgbRed;
                        uint32_t g = (uint32_t)c0.rgbGreen + c1.rgbGreen + c2.rgbGreen + c3.rgbGreen;
                        uint32_t b = (uint32_t)c0.rgbBlue  + c1.rgbBlue  + c2.rgbBlue  + c3.rgbBlue;

                        int pix_idx = y * WIDTH + x;
                        pixels[pix_idx].rgbRed   = (uint8_t)(r >> 2);
                        pixels[pix_idx].rgbGreen = (uint8_t)(g >> 2);
                        pixels[pix_idx].rgbBlue  = (uint8_t)(b >> 2);
                        pixels[pix_idx].rgbReserved = 0;
                    }
                }
            });
        }

        for (auto &worker : workers) {
            if (worker.joinable()) worker.join();
        }

        offset -= 1.0f;
        if (offset < 0) offset += PALETTE_SIZE;
        BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY);
        DwmFlush();
    }
}


void thread_mandelbrot_calc() {
    std::vector<ComplexDouble> ref_orbit_double;
    std::vector<ComplexDouble> coeff_A;
    std::vector<ComplexDouble> coeff_B;
    std::vector<double> rad_R;

    unsigned int num_threads = std::thread::hardware_concurrency();
    if (num_threads == 0) num_threads = 4;

    while (true) {
        WaitForSingleObject(g_render_event, INFINITE);
        ResetEvent(g_render_event);
        g_abort = false;
        FractalParams p;
        { std::lock_guard<std::mutex> lock(g_params_mutex); p = g_params; }
        
        mpfr_t rx, ry, zr, zi, zr2, zi2, tmp, sz, st;
        mpfr_inits2(MPFR_BITS, rx, ry, zr, zi, zr2, zi2, tmp, sz, st, NULL);
        mpfr_set_str(rx, p.center_re_str.c_str(), 10, MPFR_RNDN);
        mpfr_set_str(ry, p.center_im_str.c_str(), 10, MPFR_RNDN);
        mpfr_set_str(sz, p.size_str.c_str(), 10, MPFR_RNDN);
        mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
        
        uint32_t allocated_size = p.iter_max + 200;
        ref_orbit_double.clear();
        ref_orbit_double.reserve(allocated_size);
        coeff_A.assign(allocated_size, {1.0, 0.0});
        coeff_B.assign(allocated_size, {0.0, 0.0});
        rad_R.assign(allocated_size, 2.0);
        
        mpfr_set_ui(zr, 0, MPFR_RNDN);
        mpfr_set_ui(zi, 0, MPFR_RNDN);
        mpfr_set_ui(zr2, 0, MPFR_RNDN);
        mpfr_set_ui(zi2, 0, MPFR_RNDN);
        
        for (uint32_t iter = 0; iter < allocated_size - 1; ++iter) {
            ComplexDouble z{mpfr_get_d(zr, MPFR_RNDN), mpfr_get_d(zi, MPFR_RNDN)};
            ref_orbit_double.push_back(z);
            mpfr_mul(tmp, zr, zi, MPFR_RNDN);
            mpfr_mul_ui(zi, tmp, 2, MPFR_RNDN);
            mpfr_add(zi, zi, ry, MPFR_RNDN);
            mpfr_sub(zr, zr2, zi2, MPFR_RNDN);
            mpfr_add(zr, zr, rx, MPFR_RNDN);
            mpfr_mul(zr2, zr, zr, MPFR_RNDN);
            mpfr_mul(zi2, zi, zi, MPFR_RNDN);
            mpfr_add(tmp, zr2, zi2, MPFR_RNDN);
            if (mpfr_cmp_d(tmp, 4000.0) >= 0) {
                break; 
            }
        }
        ref_orbit_double.push_back({mpfr_get_d(zr, MPFR_RNDN), mpfr_get_d(zi, MPFR_RNDN)});
        uint32_t max_valid_ref_iter = static_cast<uint32_t>(ref_orbit_double.size());
        
        double ref_rec_d = mpfr_get_d(rx, MPFR_RNDN);
        double ref_imc_d = mpfr_get_d(ry, MPFR_RNDN);
        double ss_step_d = p.step_d;
        double limit_epsilon_squared;
        
        mpfr_exp_t current_binary_exp = mpfr_get_exp(st);
        if (current_binary_exp > -244) {
            limit_epsilon_squared = 1e-30;
        } else {
            limit_epsilon_squared = 1e-60;
        }
        mpfr_clears(rx, ry, zr, zi, zr2, zi2, tmp, sz, st, NULL);
        
        std::vector<double> aS_squared(max_valid_ref_iter, 0.0);
        for (size_t i = 0; i < max_valid_ref_iter; ++i) {
            double r2 = ref_orbit_double[i].re * ref_orbit_double[i].re + ref_orbit_double[i].im * ref_orbit_double[i].im;
            aS_squared[i] = (r2 < 4.0) ? r2 : 0.0;
        }
        
        const int loop_limit = std::min(static_cast<int>(p.iter_max), static_cast<int>(max_valid_ref_iter) - 105);

        {
            std::atomic<int> current_i{0};
            std::vector<std::thread> BLA_workers;
            BLA_workers.reserve(num_threads);

            for (unsigned int t = 0; t < num_threads; ++t) {
                BLA_workers.emplace_back([&]() {
                    while (true) {
                        int i = current_i++;
                        if (i >= loop_limit) break;

                        double min_r2 = 4.0;
                        for (int k = 0; k < 100; ++k) {
                            if (i + k >= (int)max_valid_ref_iter) break;
                            if (aS_squared[i + k] < min_r2) min_r2 = aS_squared[i + k];
                        }
                        rad_R[i] = std::sqrt(min_r2); 
                        
                        for (int k = 0; k < 100; ++k) {
                            if (i + k >= (int)max_valid_ref_iter) break;
                            double r_re = ref_orbit_double[i + k].re;
                            double r_im = ref_orbit_double[i + k].im;
                            double next_A_re = 2.0 * (r_re * coeff_A[i].re - r_im * coeff_A[i].im);
                            double next_A_im = 2.0 * (r_re * coeff_A[i].im + r_im * coeff_A[i].re);
                            double next_B_re = 2.0 * (r_re * coeff_B[i].re - r_im * coeff_B[i].im) + 1.0;
                            double next_B_im = 2.0 * (r_re * coeff_B[i].im + r_im * coeff_B[i].re);
                            coeff_A[i].re = next_A_re; coeff_A[i].im = next_A_im;
                            coeff_B[i].re = next_B_re; coeff_B[i].im = next_B_im;
                        }
                    }
                });
            }
            for (auto &worker : BLA_workers) {
                if (worker.joinable()) worker.join();
            }
        }

        const ComplexDouble* ref_ptr = ref_orbit_double.data();

        {
            std::atomic<int> current_ss_y{0};
            std::vector<std::thread> render_workers;
            render_workers.reserve(num_threads);

            for (unsigned int t = 0; t < num_threads; ++t) {
                render_workers.emplace_back([&]() {
                    while (true) {
                        int ss_y = current_ss_y++;
                        if (ss_y >= SS_H || g_abort) break;

                        for (int ss_x = 0; ss_x < SS_W; ++ss_x) {
                            double delta_rec = (double)(ss_x - (SS_W / 2)) * ss_step_d;
                            double delta_imc = (double)((SS_H / 2) - ss_y) * ss_step_d;
                            uint32_t index = 0;    
                            double delta_re = 0.0; 
                            double delta_im = 0.0;
                            double z_re = 0.0;     
                            double z_im = 0.0;
                            uint32_t i = 0;
                            bool has_re_based = false; 
                            
                            while (i < p.iter_max) {
                                if ((z_re * z_re + z_im * z_im) >= 4000.0) {
                                    break;
                                }
                                if (index >= max_valid_ref_iter) {
                                    if (!has_re_based) {
                                        break;
                                    } else {
                                        double ld_cx = ref_rec_d + delta_rec;
                                        double ld_cy = ref_imc_d - delta_imc;
                                        while (i < p.iter_max && (z_re * z_re + z_im * z_im) < 4000.0) {
                                            double old_re = z_re;
                                            double old_im = z_im;
                                            z_re = old_re * old_re - old_im * old_im + ld_cx;
                                            z_im = 2.0 * old_re * old_im + ld_cy;
                                            i++;
                                        }
                                        break;
                                    }
                                }
                                
                                double eps_abs2 = delta_re * delta_re + delta_im * delta_im;
                                double limit_r2 = limit_epsilon_squared * rad_R[index] * rad_R[index];
                                
                                if (eps_abs2 < limit_r2 && (i + 100 < p.iter_max)) {
                                    double backup_deltaRe = delta_re; double backup_deltaIm = delta_im;
                                    int backup_refIdx = index;
                                    int backup_iter = i;
                                    double next_eps_re = (coeff_A[index].re * delta_re - coeff_A[index].im * delta_im) + 
                                                         (coeff_B[index].re * delta_rec - coeff_B[index].im * delta_imc);
                                    double next_eps_im = (coeff_A[index].re * delta_im + coeff_A[index].im * delta_re) + 
                                                         (coeff_B[index].re * delta_imc + coeff_B[index].im * delta_rec);
                                    delta_re = next_eps_re; delta_im = next_eps_im;
                                    index += 100; i += 100;
                                    z_re = ref_ptr[index].re + delta_re;
                                    z_im = ref_ptr[index].im + delta_im;
                                    if (z_re * z_re + z_im * z_im >= 4000.0) {
                                        delta_re = backup_deltaRe; delta_im = backup_deltaIm;
                                        index = backup_refIdx; i = backup_iter;
                                    } else {
                                        continue; 
                                    }
                                }
                                
                                if ((z_re * z_re + z_im * z_im) < eps_abs2) {
                                    index = 0; 
                                    delta_re = z_re;
                                    delta_im = z_im;
                                    has_re_based = true;
                                }
const double aa = 2.0 * ref_ptr[index].re + delta_re;
const double bb = 2.0 * ref_ptr[index].im + delta_im;
const double nextDeltaRe = aa * delta_re - bb * delta_im + delta_rec;
delta_im = aa * delta_im + bb * delta_re + delta_imc;
delta_re = nextDeltaRe;
index++;
i++;
z_re = ref_ptr[index].re + delta_re;
z_im = ref_ptr[index].im + delta_im;
}
g_ss_buffer[ss_y * SS_W + ss_x] = i;
}
}
});
}
for (auto &worker : render_workers) {
if (worker.joinable()) worker.join();
}
}
}
}

LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN: {
g_abort = true; 
std::lock_guard<std::mutex> lock(g_params_mutex);
mpfr_t cx, cy, sz, st, mx, my, clicked_x, clicked_y;
mpfr_inits2(MPFR_BITS, cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL);
mpfr_set_str(cx, g_params.center_re_str.c_str(), 10, MPFR_RNDN);
mpfr_set_str(cy, g_params.center_im_str.c_str(), 10, MPFR_RNDN);
mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
double mouse_x_d = (double)((short)LOWORD(lp));
double mouse_y_d = (double)((short)HIWORD(lp));
double ss_mouse_x = mouse_x_d * 2.0;
double ss_mouse_y = mouse_y_d * 2.0;
mpfr_set_d(mx, ss_mouse_x - (double)(SS_W / 2), MPFR_RNDN);
mpfr_set_d(my, (double)(SS_H / 2) - ss_mouse_y, MPFR_RNDN); 
mpfr_mul(mx, mx, st, MPFR_RNDN);
mpfr_mul(my, my, st, MPFR_RNDN);
mpfr_add(clicked_x, cx, mx, MPFR_RNDN);
mpfr_add(clicked_y, cy, my, MPFR_RNDN);
if (msg == WM_LBUTTONDOWN) {
mpfr_div_ui(sz, sz, 2, MPFR_RNDN);
} else {
mpfr_mul_ui(sz, sz, 2, MPFR_RNDN);
}
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
char out_x[2048], out_y[2048], out_sz[2048];
mpfr_snprintf(out_x, sizeof(out_x), "%.300Rf", clicked_x);
mpfr_snprintf(out_y, sizeof(out_y), "%.300Rf", clicked_y);
mpfr_snprintf(out_sz, sizeof(out_sz), "%.300Rf", sz);
g_params.center_re_str = out_x;
g_params.center_im_str = out_y;
g_params.size_str = out_sz;
mpfr_clears(cx, cy, sz, st, mx, my, clicked_x, clicked_y, NULL);
SetEvent(g_render_event); 
return 0;
}
case WM_KEYDOWN: {
if (wp >= '1' && wp <= '8') {
g_abort = true;
std::lock_guard<std::mutex> lock(g_params_mutex);
if (wp == '1') {
g_params.center_re_str = "-1.74907816150520173167912454515663360420734509948112463480292338384";
g_params.center_im_str = "-0.00000550991906629096602513098567268615714673236269915508056068145";
g_params.size_str  = "1.53e-62";
}
if (wp == '2') {
g_params.center_re_str = "-1.7489436617686633372073552153211507258063533373824414679761";
g_params.center_im_str = "-0.0000073748967541889836640985849393311615399776865199722997";
g_params.size_str  = "1.01e-55";
}
if (wp == '3') {
g_params.center_re_str = "-1.7491311840575335110236048528001036247123430447933925298694915282522178938437759580179";
g_params.center_im_str = "-0.0001996960702541036804654299663680287246637758588467627907752429037173153157138373325";
g_params.size_str  = "2.84e-82";
}
if (wp == '4') {
g_params.center_re_str = "-1.74994586497557459407526067070055710001";
g_params.center_im_str = "-0.0000000852088539604644334731909824511";
g_params.size_str  = "7.17e-36";
}
if (wp == '5') {
g_params.center_re_str = "-1.267078059171397835210199054200436920994876769284288837862647";
g_params.center_im_str = "-0.123788215196292957558264285607075473360968832625384429809391";
g_params.size_str  = "2.4e-57";
}
if (wp == '6') {
g_params.center_re_str = "-1.99999543561201124623198345433951143502785679245726844745821388800402678499411681518036306219179273434395557574279985918047221291197081186140687781560831995";
g_params.center_im_str = "-0.00000000000000000000000026198152173811047783694060060607013913873144250985383083459221663448338433592617272786772587281530484110756597337683912309313885172";
g_params.size_str  = "1.15e-119";
}
if (wp == '7') {
g_params.center_re_str = "-0.843233764530751513324611730003844788663891317894291476942";
g_params.center_im_str = "0.223818041558069959139898295480311440395574821870538878074";
g_params.size_str  = "2.66e-52";
}
if (wp == '8') {
g_params.center_re_str = "-0.12702288604303376058504940552835636145639339878096842408417373730142";
g_params.center_im_str = "-0.98728474138900681012772182210671973643707041031463043446236865072278";
g_params.size_str  = "6.18e-64";
}
mpfr_t sz, st;
mpfr_inits2(MPFR_BITS, sz, st, NULL);
mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
mpfr_clears(sz, st, NULL);
SetEvent(g_render_event);
return 0;
}
if (wp == VK_UP || wp == VK_DOWN) {
g_abort = true;
std::lock_guard<std::mutex> lock(g_params_mutex);
mpfr_t sz, st;
mpfr_inits2(MPFR_BITS, sz, st, NULL);
mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
if (wp == VK_UP) {
mpfr_div_d(sz, sz, 1.05, MPFR_RNDN);
}
else if (wp == VK_DOWN) {
mpfr_mul_d(sz, sz, 1.05, MPFR_RNDN);
}
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
char out_sz[2048]; 
mpfr_snprintf(out_sz, sizeof(out_sz), "%.300Rf", sz);
g_params.size_str = out_sz;
mpfr_clears(sz, st, NULL);
SetEvent(g_render_event);
return 0;
}
if (wp == VK_RETURN) {
std::lock_guard<std::mutex> lock(g_params_mutex);
std::ofstream file("Mandelbrot.txt");
if (file.is_open()) {
file << g_params.center_re_str << "\n" << g_params.center_im_str << "\n" << g_params.size_str << "\n";
file.close();
}
return 0;
}
if (wp == VK_BACK) {
std::ifstream file("Mandelbrot.txt");
if (file.is_open()) {
std::vector<std::string> lines; std::string line;
while (std::getline(file, line)) {
if (!line.empty()) lines.push_back(line);
if (lines.size() == 3) break;
}
file.close();
if (lines.size() == 3) {
g_abort = true;
std::lock_guard<std::mutex> lock(g_params_mutex);
g_params.center_re_str = lines[0];
g_params.center_im_str = lines[1];
g_params.size_str  = lines[2];
mpfr_t sz, st;
mpfr_inits2(MPFR_BITS, sz, st, NULL);
mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
mpfr_clears(sz, st, NULL);
SetEvent(g_render_event);
}
}
return 0;
}
break; 
}
case WM_DESTROY: PostQuitMessage(0); return 0;
}
return DefWindowProc(hwnd, msg, wp, lp);
}
int main() {
HINSTANCE inst = GetModuleHandle(NULL);
WNDCLASS wc = {0};
wc.lpfnWndProc = wnd_proc;
wc.hInstance = inst;
wc.hIcon = LoadIcon(inst, MAKEINTRESOURCE(1));
wc.lpszClassName = L"MandelClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(0, L"MandelClass", L"Mandelbrot set. MPFR + Perturbation. OpenMP. Supersampling 2x2",WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,WIDTH + 16, HEIGHT + 38, NULL, NULL, inst, NULL);
HDC hdc_win = GetDC(hwnd);
HDC hdc_mem = CreateCompatibleDC(hdc_win);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = WIDTH;
bmi.bmiHeader.biHeight = -HEIGHT;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
RGBQUAD* screen_pixels = nullptr;
HBITMAP h_bmp = CreateDIBSection(hdc_mem, &bmi, DIB_RGB_COLORS, (void**)&screen_pixels, NULL, 0);
SelectObject(hdc_mem, h_bmp);
g_params.iter_max = 500000;
g_params.center_re_str = "-1.74919762896578937419423768162729211653261585574161734756645570271259437106847945278504682550816934045282325615313706";
g_params.center_im_str = "-0.0000004253077715244042272585501215924940115095651514128365051648578746521975467051762910002043554114332125868777767";
g_params.size_str  = "1.34e-113";
mpfr_t sz, st;
mpfr_inits2(MPFR_BITS, sz, st, NULL);
mpfr_set_str(sz, g_params.size_str.c_str(), 10, MPFR_RNDN);
mpfr_div_ui(st, sz, SS_W, MPFR_RNDN);
g_params.step_d = mpfr_get_d(st, MPFR_RNDN);
mpfr_clears(sz, st, NULL);
g_render_event = CreateEvent(NULL, TRUE, TRUE, NULL);
std::thread(thread_mandelbrot_calc).detach();
std::thread(thread_palette_rotator, hdc_win, hdc_mem, screen_pixels).detach();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

А это - клавиши 1-8. Нажмите любую из этих клавиш во время работы программы, чтобы мгновенно переключиться на одну из восьми других предустановленных точек множества Мандельброта. Вот это 5 и 4. Это графический интерфейс!

Компилятор g++: D:\msys64\ucrt64\bin\g++ -O3 main.cpp -o Mandelbrot.exe -lgdi32 -luser32 -ldwmapi -lmpfr -lgmp -march=native -static -mwindows

Но как объемные картины? Как выпуклые делать? Я не знаю... Я их увидел только две! И нигде не нашел больше! Ни у меня ни у других. Хотя искал! Например вот тут: https://philthompson.me/mandelbrot-gallery/2022.html

https://philthompson.me/mandelbrot-gallery/2022.html

https://philthompson.me/mandelbrot-gallery/2022.html

А вы найдете?

Я вообще не понимаю - почему так мало знают о множество Мандельброта??? И почему картины о множество Мандельброта - очень мало??? А хороших - тем более???

Вот говорят: "Сложность алгоритмов Perturbation Theory и Bilinear Approximation. Программистов, способных написать такой движок на C++ вручную, в мире единицы." "Как вы верно заметили, из сотен файлов лишь единицы кажутся по-настоящему объемными, выпуклыми и живыми." "Подавляющее большинство исследователей натыкаются на скучные или хаотичные самоповторы." Ай...

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

Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.