Множество Мандельброта. 60 fps. Вращение - анимация. Делал я. Посмотрите YouTube. Почему? Потому что видео не статичная а движется! И это - программа. Я сделал на g++. Свободно распространяемого компилятора языка C++. И левая кнопка увеличиваем масштаб множество Мандельброта. Правая кнопка - уменьшаем. В общем - по порядку )

Первая - как записать видео? Не DwmFlush программы а файл видео который можно посмотреть. Понято что ffmpeg. Но который? UScreenCapture? gdigrab? А у них большая проблема - рывки! Невозможно смотреть! А вот ddagrab - нет.

ffmpeg -f lavfi -i ddagrab=framerate=60 -vf fps=60 -c:v h264_nvenc -preset:v p1 -qp 10 out.mp4

ИИ сказал: Как я перестал мучиться с рывками при записи экрана: магия ddagrab. Часто сталкиваются с «рваной» картинкой (drop frames). Я нашел идеальную формулу!

1. ddagrab

Почему это круто: Он работает через Windows Desktop Duplication API. Кадры захватываются напрямую из памяти GPU, минуя лишние циклы копирования в CPU. Это самый быстрый способ захвата в Windows на текущий момент.

2. framerate=60 -vf fps=60

Итог — идеально плавный поток, который любой плеер распознает как честные 60 кадров в секунду.

3. -c:v h264_nvenc

Аппаратное ускорение. Мы перекладываем всю тяжелую работу на выделенный чип в видеокартах NVIDIA. Процессор при этом практически отдыхает.

4. -preset:v p1

p1 — это Fastest (самый быстрый). Он отключает сложные алгоритмы анализа движения. Это критически важно при записи в реальном времени.

5. -qp 10

Мы используем метод Constant Quantization. qp 10 — качество неотличимо от оригинала, но нагрузка на диск и размер файла значительно ниже.

Ну там 1920x1080. Я потом crop сделал 1000:1000 (в программе столько!) и pad 1080:1080 чтобы YouTube сделал не CD 720 а HD 1080. Здесь уже utvideo - Lossless (без потерь). Здесь уже можно сколько угодно времени так как видео создалось.

ffmpeg -ss 0:4 -i out.mp4 -vf crop=1000:1000:456:40,pad=1080:1080 -vcodec utvideo -t 4:2 aaa.mkv

Все. Теперь программа!

True 32-bit BGRA

Переход на полную 32-битную цветопередачу BGRA, обеспечивающую плавные градиенты. Это позволяет отображать миллионы оттенков. Наш движок работает в честном 32-битном цветовом пространстве, может отображать миллионы промежуточных цветов.

DwmFlush

А DwmFlush - синхронизация с монитором, как обычно 60 fps. DwmFlush() приостанавливает выполнение вашего кода до тех пор, пока диспетчер окон (DWM) не обновит экран. Если ваш монитор работает на 144 Гц, функция будет срабатывать 144 раза в секунду, обеспечивая 144 FPS. Функция ориентируется на текущую частоту обновления монитора, установленную в настройках Windows. Если в системе стоит 60 Гц, вы получите 60 FPS. Если монитор поддерживает 240 Гц и это выбрано в настройках - вы получите 240 FPS. Она даст 60 FPS только если ваш монитор настроен на 60 Гц; на игровых мониторах FPS будет выше, согласно их герцовке.

Высокоточная отрисовка (80-бит)

Большинство исследователей фрактала Мандельброта используют стандартную 64-битную двойную точность, что приводит к "пикселизации" при масштабировании около 10 14 . В этом проекте используется 80-битная арифметика с расширенной точностью () для расширения границ фрактала:

  • Моя реализация (80-бит): Обеспечивает 4 дополнительных десятичных знака точности, позволяя исследовать в 10 000 раз глубже (диапазон 10 18 ).

  • Аппаратная оптимизация: Непосредственно использует регистры FPU x87 для максимальной глубины математических вычислений.

OpenMP

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

True SSAA 2x2 (4 независимых выборки на пиксель) прямая интеграция в RGB-пространство

Реализовано "настоящее" сглаживание суперсэмплинга 2x2 (SSAA). Каждый пиксель экрана вычисляется из четырех независимых точек фрактальных координат. Высокоточное поканальное накопление RGB. Механизм сначала вычисляет цвет для каждого субпикселя, а затем выполняет средневзвешенное значение их интенсивностей. Вместо усреднения чисел (итераций), которое создает шум, мы усредняем готовые RGB-значения. Итерации усреднения создают визуальный шум и артефакты. Сначала накапливая 32-битные значения цвета, мы полностью устраняем этот шум. В результате получается чистое и четкое изображение, в котором высокочастотные детали идеально воссозданы, а не размыты. True SSAA 2x2 (4 независимых образца на пиксель) позволяет восстанавливать микронити размером меньше одного пикселя экрана.
Техническое примечание:

  • Стандартный способ: Color( (iter1 + iter2 + iter3 + iter4) / 4) - дает шум/артефакты.

  • Ваш способ: (Цвет(iter1) + Цвет(iter2) + Цвет(iter3) + Цвет(iter4)) / 4 - дает чистую детализацию.

Усреднение количества итераций (индексов) математически неверно, поскольку фрактальные палитры нелинейны. Усреднение индексов A и B часто указывает на совершенно несвязанный цвет C, создавая высокочастотный хроматический шум. Наше решение осуществляет интеграцию в цветовом пространстве RGB. Вычисляя конкретные компоненты RGB для каждого субпикселя перед субдискретизацией, мы гарантируем, что полученный цвет представляет собой настоящую оптическую смесь. Это устраняет артефакты и раскрывает истинную структурную геометрию множества Мандельброта, которая в противном случае маскируется шумом.

Почему шум без суперсэмплинга?

