- BrainTools - https://www.braintools.ru -
Множество Мандельброта. 60 fps. Вращение палитры – анимация. 256 цветов. Делал я. Посмотрите 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. Кадры захватываются напрямую из памяти [1] 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 — качество неотличимо от оригинала, но нагрузка на диск и размер файла значительно ниже.
Ну там 1920×1080. Я потом 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
Все. Теперь программа!
Генерация палитры сделана вот. 0 – это Green, 1 – это Blue и 2 – это Red. Здесь я в Delphi что бы проще:
pal[a][0]:=round(127+127*cos(2*pi*a/255));
pal[a][1]:=round(127+127*sin(2*pi*a/255));
pal[a][2]:=Random(256)
Это специально для 8-битного индексированного режима (256 цветов). В нем используется SetDIBColorTable, которая работает только с палитрами. Функция SetDIBColorTable физически не может принять больше 256 записей. Палитра (256 цветов): Работает мгновенно, так как вы меняете всего 1 КБ данных (256 цветов). Когда вы «вращаете» палитру вы меняете всего 1024 байта (256 записей по 4 байта), поэтому это работает невероятно быстро даже на старом железе.
А DwmFlush – синхронизация с монитором, как обычно 60 fps. DwmFlush() приостанавливает выполнение вашего кода до тех пор, пока диспетчер окон (DWM) не обновит экран. Если ваш монитор работает на 144 Гц, функция будет срабатывать 144 раза в секунду, обеспечивая 144 FPS. Функция ориентируется на текущую частоту обновления монитора, установленную в настройках Windows. Если в системе стоит 60 Гц, вы получите 60 FPS. Если монитор поддерживает 240 Гц и это выбрано в настройках — вы получите 240 FPS. Она даст 60 FPS только если ваш монитор настроен на 60 Гц; на игровых мониторах FPS будет выше, согласно их герцовке.
Вот что на экране видео точно также и на программе! И есть восемь предустановленных мест Множества Мандельброта. Это C++:
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.550327628L, -0.625931405602L, 0.00000000781L},
{-0.55033233469975L, 0.62593882612931L, 0.0000000000023L},
{-1.3996669964593604L, 0.0005429083913L, 0.000000000000026L},
{-0.5503493176297569L, 0.6259309572825709L, 0.00000000000031L}
};
WM_LBUTTONDOWN (Левая кнопка) – увеличиваем масштаб в 2 раза и центрируем новую область вокруг точки клика а WM_RBUTTONDOWN (Правая кнопка) – уменьшаем. В VK_F1 – VK_F8 – восемь мест Множество Мандельброта на экран. VK_UP (Стрелка ВВЕРХ) и VK_DOWN (Стрелка ВНИЗ) – увеличиваем и уменьшаем в 2 раза но без точки клика. А в VK_LEFT (Стрелка ВЛЕВО) и VK_RIGHT (Стрелка ВПРАВО) – в 1.1 раз!
Очень важно VK_RETURN (Enter, Ввод) – у вас сейчас на экран какое-то Множество Мандельброта. И сейчас оно запишется в файл!!! Mandelbrot.txt вот таком виде:
А VK_BACK (это та самая клавиша НАД Enter, Backspace) – читает Mandelbrot.txt (читаем три строки из файла) и запускает на экран.
Теперь надо параллелизации циклов в C++. Чаще всего используется стандарт OpenMP. Это самый простой и эффективный способ, так как он поддерживается компилятором g++ «из коробки». Добавьте всего одну строку перед внешним циклом. Мы распараллеливаем именно внешний цикл, чтобы каждое ядро процессора считало свою порцию строк. Директива OpenMP для распараллеливания цикла schedule (dynamic) лучше всего подходит для Мандельброта, так как разные области считаются разное время. Черные области (внутри множества) считаются очень долго, а пустые области — быстро. Динамическое распределение не даст ядрам простаивать. Атомарного счетчика гарантирует, что если 16 ядер одновременно попытаются уменьшить переменную, она уменьшится корректно.
#pragma omp parallel for: Разделяет 1080 строк изображения между всеми доступными ядрами процессора.
schedule(dynamic): Позволяет ядрам брать новую строку сразу после того, как они закончили старую (важно, так как центр фрактала считается дольше краев).
atomic<int> linesLeft: Позволяет безопасно отслеживать прогресс из разных потоков.
#pragma omp critical: Предотвращает “кашу” в консоли, когда несколько потоков пытаются вывести текст одновременно.
OpenMP – это стандарт, который говорит компилятору: «Возьми этот цикл и сам раздай итерации разным ядрам процессора». Параллельное программирование — это широкая область, а OpenMP — это лишь один из самых удобных инструментов в ней.
Балансировка нагрузки (schedule(dynamic)): Она незаменима. В Мандельброте одни части считаются быстро, другие — долго. OpenMP сам «подсовывает» работу освободившимся ядрам.
Масштабируемость: Ваш код будет одинаково эффективно работать и на 4-ядерном ноутбуке, и на 128-ядерном сервере без изменения программы.
Итог: Да, используя OpenMP, вы занимаетесь параллельным программированием на уровне многопоточности (Multithreading). Это самый эффективный способ заставить процессор работать на 100%.
Но где же сами изображения? Я хочу увидеть Множество Мандельброта!
И вот тут начинается самое интересное. Если вы посмотрите на большинство программ, вы увидите проблему: всего 256 цветов и явные «ступеньки» между цветовыми переходами (так называемый цветовой бандинг).
Я не смог на это смотреть без боли [2]. А что же делают другие разработчики? Я изучил популярные проекты:
Четыре из пяти проектов вообще статичны! Только пятый, Xaos, предлагает анимацию. Но все они ограничены палитрой в 256 цветов.
Мое решение — это совершенно другой уровень качества. Я реализовал честный суперсэмплинг (антиалиасинг) со сглаживанием 8×8 (64 прохода на пиксель). Это позволяет получить плавные градиенты, которые 24-битного цвета TrueColor! И тоже параллельный цикл OpenMP.
Но самое главное – КАРТИНКИ ))) Смотрите:
Нравится?
Ну тогда КОД!!! Полный код программы 60 fps и вращение палитры 256 цветов и распараллеливаем. И записывается в файл Mandelbrot.txt
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <dwmapi.h>
#include <vector>
#include <cmath>
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>
#include <omp.h> // Аналог Rayon для C++
#include <iomanip>
// Константы
const int WIDTH = 1000;
const int HEIGHT = 1000;
struct FractalParams {
long double step;
long double labsc;
long double bordi;
long double size;
uint32_t iter_max;
};
// Глобальные переменные для управления (аналог Arc/Mutex)
std::mutex g_params_mutex;
FractalParams g_params;
std::atomic<bool> g_abort{false};
std::atomic<int> g_direction{0};
HANDLE g_render_event;
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.550327628L, -0.625931405602L, 0.00000000781L},
{-0.55033233469975L, 0.62593882612931L, 0.0000000000023L},
{-1.3996669964593604L, 0.0005429083913L, 0.000000000000026L},
{-0.5503493176297569L, 0.6259309572825709L, 0.00000000000031L}
};
// Генерация палитры
void generate_palette(RGBQUAD* pal) {
const double pi = 3.141592653589793;
for (int a = 0; a < 255; ++a) {
double angle = (2.0 * pi * a) / 255.0;
pal[a].rgbGreen = (uint8_t)std::round(127.0 + 127.0 * std::cos(angle));
pal[a].rgbBlue = (uint8_t)std::round(127.0 + 127.0 * std::sin(angle));
pal[a].rgbRed = (uint8_t)(rand() % 256);
pal[a].rgbReserved = 0;
}
pal[255] = {255, 255, 255, 0};
}
// Поток вращения палитры
void thread_palette_rotator(HDC hdc_win, HDC hdc_m) {
RGBQUAD palette[256];
generate_palette(palette);
while (true) {
// Вращение (аналог rotate_left/right)
RGBQUAD temp = palette[0];
for (int i = 0; i < 254; ++i) palette[i] = palette[i+1];
palette[254] = temp;
SetDIBColorTable(hdc_m, 0, 256, palette);
DwmFlush(); // Синхронизация с монитором
BitBlt(hdc_win, 0, 0, WIDTH, HEIGHT, hdc_m, 0, 0, SRCCOPY);
}
}
// Поток расчета (аналог rayon)
void thread_mandelbrot_calc(uint8_t* pixels) {
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;
}
// Параллельный цикл (6 ядер Phenom II будут загружены на 100%)
#pragma omp parallel for schedule(dynamic)
for (int y = 0; y < HEIGHT; ++y) {
if (g_abort) continue;
long double imc = p.bordi - (y * p.step);
for (int x = 0; x < WIDTH; ++x) {
long double rec = p.labsc + (x * p.step);
long double re = 0, im = 0;
uint8_t color = 255;
for (uint32_t i = 0; i < p.iter_max; ++i) {
long double re2 = re * re;
long double im2 = im * im;
if (re2 + im2 > 100.0L) {
color = (uint8_t)(i % 255);
break;
}
im = 2.0L * re * im + imc;
re = re2 - im2 + rec;
}
pixels[y * WIDTH + x] = color;
}
}
}
}
// Оконная процедура
LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_LBUTTONDOWN: {
g_abort = true; // Останавливаем текущий расчет
std::lock_guard<std::mutex> lock(g_params_mutex);
// 1. Координаты клика в пикселях
long double x = (short)LOWORD(lparam);
long double y = (short)HIWORD(lparam);
// 2. Находим реальные координаты точки, в которую кликнули
long double clicked_re = g_params.labsc + (x * g_params.step);
long double clicked_im = g_params.bordi - (y * g_params.step);
// 3. Увеличиваем масштаб в 2 раза (уменьшаем размер области)
g_params.size /= 2.0L;
// 4. Пересчитываем шаг для нового масштаба
g_params.step = g_params.size / (long double)WIDTH;
// 5. Центрируем новую область вокруг точки клика
g_params.labsc = clicked_re - (g_params.size / 2.0L);
g_params.bordi = clicked_im + (g_params.size / 2.0L);
// 6. Запускаем перерисовку
SetEvent(g_render_event);
return 0;
}
case WM_RBUTTONDOWN: {
g_abort = true; // Останавливаем текущий расчет
std::lock_guard<std::mutex> lock(g_params_mutex);
long double x = (short)LOWORD(lparam);
long double y = (short)HIWORD(lparam);
long double clicked_re = g_params.labsc + (x * g_params.step);
long double clicked_im = g_params.bordi - (y * g_params.step);
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 (wparam >= VK_F1 && wparam <= VK_F8) {
int idx = wparam - 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 / 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);
}
if (wparam == VK_UP) {
std::lock_guard<std::mutex> lock(g_params_mutex);
long double cr = g_params.labsc + (g_params.size / 2.0L);
long double ci = g_params.bordi - (g_params.size / 2.0L);
g_params.size /= 2.0L;
g_params.step = g_params.size / WIDTH;
g_params.labsc = cr - (g_params.size / 2.0L);
g_params.bordi = ci + (g_params.size / 2.0L);
SetEvent(g_render_event);
}
if (wparam == VK_DOWN) {
std::lock_guard<std::mutex> lock(g_params_mutex);
long double cr = g_params.labsc + (g_params.size / 2.0L);
long double ci = g_params.bordi - (g_params.size / 2.0L);
g_params.size *= 2.0L;
g_params.step = g_params.size / WIDTH;
g_params.labsc = cr - (g_params.size / 2.0L);
g_params.bordi = ci + (g_params.size / 2.0L);
SetEvent(g_render_event);
}
if (wparam == VK_LEFT) {
std::lock_guard<std::mutex> lock(g_params_mutex);
long double cr = g_params.labsc + (g_params.size / 2.0L);
long double ci = g_params.bordi - (g_params.size / 2.0L);
g_params.size /= 1.1L;
g_params.step = g_params.size / WIDTH;
g_params.labsc = cr - (g_params.size / 2.0L);
g_params.bordi = ci + (g_params.size / 2.0L);
SetEvent(g_render_event);
}
if (wparam == VK_RIGHT) {
std::lock_guard<std::mutex> lock(g_params_mutex);
long double cr = g_params.labsc + (g_params.size / 2.0L);
long double ci = g_params.bordi - (g_params.size / 2.0L);
g_params.size *= 1.1L;
g_params.step = g_params.size / WIDTH;
g_params.labsc = cr - (g_params.size / 2.0L);
g_params.bordi = ci + (g_params.size / 2.0L);
SetEvent(g_render_event);
}
if (wparam == VK_BACK) { // VK_BACK - это та самая клавиша над Enter
std::ifstream file("Mandelbrot.txt");
if (file.is_open()) {
std::string line;
std::vector<long double> coords;
// Читаем три строки из файла
while (std::getline(file, line)) {
try {
// Преобразуем строку в long double
coords.push_back(std::stold(line));
} catch (...) {
// Игнорируем ошибки парсинга, если строка пустая или не число
}
if (coords.size() == 3) break; // Нам нужно только 3 значения
}
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 size = coords[2];
g_params.size = size;
g_params.step = size / (long double)WIDTH;
// Центрируем изображение по загруженным координатам
g_params.labsc = c_re - (g_params.size / 2.0L);
g_params.bordi = c_im + (g_params.size / 2.0L);
SetEvent(g_render_event); // Запускаем новый рендеринг
}
}
}
if (wparam == 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()) {
// Устанавливаем точность 20 знаков после запятой
file << std::fixed << std::setprecision(20);
file << center_re << "n";
file << center_im << "n";
file << g_params.size << "n";
file.close();
}
}
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int main() {
HINSTANCE inst = GetModuleHandle(NULL);
WNDCLASS wc = {0};
wc.lpfnWndProc = wnd_proc;
wc.hInstance = inst;
wc.lpszClassName = L"MandelClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(0, L"MandelClass", L"Mandelbrot Explorer 80-bit C++",
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 = 8;
uint8_t* bits = nullptr;
HBITMAP h_bmp = CreateDIBSection(hdc_mem, &bmi, DIB_RGB_COLORS, (void**)&bits, NULL, 0);
SelectObject(hdc_mem, h_bmp);
// Начальные параметры
g_params = { 0.00000000000015L / (long double)WIDTH, -0.7849975438787509L - (0.00000000000015L / 2.0L), 0.1465897423090329L + (0.00000000000015L / 2.0L), 0.00000000000015L, 50000 };
g_render_event = CreateEvent(NULL, TRUE, TRUE, NULL);
std::thread(thread_palette_rotator, hdc_win, hdc_mem).detach();
std::thread(thread_mandelbrot_calc, bits).detach();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
А это другая программа – для читает Mandelbrot.txt, с суперсэмплингом (антиалиасингом), сглаживание 8×8 (всего 64 прохода на один пиксель) – TrueColor, 24-бита. И конечно с параллельный цикл OpenMP. И записывает в Mandelbrot.bmp
#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)
int main() {
long double absc, ordi, size_val; // Будет использовать 80-бит
ifstream ff("Mandelbrot.txt");
if (!ff.is_open()) {
cerr << "Error: Mandelbrot.txt not found!" << endl;
return 1;
}
ff >> absc >> ordi >> size_val;
ff.close();
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] = (uint8_t)(rand() % 256);
}
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];
}
}
// Запись в буфер (BMP хранит строки снизу вверх, порядок BGR)
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! С анимацией, с суперсэмплингом (антиалиасингом), сглаживание 8×8 (всего 64 прохода на один пиксель) и конечно с параллельный цикл OpenMP.
Вот я как написал ИИ: “Вот теперь у меня вопрос. Сейчас надо не суперсэмплингом! Нет сглаживание 8×8! Обычное Мандельброта но не 1920 на 1080 размеры а в 8 раз больше – 15360 на 8640! Их надо 255 файлов – движется вращение палитры. Но ведь только палитра движется. Остальное же не движется. А вот потом антиалиасингом до 1920 на 1080. Но тут не Мандельброта а просто выполняют сглаживание 8×8 (всего 64 прохода на один пиксель). 255 файлов да но потом. Как это сделать???”
И он мне ответил: Это отличная стратегия оптимизации! Вы хотите применить пререндер: сначала рассчитать тяжелую математику [8] (номера итераций) один раз, сохранить их, а затем быстро генерировать кадры, просто меняя цвета и уменьшая размер.
Поскольку считать 15360×8640 255 раз — это безумие, мы разделим задачу на два этапа.
Этап 1: Генерация «карты итераций» (Raw Data)
Вместо BMP мы создадим один огромный файл, где для каждого пикселя запишем только число t (номер итерации). Для 15360×8640 при использовании uint8_t файл займет около 132 МБ.
Этап 2: Генерация 255 кадров (Цвет + Сглаживание)
Теперь мы читаем эту карту и для каждого кадра делаем:
Берем блок 8×8 пикселей из большой карты.
Красим каждый пиксель согласно сдвинутой палитре.
Усредняем цвета (это и есть сглаживание) и записываем в файл 1920×1080.
Почему это сработает быстро?
Память: Массив iterMap занимает около 132 МБ. Это легко помещается в современную оперативную память. Тяжелый цикл do-while выполняется только один раз для всей анимации.
Вращение палитры: В этапе 2 нет long double, нет возведения в квадрат. Только сложение целых чисел и чтение из памяти.
Параллелизм: Этап 2 тоже идеально распараллеливается. 255 кадров будут вылетать очень быстро. Реализован честный Downsampling. Мы берем блок 8×8 и усредняем их.
Когда у вас будет 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 году. Почему это сработало? Произошел интересный технический эффект:
-color_range full заставили NVENC закодировать данные в полном диапазоне (0–255).
-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/ [9]
Вот программа третья.
#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();
}
int main() {
cout << "Cleaning old frames..." << endl;
for (int i = 0; i < 255; ++i) {
// Генерируем точное имя файла, как при сохранении
string filename = "frame_" + to_string(1000 + i).substr(1) + ".bmp";
// remove возвращает 0, если файл успешно удален
std::remove(filename.c_str());
}
const int targetW = 1920;
const int targetH = 1080;
const int scale = 8;
const int rawW = targetW * scale; // 15360
const int rawH = targetH * scale; // 8640
long double absc, ordi, size_val;
ifstream ff("Mandelbrot.txt");
if (!ff.is_open()) { cerr << "Mandelbrot.txt not found!"; return 1; }
ff >> absc >> ordi >> size_val;
ff.close();
// 1. РАСЧЕТ КАРТЫ ИТЕРАЦИЙ
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));
// Сохраняем: 255 - внутри множества, 0..254 - внешние итерации
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;
}
// 2. ПОДГОТОВКА ПАЛИТРЫ
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] = (uint8_t)(rand() % 256);
}
// Цвет для индекса 255 - СТРОГО БЕЛЫЙ
pal[255][0] = 255; pal[255][1] = 255; pal[255][2] = 255;
// 3. РЕНДЕРИНГ 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 {
// Вычитаем frame, чтобы палитра крутилась в обратную сторону.
// Добавляем 255, чтобы результат (t - frame) не стал отрицательным числом.
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-бита.
Вот. Математика позволяет такие красивые вещи! И кто бы не был, например синий с жабрами человек в расстояние сто миллионов световых лет – а Множество Мандельброта такое же! Может быть оно вне пространства и времени?
Хочу сказать что ИИ очень помог! Дело в том что у меня инсульт [10]. Уже три года. И я не могу сказать! И я не могу сделал! А ИИ может много! Может программы делать в том числе. Это будущее!
“В YouTube рекомендуем добавлять теги”. Я написал:
Множество Мандельброта
Mandelbrot set
Ensemble de Mandelbrot
Mandelbrot-Menge
Conjunto de Mandelbrot
Mandelbrot kümesi
קבוצת מנדלברוט
曼德博集合
マンデルブロ集合
망델브로 집합
مجموعة ماندلبرو
ম্যান্ডেলব্রট সেট
Himpunan Mandelbrot
И языки видео – добавить перевод – я написал название и описание (с помощью google translate) Английский, Арабский, Бенгальский (Индия), Иврит, Индонезийский, Испанский, Итальянский, Китайский, Корейский, Немецкий, Персидский, Португальский, Турецкий, Урду, Французский, Хинди, Шведский, Японский.
Может кто-то посмотрит? А еще говорят что можно на ГитХаб. Надо у ИИ спросить…
Автор: aokoroko
Источник [11]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/24782
URLs in this post:
[1] памяти: http://www.braintools.ru/article/4140
[2] боли: http://www.braintools.ru/article/9901
[3] https://mathr.co.uk/kf/kf.html: https://mathr.co.uk/kf/kf.html
[4] https://www.juliasets.dk/Mandelbrot.htm: https://www.juliasets.dk/Mandelbrot.htm
[5] https://math.hws.edu/eck/js/mandelbrot/java/MandelbrotSettings/: https://math.hws.edu/eck/js/mandelbrot/java/MandelbrotSettings/
[6] https://www.ultrafractal.com/: https://www.ultrafractal.com/
[7] https://xaos-project.github.io/: https://xaos-project.github.io/
[8] математику: http://www.braintools.ru/article/7620
[9] https://ffmpeg.org/: https://ffmpeg.org/
[10] инсульт: http://www.braintools.ru/brain-disease/insult
[11] Источник: https://habr.com/ru/articles/987598/?utm_source=habrahabr&utm_medium=rss&utm_campaign=987598
Нажмите здесь для печати.