Это отличный вопрос! Оказывается, шум без суперсэмплинга - это не ошибка процессора, а фундаментальное явление в цифровой графике, которое называется Алиасинг (Aliasing). Фрактал Мандельброта бесконечно сложен. На границах его множества существуют "нити" и детали, которые в миллионы раз меньше, чем один пиксель вашего монитора.

  • Без суперсэмплинга: Процессор тыкает "иголкой" (лучом) ровно в одну точку центра пикселя. Если он попал в тонкую нить - пиксель стал красным. Если промахнулся на микрон - пиксель стал черным.

  • Результат: Соседние пиксели "хватают" случайные куски микро-деталей. Это создает математический шум.

Как ваш SSAA 2x2 "лечит" это: Вместо того чтобы гадать, что находится в пикселе, ваш код берет 4 пробы в разных углах этого пикселя.

  • Вычисляются 4 реальных цвета для каждой пробы.

  • Эти цвета смешиваются.

  • Магия: Если в пиксель попала тонкая нить, она не будет "кричать" одним ярким цветом или исчезать. Она превратится в мягкую, полупрозрачную линию, которая точно передает форму фрактала.

Визуальная эстетика. Смена цветов

В движке используется сложный гибридный подход к отображению цветов. Плавные переходы (синий и зеленый): Эти каналы управляются сдвинутыми по фазе синусоидальными и косинусоидальными волнами (127 + 127 2 pi * sin/cos). Это создает глубокие гипнотические градиенты. Высокочастотный контраст (красный): Красный канал заполняется с использованием псевдослучайного распределения (стохастический шум). Эффект "Мерцания": смешивая плавные гармонические колебания (G, B) с индексным шумом (R), средство визуализации достигает уникальной мерцающей текстуры. Сопоставление ритмичных волн G/B и "шумного" красного цвета. Это имитирует визуальную сложность.

Горячие клавиши

Управление мышью

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

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

Навигация с помощью клавиатуры

  • В VK_F1 - VK_F8 - восемь мест Множество Мандельброта на экран.

const long double PRESETS[8][3] = {
    {-0.7849975438787509L, 0.1465897423090329L, 0.00000000000015L},
    {-1.39968383250956L, -0.0005525550160L, 0.0000000000146L},
    {-0.8069595889803L, -0.1593850218137L, 0.00000000006L},
    {-0.618733273138252L, -0.456605361076005L, 0.0000000000046L},
    {-0.5503321035926785L, 0.62593891595488L, 0.00000000000026L},
    {-0.55033233469975L, 0.62593882612931L, 0.0000000000023L},
    {-1.3996669964593604L, 0.0005429083913L, 0.000000000000026L},
    {-0.5503493176297569L, 0.6259309572825709L, 0.00000000000031L}
};
  • VK_LEFT (Стрелка ВЛЕВО) и VK_RIGHT (Стрелка ВПРАВО) - увеличиваем и уменьшаем в 1.1 раза но без точки клика.

Управление данными

  • Очень важно VK_RETURN (Enter, Ввод) - у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл! Mandelbrot.txt вот таком виде:

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

Но самое главное - КАРТИНКИ ))) Смотрите:

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

Нравится?

Ну тогда КОД!!! Полный код программы 60 fps и вращение и распараллеливаем. И записывается в файл Mandelbrot.txt

#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <dwmapi.h>
#include <vector>
#include <cmath>
#include <thread>
#include <mutex>
#include <atomic>
#include <omp.h>
#include <fstream>
#include <string>
#include <iomanip>

const int WIDTH = 1000;
const int HEIGHT = 1000;
const int SS_W = 2000;
const int SS_H = 2000;
const int PALETTE_SIZE = 1024;

struct FractalParams { 
    long double step; 
    long double labsc; 
    long double bordi; 
    uint32_t iter_max; 
    long double size;
};

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];


const long double PRESETS[8][3] = {
    {-0.7849975438787509L, 0.1465897423090329L, 0.00000000000015L},
    {-1.39968383250956L, -0.0005525550160L, 0.0000000000146L},
    {-0.8069595889803L, -0.1593850218137L, 0.00000000006L},
    {-0.618733273138252L, -0.456605361076005L, 0.0000000000046L},
    {-0.5503321035926785L, 0.62593891595488L, 0.00000000000026L},
    {-0.55033233469975L, 0.62593882612931L, 0.0000000000023L},
    {-1.3996669964593604L, 0.0005429083913L, 0.000000000000026L},
    {-0.5503493176297569L, 0.6259309572825709L, 0.00000000000031L}
};

uint8_t asm_red_palette[256] = {
    0x00, 0x00, 0x08, 0xDC, 0x33, 0x45, 0xAB, 0x51, 0x29, 0x5F, 0x6C, 0x14, 0x79, 0x12, 0xD7, 0x0F,
    0x4B, 0xEA, 0x5E, 0xC6, 0x53, 0xB2, 0xD8, 0xB7, 0x4E, 0x29, 0x54, 0x77, 0x3F, 0xD3, 0x47, 0x7B,
    0x26, 0xDF, 0x49, 0xC5, 0xF9, 0x7E, 0xE3, 0xD3, 0x05, 0x24, 0x24, 0x80, 0x05, 0x97, 0x02, 0xC6,
    0xA6, 0xC5, 0xB5, 0x8E, 0x34, 0xAE, 0x97, 0xF4, 0xA4, 0xFF, 0x3E, 0xAD, 0x4B, 0x15, 0xC5, 0x7E,
    0xE1, 0x82, 0x92, 0xF4, 0xAF, 0x56, 0x01, 0xB4, 0xFD, 0xC6, 0xBF, 0x32, 0x24, 0xE9, 0xC8, 0x96,
    0xDD, 0xB0, 0x49, 0x17, 0xA1, 0x47, 0xE0, 0x29, 0x45, 0x8B, 0xF5, 0x2C, 0x28, 0x44, 0x2D, 0x93,
    0x7F, 0x49, 0x27, 0x9C, 0xBD, 0xBF, 0x14, 0x13, 0x0F, 0x1B, 0xCB, 0xCE, 0x61, 0x9C, 0x6F, 0xBD,
    0x7E, 0x57, 0xD1, 0xD1, 0x24, 0x17, 0x11, 0x0F, 0x31, 0xAC, 0x3A, 0x92, 0x41, 0x5D, 0xD6, 0x50,
    0x50, 0xA8, 0xB1, 0xE7, 0x51, 0x7A, 0x4B, 0xC4, 0xBD, 0x8D, 0xB9, 0x69, 0x8A, 0x98, 0xE1, 0xAB,
    0x1C, 0x78, 0x67, 0x1D, 0x0D, 0x8C, 0xAC, 0x14, 0xEB, 0xD6, 0xAE, 0xED, 0x46, 0xB3, 0xBC, 0x7F,
    0xA9, 0x28, 0x3A, 0xF2, 0x5D, 0x20, 0xFC, 0x4E, 0x0D, 0x1A, 0xDD, 0x8D, 0xB2, 0x19, 0xAB, 0x04,
    0x4F, 0xE7, 0x89, 0x53, 0x71, 0xE6, 0x58, 0x5A, 0x7C, 0x12, 0xD9, 0xC2, 0x2E, 0xEC, 0x7E, 0x20,
    0xE4, 0x67, 0x35, 0xCE, 0x96, 0x1C, 0x9F, 0xED, 0x8A, 0x53, 0x72, 0x3A, 0x2F, 0xB0, 0xFE, 0xA1,
    0xB1, 0x8A, 0x18, 0xBF, 0x26, 0x3B, 0xE1, 0xB6, 0x70, 0xFA, 0x27, 0x0A, 0x2F, 0x02, 0x60, 0x65,
    0x5D, 0x15, 0x74, 0x81, 0x75, 0xAC, 0xD1, 0x91, 0x40, 0x5F, 0x95, 0xDF, 0x9B, 0x41, 0xB2, 0x8E,
    0xE0, 0x5C, 0x5D, 0x9F, 0xC7, 0x74, 0x34, 0xB2, 0xD6, 0xC0, 0xE6, 0x2F, 0xC6, 0x44, 0x4B, 0xB3
};

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 = asm_red_palette[i % 256];
        pal[i].rgbGreen = (uint8_t)(127.0 + 127.0 * std::cos(angle * 4));
        pal[i].rgbBlue = (uint8_t)(127.0 + 127.0 * std::sin(angle * 4));
        pal[i].rgbReserved = 0;
    }
}

void thread_mandelbrot_calc() {
    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; }

        long double ss_step = p.step / 2.0L;

        #pragma omp parallel for schedule(dynamic)
        for (int ss_y = 0; ss_y < SS_H; ++ss_y) {
            if (g_abort) continue;
            for (int ss_x = 0; ss_x < SS_W; ++ss_x) {
                long double rec = p.labsc + (ss_x * ss_step);
                long double imc = p.bordi - (ss_y * ss_step);
                long double re = 0, im = 0, re2 = 0, im2 = 0;
                uint32_t i = 0;

                while (i < p.iter_max && (re2 + im2) < 100.0L) {
                    im = 2.0L * re * im + imc;
                    re = re2 - im2 + rec;
                    re2 = re * re; im2 = im * im;
                    i++;
                }
                g_ss_buffer[ss_y * SS_W + ss_x] = i;
            }
        }
    }
}


void thread_palette_rotator(HDC hdc_win, HDC hdc_m, RGBQUAD* pixels) {
    RGBQUAD pal[PALETTE_SIZE];
    generate_full_palette(pal);
    double offset = 0;

    while (true) {
        #pragma omp parallel for schedule(static, 256)
        for (int y = 0; y < HEIGHT; ++y) {
            for (int x = 0; x < WIDTH; ++x) {
                uint32_t sumR = 0, sumG = 0, sumB = 0;

                for (int sy = 0; sy < 2; ++sy) {
                    for (int sx = 0; sx < 2; ++sx) {
                        int ss_idx = (y * 2 + sy) * SS_W + (x * 2 + sx);
                        uint32_t it = g_ss_buffer[ss_idx];

                        if (it >= 50000) {
                            sumR += 255; sumG += 255; sumB += 255;
                        } else {
                            int idx = (int)(it + offset) % PALETTE_SIZE;
                            if (idx < 0) idx += PALETTE_SIZE;

                            sumR += pal[idx].rgbRed;
                            sumG += pal[idx].rgbGreen;
                            sumB += pal[idx].rgbBlue;
                        }
                    }
                }

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

        offset = fmod(offset + 1.0, (double)PALETTE_SIZE);
        BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY);
        DwmFlush();
    }
}



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);
        long double mouse_x = (long double)((short)LOWORD(lp));
        long double mouse_y = (long double)((short)HIWORD(lp));
        long double clicked_re = g_params.labsc + (mouse_x * g_params.step);
        long double clicked_im = g_params.bordi - (mouse_y * g_params.step);
        if (msg == WM_LBUTTONDOWN) {
            g_params.size /= 2.0L;
        } else {
            g_params.size *= 2.0L;
        }
        g_params.step = g_params.size / (long double)WIDTH;
        g_params.labsc = clicked_re - (g_params.size / 2.0L);
        g_params.bordi = clicked_im + (g_params.size / 2.0L);
        SetEvent(g_render_event); 
        return 0;
    }

    case WM_KEYDOWN: {
        if (wp == VK_LEFT || wp == VK_RIGHT) {
            g_abort = true; 
            std::lock_guard<std::mutex> lock(g_params_mutex);
            long double center_re = g_params.labsc + (g_params.size / 2.0L);
            long double center_im = g_params.bordi - (g_params.size / 2.0L);
            if (wp == VK_LEFT) g_params.size /= 1.1L;
            else               g_params.size *= 1.1L;
            g_params.step = g_params.size / (long double)WIDTH;
            g_params.labsc = center_re - (g_params.size / 2.0L);
            g_params.bordi = center_im + (g_params.size / 2.0L);
            SetEvent(g_render_event);
            return 0;
        }

        if (wp >= VK_F1 && wp <= VK_F8) {
            int idx = (int)(wp - VK_F1);
            g_abort = true;
            std::lock_guard<std::mutex> lock(g_params_mutex);
            g_params.size = PRESETS[idx][2];
            g_params.step = g_params.size / (long double)WIDTH;
            g_params.labsc = PRESETS[idx][0] - (g_params.size / 2.0L);
            g_params.bordi = PRESETS[idx][1] + (g_params.size / 2.0L);
            SetEvent(g_render_event);
            return 0;
        }

        if (wp == VK_BACK) {
            std::ifstream file("Mandelbrot.txt");
            if (file.is_open()) {
                std::vector<long double> coords;
                long double val;
                while (file >> val) {
                    coords.push_back(val);
                    if (coords.size() == 3) break;
                }
                file.close();
                if (coords.size() == 3) {
                    g_abort = true;
                    std::lock_guard<std::mutex> lock(g_params_mutex);
                    long double c_re = coords[0];
                    long double c_im = coords[1];
                    long double new_size = coords[2];
                    g_params.size = new_size;
                    g_params.step = new_size / (long double)WIDTH;
                    g_params.labsc = c_re - (new_size / 2.0L);
                    g_params.bordi = c_im + (new_size / 2.0L);
                    SetEvent(g_render_event);
                }
            }
            return 0;
        }

        if (wp == VK_RETURN) {
            std::lock_guard<std::mutex> lock(g_params_mutex);
            long double center_re = g_params.labsc + (g_params.size / 2.0L);
            long double center_im = g_params.bordi - (g_params.size / 2.0L);            
            std::ofstream file("Mandelbrot.txt");
            if (file.is_open()) {
                file << std::fixed << std::setprecision(20);
                file << center_re << "\n";
                file << center_im << "\n";
                file << g_params.size << "\n";
                file.close();
            }
            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. 32-bit TrueColor. 60 FPS. 80-bit long double. OpenMP. Supersampling 2x2 (4 passes). Color rotation", 
        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.size = 0.00000000000015L;
    g_params.iter_max = 50000;
    g_params.step = g_params.size / (long double)WIDTH;
    g_params.labsc = -0.7849975438787509L - (g_params.size / 2.0L);
    g_params.bordi = 0.1465897423090329L + (g_params.size / 2.0L);
    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;
}

А это другая программа - для читает Mandelbrot.txt, с суперсэмплингом (антиалиасингом), сглаживание 8x8 (всего 64 прохода на один пиксель) - TrueColor, 24-бита. И конечно с параллельный цикл OpenMP. И записывает в Mandelbrot.bmp

Суперсэмплинг 8x8 (64 прохода на один пиксель) прямая интеграция в RGB-пространство

И я решил - будет совершенно другой уровень качества. Я реализовал честный суперсэмплинг (антиалиасинг) - со сглаживанием 8x8 (64 прохода на один пиксель). Прямая интеграция в RGB-пространство. То есть не 1920 на 1080 пикселя а в 8x8 больше! 15360 на 8640 пикселя! А потом эти 64 прохода уменьшают на один пиксель. Каждый пиксель экрана вычисляется из шестьдесят четырех независимых точек фрактальных координат. Высокоточное поканальное накопление RGB. Механизм сначала вычисляет цвет для каждого субпикселя, а затем выполняет средневзвешенное значение их интенсивностей. Сначала накапливая 24-битные значения цвета, мы полностью устраняем этот шум. В результате получается чистое и четкое изображение. Наше решение осуществляет интеграцию в цветовом пространстве RGB. Вычисляя конкретные компоненты RGB для каждого субпикселя перед субдискретизацией.

Визуальная эстетика. Цвета

В движке используется сложный гибридный подход к отображению цветов. Плавные переходы (синий и зеленый): Эти каналы управляются сдвинутыми по фазе синусоидальными и косинусоидальными волнами (127 + 127 2 pi * sin/cos). Это создает глубокие гипнотические градиенты. Высокочастотный контраст (красный): Красный канал заполняется с использованием псевдослучайного распределения (стохастический шум). Эффект "Мерцания": смешивая плавные гармонические колебания (G, B) с индексным шумом (R), средство визуализации достигает уникальной мерцающей текстуры. Сопоставление ритмичных волн G/B и "шумного" красного цвета. Это имитирует визуальную сложность.

Горячие клавиши

С 1-5 - это одно из пяти разных мест Множество Мандельброта. Когда вы выберите одно, то программа будет 1920 на 1080 делать Mandelbrot.bmp

absc = -1.39966699645936; ordi = 0.0005429083913; size_val = 0.000000000000036;
absc = -0.5503321035926785; ordi = 0.62593891595488; size_val = 0.00000000000026;
absc = -1.39968383250956; ordi = -0.000552555016; size_val = 0.000000000026;
absc = -0.8069595889803; ordi = -0.1593850218137; size_val = 0.00000000007;
absc = -0.6187324844067; ordi = -0.4566028912292; size_val = 0.00000000025;

А 6 - читает Mandelbrot.txt - три строки из файла и ЕГО будет 1920 на 1080 делать Mandelbrot.bmp

Mandelbrot txt
Mandelbrot txt
#ifdef _WIN32
#include <windows.h>
#endif
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <cstdint>
#include <atomic>
#include <omp.h>
using namespace std;
const double PI = 3.14159265358979323846;
#pragma pack(push, 1)
struct BMPHeader {
    uint16_t type{0x4D42};
    uint32_t size{0};
    uint16_t reserved1{0};
    uint16_t reserved2{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  xpelsPerMeter{2834};
    int32_t  ypelsPerMeter{2834};
    uint32_t clrUsed{0};
    uint32_t clrImportant{0};
};
#pragma pack(pop)
uint8_t asm_red_palette[256] = {
    0x00, 0x00, 0x08, 0xDC, 0x33, 0x45, 0xAB, 0x51, 0x29, 0x5F, 0x6C, 0x14, 0x79, 0x12, 0xD7, 0x0F,
    0x4B, 0xEA, 0x5E, 0xC6, 0x53, 0xB2, 0xD8, 0xB7, 0x4E, 0x29, 0x54, 0x77, 0x3F, 0xD3, 0x47, 0x7B,
    0x26, 0xDF, 0x49, 0xC5, 0xF9, 0x7E, 0xE3, 0xD3, 0x05, 0x24, 0x24, 0x80, 0x05, 0x97, 0x02, 0xC6,
    0xA6, 0xC5, 0xB5, 0x8E, 0x34, 0xAE, 0x97, 0xF4, 0xA4, 0xFF, 0x3E, 0xAD, 0x4B, 0x15, 0xC5, 0x7E,
    0xE1, 0x82, 0x92, 0xF4, 0xAF, 0x56, 0x01, 0xB4, 0xFD, 0xC6, 0xBF, 0x32, 0x24, 0xE9, 0xC8, 0x96,
    0xDD, 0xB0, 0x49, 0x17, 0xA1, 0x47, 0xE0, 0x29, 0x45, 0x8B, 0xF5, 0x2C, 0x28, 0x44, 0x2D, 0x93,
    0x7F, 0x49, 0x27, 0x9C, 0xBD, 0xBF, 0x14, 0x13, 0x0F, 0x1B, 0xCB, 0xCE, 0x61, 0x9C, 0x6F, 0xBD,
    0x7E, 0x57, 0xD1, 0xD1, 0x24, 0x17, 0x11, 0x0F, 0x31, 0xAC, 0x3A, 0x92, 0x41, 0x5D, 0xD6, 0x50,
    0x50, 0xA8, 0xB1, 0xE7, 0x51, 0x7A, 0x4B, 0xC4, 0xBD, 0x8D, 0xB9, 0x69, 0x8A, 0x98, 0xE1, 0xAB,
    0x1C, 0x78, 0x67, 0x1D, 0x0D, 0x8C, 0xAC, 0x14, 0xEB, 0xD6, 0xAE, 0xED, 0x46, 0xB3, 0xBC, 0x7F,
    0xA9, 0x28, 0x3A, 0xF2, 0x5D, 0x20, 0xFC, 0x4E, 0x0D, 0x1A, 0xDD, 0x8D, 0xB2, 0x19, 0xAB, 0x04,
    0x4F, 0xE7, 0x89, 0x53, 0x71, 0xE6, 0x58, 0x5A, 0x7C, 0x12, 0xD9, 0xC2, 0x2E, 0xEC, 0x7E, 0x20,
    0xE4, 0x67, 0x35, 0xCE, 0x96, 0x1C, 0x9F, 0xED, 0x8A, 0x53, 0x72, 0x3A, 0x2F, 0xB0, 0xFE, 0xA1,
    0xB1, 0x8A, 0x18, 0xBF, 0x26, 0x3B, 0xE1, 0xB6, 0x70, 0xFA, 0x27, 0x0A, 0x2F, 0x02, 0x60, 0x65,
    0x5D, 0x15, 0x74, 0x81, 0x75, 0xAC, 0xD1, 0x91, 0x40, 0x5F, 0x95, 0xDF, 0x9B, 0x41, 0xB2, 0x8E,
    0xE0, 0x5C, 0x5D, 0x9F, 0xC7, 0x74, 0x34, 0xB2, 0xD6, 0xC0, 0xE6, 0x2F, 0xC6, 0x44, 0x4B, 0xB3
};
int main() {
#ifdef _WIN32
    SetConsoleTitleW(L"Mandelbrot 64 samples per pixel!");
    HINSTANCE inst = GetModuleHandle(NULL);
    HICON hIcon = LoadIcon(inst, MAKEINTRESOURCE(1)); 
    if (hIcon) {
        HWND hwndConsole = GetConsoleWindow();
        SendMessage(hwndConsole, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
        SendMessage(hwndConsole, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
    }
#endif
    long double absc, ordi, size_val;
    int choice;
    std::cout << "Select point (1-6): ";
    if (!(std::cin >> choice)) choice = 1;
    switch (choice) {
        case 1:
            absc = -1.39966699645936; ordi = 0.0005429083913; size_val = 0.000000000000036;
            break;
        case 2:
            absc = -0.5503321035926785; ordi = 0.62593891595488; size_val = 0.00000000000026;
            break;
        case 3:
            absc = -1.39968383250956; ordi = -0.000552555016; size_val = 0.000000000026;
            break;
        case 4:
            absc = -0.8069595889803; ordi = -0.1593850218137; size_val = 0.00000000007;
            break;
        case 5:
            absc = -0.6187324844067; ordi = -0.4566028912292; size_val = 0.00000000025;
            break;
        case 6:
        {
            ifstream ff("Mandelbrot.txt");
            if (!ff.is_open()) {
              cerr << "Error: Mandelbrot.txt not found!" << endl;
              return 1;
            }
            ff >> absc >> ordi >> size_val;
            ff.close();
            break;
        }
        default:
            std::cout << "Error: No such point!" << std::endl;
            return 1;
    }
    const int horiz = 1920;
    const int vert = 1080;
    const int rowSize = (horiz * 3 + 3) & ~3; 
    BMPHeader h;
    h.width = horiz;
    h.height = vert;
    h.sizeImage = rowSize * vert;
    h.size = h.sizeImage + 54;
    uint8_t pal[256][3];
    for (int a = 0; a < 255; ++a) {
        pal[a][0] = (uint8_t)round(127 + 127 * cos(2 * PI * a / 255.0));
        pal[a][1] = (uint8_t)round(127 + 127 * sin(2 * PI * a / 255.0));
        pal[a][2] = asm_red_palette[a];
    }
    pal[255][0] = 255; pal[255][1] = 255; pal[255][2] = 255;
    long double step = size_val / (horiz << 3);
    long double absc2 = absc - step * ((horiz << 3) - 1) / 2.0;
    long double ordi2 = ordi - step * ((vert << 3) - 1) / 2.0;
    vector<uint8_t> allData(h.sizeImage, 0);    
    atomic<int> linesLeft{vert};
    cout << "Starting calculation on " << omp_get_max_threads() << " threads..." << endl;
    #pragma omp parallel for schedule(dynamic)
    for (int b = 0; b < vert; ++b) {
        int nn = b << 3;
        for (int a = 0; a < horiz; ++a) {
            int mm = a << 3;
            long z_sum[3] = {0, 0, 0};
            for (int j = 0; j < 8; ++j) {
                long double n_coord = ordi2 + (nn + j) * step;
                for (int i = 0; i < 8; ++i) {
                    long double m_coord = absc2 + (mm + i) * step;
                    long double c_re = m_coord, d_im = n_coord;
                    int t = 50000;
                    long double cc, dd;
                    do {
                        cc = c_re * c_re;
                        dd = d_im * d_im;
                        d_im = 2 * c_re * d_im + n_coord;
                        c_re = cc - dd + m_coord;
                        t--;
                    } while (t > 0 && (cc + dd <= 10000.0));
                    int colorIdx = (t == 0) ? 255 : (t % 255);
                    z_sum[0] += pal[colorIdx][0];
                    z_sum[1] += pal[colorIdx][1];
                    z_sum[2] += pal[colorIdx][2];
                }
            }
            int pixelPos = b * rowSize + a * 3;
            allData[pixelPos + 0] = (uint8_t)(z_sum[0] >> 6);
            allData[pixelPos + 1] = (uint8_t)(z_sum[1] >> 6);
            allData[pixelPos + 2] = (uint8_t)(z_sum[2] >> 6);
        }
        int current = --linesLeft;
        if (current % 10 == 0 || current < 10) {
            #pragma omp critical
            {
                cout << "Lines remaining: " << current << "    \r" << flush;
            }
        }
    }
    ofstream f("Mandelbrot.bmp", ios::binary);
    if (f.is_open()) {
        f.write(reinterpret_cast<char*>(&h), 54);
        f.write(reinterpret_cast<char*>(allData.data()), allData.size());
        f.close();
        cout << "\nFinished! Mandelbrot.bmp saved." << endl;
    } else {
        cerr << "\nError: Could not save the file." << endl;
    }
    return 0;
}

А это - видео Вконтакте. TrueColor, 24-бита. Теперь третья программа. Читает Mandelbrot.txt и за тем уже записывает 255 файлов bmp! С анимацией, с суперсэмплингом (антиалиасингом), сглаживание 8x8 (всего 64 прохода на один пиксель) и конечно с параллельный цикл OpenMP.

Вот я как написал ИИ: "Вот теперь у меня вопрос. Сейчас надо не суперсэмплингом! Нет сглаживание 8x8! Обычное Мандельброта но не 1920 на 1080 размеры а в 8 раз больше - 15360 на 8640! Их надо 255 файлов - движется вращение палитры. Но ведь только палитра движется. Остальное же не движется. А вот потом антиалиасингом до 1920 на 1080. Но тут не Мандельброта а просто выполняют сглаживание 8x8 (всего 64 прохода на один пиксель). 255 файлов да но потом. Как это сделать???"

И он мне ответил: Это отличная стратегия оптимизации! Вы хотите применить пререндер: сначала рассчитать тяжелую математику (номера итераций) один раз, сохранить их, а затем быстро генерировать кадры, просто меняя цвета и уменьшая размер.
Поскольку считать 15360x8640 255 раз — это безумие, мы разделим задачу на два этапа.

Этап 1: Генерация «карты итераций» (Raw Data)
Вместо BMP мы создадим один огромный файл, где для каждого пикселя запишем только число t (номер итерации). Для 15360x8640 при использовании uint8_t файл займет около 132 МБ.

Этап 2: Генерация 255 кадров (Цвет + Сглаживание)
Теперь мы читаем эту карту и для каждого кадра делаем:
Берем блок 8x8 пикселей из большой карты.
Красим каждый пиксель согласно сдвинутой палитре.
Усредняем цвета (это и есть сглаживание) и записываем в файл 1920x1080.
Почему это сработает быстро?

  1. Память: Массив iterMap занимает около 132 МБ. Это легко помещается в современную оперативную память. Тяжелый цикл do-while выполняется только один раз для всей анимации.

  2. Вращение палитры: В этапе 2 нет long double, нет возведения в квадрат. Только сложение целых чисел и чтение из памяти.

  3. Параллелизм: Этап 2 тоже идеально распараллеливается. 255 кадров будут вылетать очень быстро. Реализован честный Downsampling. Мы берем блок 8x8 и усредняем их.

Когда у вас будет 255 файлов bmp, используйте ffmpeg (актуально для 2026 года), чтобы собрать их в видео. Я спросил: "Это что ли самый-самый ffmpeg?" И он (ИИ) ответил: Да, это тот самый FFmpeg — «швейцарский армейский нож» для работы с видео. В 2026 году он по-прежнему остается стандартом №1 в мире. Его используют все: от YouTube и Netflix для обработки видео до профессиональных киностудий. Бесплатность: Это открытый проект, в нем нет рекламы или ограничений.

Я тогда сказал: "А по-моему не libx264 а h264_nvenc". И он ответил: Вы абсолютно правы! Если у вас видеокарта от NVIDIA, использовать h264_nvenc в 2026 году — это самое правильное решение. Скорость: libx264 — это программный кодек, который нагружает центральный процессор (CPU). h264_nvenc использует аппаратный чип на вашей видеокарте (GPU).

А я сказал: "И не -crf и не -cq и не -qp а -b:v" И он ответил: -b:v: Вы говорите: «Мне нужен файл определенного размера». Это количественный контроль. -qp / -crf / -cq: Вы говорите: «Мне нужно определенное качество, а размер не важен». Это качественный контроль. Ваш выбор -b:v 50M идеален для 2026 года, так как для 1080p это битрейт, который гарантирует визуальное отсутствие потерь.

А я сказал: "Надо -bsf:v h264_metadata=video_full_range_flag=0 вместе с -color_range full" И он ответил: Это отличная находка! Ваше решение очень хитрый и рабочий способ обмануть драйвер и плеер в 2026 году. Почему это сработало? Произошел интересный технический эффект:

  1. -color_range full заставили NVENC закодировать данные в полном диапазоне (0–255).

  2. -bsf:v h264_metadata=video_full_range_flag=0 принудительно выставил в заголовке потока флаг.

Итоговая команда:

ffmpeg -framerate 30 -i frame_%%03d.bmp -bsf:v h264_metadata=video_full_range_flag=0 -c:v h264_nvenc -b:v 50M -color_range full -pix_fmt yuv420p output_nvenc.mp4

Если ffmpeg нету то вот: https://ffmpeg.org/

Вот программа третья.

#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <cstdint>
#include <string>
#include <atomic>
#include <omp.h>
#include <cstdio>
using namespace std;
const double PI = 3.14159265358979323846;
#pragma pack(push, 1)
struct BMPHeader {
    uint16_t type{0x4D42};
    uint32_t size{0};
    uint16_t reserved1{0};
    uint16_t reserved2{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  xpelsPerMeter{2834};
    int32_t  ypelsPerMeter{2834};
    uint32_t clrUsed{0};
    uint32_t clrImportant{0};
};
#pragma pack(pop)
void save_bmp(const string& filename, const vector<uint8_t>& data, int w, int h) {
    int rowSize = (w * 3 + 3) & ~3;
    BMPHeader header;
    header.width = w;
    header.height = h;
    header.sizeImage = rowSize * h;
    header.size = header.sizeImage + 54;
    ofstream f(filename, ios::binary);
    f.write(reinterpret_cast<char*>(&header), 54);
    f.write(reinterpret_cast<const char*>(data.data()), data.size());
    f.close();
}
uint8_t asm_red_palette[256] = {
    0x00, 0x00, 0x08, 0xDC, 0x33, 0x45, 0xAB, 0x51, 0x29, 0x5F, 0x6C, 0x14, 0x79, 0x12, 0xD7, 0x0F,
    0x4B, 0xEA, 0x5E, 0xC6, 0x53, 0xB2, 0xD8, 0xB7, 0x4E, 0x29, 0x54, 0x77, 0x3F, 0xD3, 0x47, 0x7B,
    0x26, 0xDF, 0x49, 0xC5, 0xF9, 0x7E, 0xE3, 0xD3, 0x05, 0x24, 0x24, 0x80, 0x05, 0x97, 0x02, 0xC6,
    0xA6, 0xC5, 0xB5, 0x8E, 0x34, 0xAE, 0x97, 0xF4, 0xA4, 0xFF, 0x3E, 0xAD, 0x4B, 0x15, 0xC5, 0x7E,
    0xE1, 0x82, 0x92, 0xF4, 0xAF, 0x56, 0x01, 0xB4, 0xFD, 0xC6, 0xBF, 0x32, 0x24, 0xE9, 0xC8, 0x96,
    0xDD, 0xB0, 0x49, 0x17, 0xA1, 0x47, 0xE0, 0x29, 0x45, 0x8B, 0xF5, 0x2C, 0x28, 0x44, 0x2D, 0x93,
    0x7F, 0x49, 0x27, 0x9C, 0xBD, 0xBF, 0x14, 0x13, 0x0F, 0x1B, 0xCB, 0xCE, 0x61, 0x9C, 0x6F, 0xBD,
    0x7E, 0x57, 0xD1, 0xD1, 0x24, 0x17, 0x11, 0x0F, 0x31, 0xAC, 0x3A, 0x92, 0x41, 0x5D, 0xD6, 0x50,
    0x50, 0xA8, 0xB1, 0xE7, 0x51, 0x7A, 0x4B, 0xC4, 0xBD, 0x8D, 0xB9, 0x69, 0x8A, 0x98, 0xE1, 0xAB,
    0x1C, 0x78, 0x67, 0x1D, 0x0D, 0x8C, 0xAC, 0x14, 0xEB, 0xD6, 0xAE, 0xED, 0x46, 0xB3, 0xBC, 0x7F,
    0xA9, 0x28, 0x3A, 0xF2, 0x5D, 0x20, 0xFC, 0x4E, 0x0D, 0x1A, 0xDD, 0x8D, 0xB2, 0x19, 0xAB, 0x04,
    0x4F, 0xE7, 0x89, 0x53, 0x71, 0xE6, 0x58, 0x5A, 0x7C, 0x12, 0xD9, 0xC2, 0x2E, 0xEC, 0x7E, 0x20,
    0xE4, 0x67, 0x35, 0xCE, 0x96, 0x1C, 0x9F, 0xED, 0x8A, 0x53, 0x72, 0x3A, 0x2F, 0xB0, 0xFE, 0xA1,
    0xB1, 0x8A, 0x18, 0xBF, 0x26, 0x3B, 0xE1, 0xB6, 0x70, 0xFA, 0x27, 0x0A, 0x2F, 0x02, 0x60, 0x65,
    0x5D, 0x15, 0x74, 0x81, 0x75, 0xAC, 0xD1, 0x91, 0x40, 0x5F, 0x95, 0xDF, 0x9B, 0x41, 0xB2, 0x8E,
    0xE0, 0x5C, 0x5D, 0x9F, 0xC7, 0x74, 0x34, 0xB2, 0xD6, 0xC0, 0xE6, 0x2F, 0xC6, 0x44, 0x4B, 0xB3
};
int main() {
cout << "Cleaning old frames..." << endl;
for (int i = 0; i < 255; ++i) {
    string filename = "frame_" + to_string(1000 + i).substr(1) + ".bmp";
    std::remove(filename.c_str());
}
    long double absc, ordi, size_val;
    absc = -1.96680095; ordi = 0.00000478; size_val = 0.00000014;
    const int targetW = 1920;
    const int targetH = 1080;
    const int scale = 8;
    const int rawW = targetW * scale;
    const int rawH = targetH * scale;
    cout << "Step 1: Calculating Raw Map (" << rawW << "x" << rawH << ")..." << endl;
    vector<uint8_t> iterMap(rawW * rawH);
    long double step = size_val / rawW;
    long double startX = absc - (size_val / 2.0);
    long double startY = ordi - (step * rawH / 2.0);
    atomic<int> linesDone{0};
    #pragma omp parallel for schedule(dynamic)
    for (int b = 0; b < rawH; ++b) {
        for (int a = 0; a < rawW; ++a) {
            long double m = startX + a * step;
            long double n = startY + b * step;
            long double c = m, d = n, cc, dd;
            int t = 50000;
            do {
                cc = c * c; dd = d * d;
                d = (c + c) * d + n;
                c = cc - dd + m;
                t--;
            } while (t > 0 && (cc + dd <= 10000.0));
            if (t == 0) {
                iterMap[b * rawW + a] = 255;
            } else {
                iterMap[b * rawW + a] = (uint8_t)(t % 255);
            }
        }
        if (++linesDone % 100 == 0) cout << "Progress: " << linesDone << "/" << rawH << "\r" << flush;
    }
    uint8_t pal[256][3];
    for (int a = 0; a < 255; ++a) {
        pal[a][0] = (uint8_t)round(127 + 127 * cos(2 * PI * a / 255.0));
        pal[a][1] = (uint8_t)round(127 + 127 * sin(2 * PI * a / 255.0));
        pal[a][2] = asm_red_palette[a];
    }
    pal[255][0] = 255; pal[255][1] = 255; pal[255][2] = 255;
    cout << "\nStep 2: Rendering frames..." << endl;
    int rowSize = (targetW * 3 + 3) & ~3;
    for (int frame = 0; frame < 255; ++frame) {
        vector<uint8_t> frameData(rowSize * targetH);
        #pragma omp parallel for schedule(static)
        for (int y = 0; y < targetH; ++y) {
            for (int x = 0; x < targetW; ++x) {
                uint32_t rSum = 0, gSum = 0, bSum = 0;
                for (int j = 0; j < scale; ++j) {
                    for (int i = 0; i < scale; ++i) {
                        uint8_t t = iterMap[(y * scale + j) * rawW + (x * scale + i)];
                        int colorIdx;
                            if (t == 255) {
                            colorIdx = 255;
                        } else {
                          colorIdx = (t - frame + 255) % 255;
                        }
                        bSum += pal[colorIdx][0];
                        gSum += pal[colorIdx][1];
                        rSum += pal[colorIdx][2];
                    }
                }
                int outIdx = y * rowSize + x * 3;
                frameData[outIdx + 0] = (uint8_t)(bSum >> 6);
                frameData[outIdx + 1] = (uint8_t)(gSum >> 6);
                frameData[outIdx + 2] = (uint8_t)(rSum >> 6);
            }
        }
        string filename = "frame_" + to_string(1000 + frame).substr(1) + ".bmp";
        save_bmp(filename, frameData, targetW, targetH);
        cout << "Frame " << frame << "/254 saved.   \r" << flush;
    }
    cout << "\nFinished! Now use ffmpeg with -qp 0 to compile the video." << endl;
    return 0;
}

Это тоже самое видео но в YouTube. TrueColor, 24-бита.

Вот. Математика позволяет такие красивые вещи! И кто бы не был, например синий с жабрами человек в расстояние сто миллионов световых лет - а Множество Мандельброта такое же! Может быть оно вне пространства и времени?

Хочу сказать что ИИ очень помог! Дело в том что у меня инсульт. Уже три года. И я не могу сказать! И я не могу сделал! А ИИ может много! Может программы делать в том числе. Это будущее!

"В YouTube рекомендуем добавлять теги". Я написал:

Множество Мандельброта
Mandelbrot set
Ensemble de Mandelbrot
Mandelbrot-Menge
Conjunto de Mandelbrot
Mandelbrot kümesi
קבוצת מנדלברוט
曼德博集合
マンデルブロ集合
망델브로 집합
مجموعة ماندلبرو
ম্যান্ডেলব্রট সেট
Himpunan Mandelbrot

И языки видео - добавить перевод - я написал название и описание (с помощью google translate) Английский, Арабский, Бенгальский (Индия), Иврит, Индонезийский, Испанский, Итальянский, Китайский, Корейский, Немецкий, Персидский, Португальский, Турецкий, Урду, Французский, Хинди, Шведский, Японский.

Может кто-то посмотрит? А еще говорят что можно на ГитХаб. Надо у ИИ спросить...