第2章:フラクタル描画の実装

2.1 プロジェクト構成

2.1.1 ディレクトリ構造

fractol/
├── Makefile
├── includes/
│   └── fractol.h
├── srcs/
│   ├── main.c
│   ├── init.c
│   ├── fractals/
│   │   ├── mandelbrot.c
│   │   ├── julia.c
│   │   └── burning_ship.c
│   ├── render/
│   │   ├── render.c
│   │   ├── color.c
│   │   └── pixel.c
│   ├── hooks/
│   │   ├── key_hooks.c
│   │   └── mouse_hooks.c
│   └── utils/
│       ├── error.c
│       ├── parse.c
│       └── cleanup.c
├── libft/
└── minilibx/

2.1.2 ヘッダーファイル

/* fractol.h */
#ifndef FRACTOL_H
# define FRACTOL_H

# include <mlx.h>
# include <stdlib.h>
# include <unistd.h>
# include <math.h>
# include "libft.h"

/*
** ================== 定数 ==================
*/
# define WIN_WIDTH      800
# define WIN_HEIGHT     800
# define WIN_TITLE      "fract-ol"

/* フラクタルタイプ */
# define MANDELBROT     1
# define JULIA          2
# define BURNING_SHIP   3

/* キーコード(macOS) */
# define KEY_ESC        53
# define KEY_UP         126
# define KEY_DOWN       125
# define KEY_LEFT       123
# define KEY_RIGHT      124
# define KEY_PLUS       24
# define KEY_MINUS      27
# define KEY_R          15
# define KEY_C          8
# define KEY_1          18
# define KEY_2          19
# define KEY_3          20

/* マウス */
# define MOUSE_SCROLL_UP    4
# define MOUSE_SCROLL_DOWN  5

/* デフォルト値 */
# define DEFAULT_ZOOM       200.0
# define DEFAULT_MAX_ITER   100
# define ZOOM_FACTOR        1.2

/*
** ================== 構造体 ==================
*/

typedef struct s_complex
{
    double  real;
    double  imag;
}   t_complex;

typedef struct s_img
{
    void    *ptr;
    char    *addr;
    int     bpp;
    int     line_len;
    int     endian;
}   t_img;

typedef struct s_fractol
{
    void        *mlx;
    void        *win;
    t_img       img;
    int         type;           /* フラクタルタイプ */
    double      zoom;           /* ズームレベル */
    double      offset_x;       /* X方向オフセット */
    double      offset_y;       /* Y方向オフセット */
    int         max_iter;       /* 最大反復回数 */
    t_complex   julia_c;        /* ジュリア集合のパラメータ */
    int         color_scheme;   /* カラースキーム */
}   t_fractol;

/*
** ================== 関数プロトタイプ ==================
*/

/* 初期化 */
int         init_fractol(t_fractol *f);
void        init_defaults(t_fractol *f);

/* フラクタル計算 */
int         mandelbrot(double cr, double ci, int max_iter);
int         julia(double zr, double zi, t_complex c, int max_iter);
int         burning_ship(double cr, double ci, int max_iter);

/* 描画 */
void        render(t_fractol *f);
void        my_mlx_pixel_put(t_img *img, int x, int y, int color);

/* 色 */
int         get_color(int n, int max_iter, int scheme);
int         interpolate_color(int c1, int c2, double ratio);

/* フック */
int         key_hook(int keycode, t_fractol *f);
int         mouse_hook(int button, int x, int y, t_fractol *f);
int         close_hook(t_fractol *f);

/* ユーティリティ */
void        error_exit(const char *msg);
void        print_usage(void);
int         parse_args(int argc, char **argv, t_fractol *f);
double      ft_atof(const char *str);

#endif

2.2 マンデルブロ集合の実装

2.2.1 コア計算関数

/* mandelbrot.c */

/*
 * mandelbrot - 1点のマンデルブロ計算
 *
 * @cr: 複素数cの実部
 * @ci: 複素数cの虚部
 * @max_iter: 最大反復回数
 * @return: 発散までの反復回数(収束なら max_iter)
 *
 * 反復式: z_{n+1} = z_n² + c
 * 初期値: z_0 = 0
 */
int mandelbrot(double cr, double ci, int max_iter)
{
    double  zr;
    double  zi;
    double  zr_sq;
    double  zi_sq;
    int     n;

    zr = 0.0;
    zi = 0.0;
    n = 0;

    while (n < max_iter)
    {
        zr_sq = zr * zr;
        zi_sq = zi * zi;

        /* 脱出判定: |z|² > 4 */
        if (zr_sq + zi_sq > 4.0)
            return (n);

        /* z = z² + c */
        zi = 2.0 * zr * zi + ci;
        zr = zr_sq - zi_sq + cr;

        n++;
    }
    return (max_iter);  /* 収束(集合内) */
}

/*
 * 最適化のポイント:
 *
 * 1. zr_sq, zi_sq を事前計算
 *    - 二乗の計算を重複して行わない
 *    - zr*zr は脱出判定と z² の両方で必要
 *
 * 2. 脱出判定を先に
 *    - 早期脱出で無駄な計算を省く
 *
 * 3. sqrt() を避ける
 *    - |z| > 2 の代わりに |z|² > 4 で判定
 */

2.2.2 スムース版(滑らかな色付け用)

/*
 * mandelbrot_smooth - スムースカラーリング用
 *
 * @return: 小数点精度の反復回数
 */
double mandelbrot_smooth(double cr, double ci, int max_iter)
{
    double  zr;
    double  zi;
    double  zr_sq;
    double  zi_sq;
    int     n;

    zr = 0.0;
    zi = 0.0;
    n = 0;

    while (n < max_iter)
    {
        zr_sq = zr * zr;
        zi_sq = zi * zi;

        if (zr_sq + zi_sq > 256.0)  /* 大きめの閾値 */
        {
            /* スムージング計算 */
            double log_zn = log(zr_sq + zi_sq) / 2.0;
            double nu = log(log_zn / log(2.0)) / log(2.0);
            return (n + 1.0 - nu);
        }

        zi = 2.0 * zr * zi + ci;
        zr = zr_sq - zi_sq + cr;
        n++;
    }
    return ((double)max_iter);
}

2.3 ジュリア集合の実装

2.3.1 基本実装

/* julia.c */

/*
 * julia - 1点のジュリア集合計算
 *
 * @zr: 初期値zの実部(ピクセル位置から変換)
 * @zi: 初期値zの虚部
 * @c: ジュリア集合のパラメータ(固定)
 * @max_iter: 最大反復回数
 */
int julia(double zr, double zi, t_complex c, int max_iter)
{
    double  zr_sq;
    double  zi_sq;
    int     n;

    n = 0;
    while (n < max_iter)
    {
        zr_sq = zr * zr;
        zi_sq = zi * zi;

        if (zr_sq + zi_sq > 4.0)
            return (n);

        /* z = z² + c */
        zi = 2.0 * zr * zi + c.imag;
        zr = zr_sq - zi_sq + c.real;

        n++;
    }
    return (max_iter);
}

2.3.2 美しいパラメータセット

/* 美しいジュリア集合のプリセット */
typedef struct s_preset
{
    const char  *name;
    double      real;
    double      imag;
}   t_preset;

static const t_preset g_julia_presets[] = {
    {"Dendrite",     0.0,      1.0},
    {"Rabbit",      -0.123,    0.745},
    {"Sea Horse",   -0.74543,  0.11301},
    {"Siegel Disk",  -0.391,   -0.587},
    {"Spiral",       0.285,    0.01},
    {"Star",        -0.4,      0.6},
    {"Frost",       -0.7,      0.27015},
    {"Lightning",   -0.8,      0.156},
    {NULL, 0, 0}
};

void cycle_julia_preset(t_fractol *f)
{
    static int index = 0;

    f->julia_c.real = g_julia_presets[index].real;
    f->julia_c.imag = g_julia_presets[index].imag;

    index++;
    if (g_julia_presets[index].name == NULL)
        index = 0;
}

2.4 バーニングシップの実装

/* burning_ship.c */

/*
 * burning_ship - バーニングシップ・フラクタル
 *
 * 反復式: z_{n+1} = (|Re(z_n)| + i|Im(z_n)|)² + c
 *
 * マンデルブロとの違い:
 * 各成分の絶対値を取ってから二乗する
 */
int burning_ship(double cr, double ci, int max_iter)
{
    double  zr;
    double  zi;
    double  zr_sq;
    double  zi_sq;
    int     n;

    zr = 0.0;
    zi = 0.0;
    n = 0;

    while (n < max_iter)
    {
        zr_sq = zr * zr;
        zi_sq = zi * zi;

        if (zr_sq + zi_sq > 4.0)
            return (n);

        /* 絶対値を取る */
        zr = fabs(zr);
        zi = fabs(zi);

        /* z = z² + c */
        double new_zi = 2.0 * zr * zi + ci;
        zr = zr_sq - zi_sq + cr;
        zi = new_zi;

        n++;
    }
    return (max_iter);
}

/*
 * バーニングシップの表示:
 * - Y軸を反転させると「燃える船」の形に見える
 * - ci = -ci として計算するか、表示時に反転
 */

2.5 描画エンジン

2.5.1 メインレンダリングループ

/* render.c */

void render(t_fractol *f)
{
    int     px;
    int     py;
    double  cr;
    double  ci;
    int     color;
    int     n;

    py = 0;
    while (py < WIN_HEIGHT)
    {
        px = 0;
        while (px < WIN_WIDTH)
        {
            /* ピクセル座標 → 複素座標 */
            cr = (px - WIN_WIDTH / 2.0) / f->zoom + f->offset_x;
            ci = (py - WIN_HEIGHT / 2.0) / f->zoom + f->offset_y;

            /* フラクタル計算 */
            n = calculate_point(f, cr, ci);

            /* 色の取得と描画 */
            color = get_color(n, f->max_iter, f->color_scheme);
            my_mlx_pixel_put(&f->img, px, py, color);

            px++;
        }
        py++;
    }

    mlx_put_image_to_window(f->mlx, f->win, f->img.ptr, 0, 0);
}

int calculate_point(t_fractol *f, double cr, double ci)
{
    if (f->type == MANDELBROT)
        return (mandelbrot(cr, ci, f->max_iter));
    else if (f->type == JULIA)
        return (julia(cr, ci, f->julia_c, f->max_iter));
    else if (f->type == BURNING_SHIP)
        return (burning_ship(cr, -ci, f->max_iter));  /* Y反転 */
    return (0);
}

2.5.2 最適化版レンダリング

/* 増分計算による最適化 */
void render_optimized(t_fractol *f)
{
    int     px;
    int     py;
    double  cr;
    double  ci;
    double  x_start;
    double  y_start;
    double  x_inc;
    double  y_inc;

    /* 増分を事前計算 */
    x_inc = 1.0 / f->zoom;
    y_inc = 1.0 / f->zoom;
    x_start = f->offset_x - (WIN_WIDTH / 2.0) / f->zoom;
    y_start = f->offset_y - (WIN_HEIGHT / 2.0) / f->zoom;

    ci = y_start;
    py = 0;
    while (py < WIN_HEIGHT)
    {
        cr = x_start;
        px = 0;
        while (px < WIN_WIDTH)
        {
            int n = calculate_point(f, cr, ci);
            int color = get_color(n, f->max_iter, f->color_scheme);
            my_mlx_pixel_put(&f->img, px, py, color);

            cr += x_inc;  /* 乗算→加算 */
            px++;
        }
        ci += y_inc;
        py++;
    }

    mlx_put_image_to_window(f->mlx, f->win, f->img.ptr, 0, 0);
}

2.6 カラーリング

2.6.1 基本カラースキーム

/* color.c */

/* カラースキーム定義 */
# define SCHEME_CLASSIC     0
# define SCHEME_FIRE        1
# define SCHEME_OCEAN       2
# define SCHEME_RAINBOW     3
# define SCHEME_GRAYSCALE   4

int get_color(int n, int max_iter, int scheme)
{
    double  ratio;

    /* 集合内部は黒 */
    if (n == max_iter)
        return (0x000000);

    ratio = (double)n / max_iter;

    switch (scheme)
    {
        case SCHEME_CLASSIC:
            return (color_classic(ratio));
        case SCHEME_FIRE:
            return (color_fire(ratio));
        case SCHEME_OCEAN:
            return (color_ocean(ratio));
        case SCHEME_RAINBOW:
            return (color_rainbow(ratio));
        case SCHEME_GRAYSCALE:
            return (color_grayscale(ratio));
        default:
            return (color_classic(ratio));
    }
}

2.6.2 カラースキーム実装

/* クラシック(青→白) */
int color_classic(double ratio)
{
    int r, g, b;

    r = (int)(9 * (1 - ratio) * ratio * ratio * ratio * 255);
    g = (int)(15 * (1 - ratio) * (1 - ratio) * ratio * ratio * 255);
    b = (int)(8.5 * (1 - ratio) * (1 - ratio) * (1 - ratio) * ratio * 255);

    return ((r << 16) | (g << 8) | b);
}

/* ファイアー(黒→赤→黄→白) */
int color_fire(double ratio)
{
    if (ratio < 0.33)
        return (interpolate_color(0x000000, 0xFF0000, ratio * 3));
    else if (ratio < 0.66)
        return (interpolate_color(0xFF0000, 0xFFFF00, (ratio - 0.33) * 3));
    else
        return (interpolate_color(0xFFFF00, 0xFFFFFF, (ratio - 0.66) * 3));
}

/* オーシャン(深青→シアン→白) */
int color_ocean(double ratio)
{
    if (ratio < 0.5)
        return (interpolate_color(0x000033, 0x00FFFF, ratio * 2));
    else
        return (interpolate_color(0x00FFFF, 0xFFFFFF, (ratio - 0.5) * 2));
}

/* レインボー(HSV変換) */
int color_rainbow(double ratio)
{
    double  h = ratio * 360.0;  /* 色相 */
    double  s = 1.0;            /* 彩度 */
    double  v = 1.0;            /* 明度 */

    return (hsv_to_rgb(h, s, v));
}

/* グレースケール */
int color_grayscale(double ratio)
{
    int gray = (int)(ratio * 255);
    return ((gray << 16) | (gray << 8) | gray);
}

2.6.3 HSV→RGB変換

/*
 * HSV to RGB 変換
 *
 * H: 色相 (0-360)
 * S: 彩度 (0-1)
 * V: 明度 (0-1)
 */
int hsv_to_rgb(double h, double s, double v)
{
    double  c;
    double  x;
    double  m;
    double  r, g, b;
    int     hi;

    c = v * s;
    hi = (int)(h / 60.0) % 6;
    x = c * (1.0 - fabs(fmod(h / 60.0, 2.0) - 1.0));
    m = v - c;

    if (hi == 0)
        { r = c; g = x; b = 0; }
    else if (hi == 1)
        { r = x; g = c; b = 0; }
    else if (hi == 2)
        { r = 0; g = c; b = x; }
    else if (hi == 3)
        { r = 0; g = x; b = c; }
    else if (hi == 4)
        { r = x; g = 0; b = c; }
    else
        { r = c; g = 0; b = x; }

    return (((int)((r + m) * 255) << 16) |
            ((int)((g + m) * 255) << 8) |
            (int)((b + m) * 255));
}

2.7 インタラクション

2.7.1 ズーム操作

/* mouse_hooks.c */

int mouse_hook(int button, int x, int y, t_fractol *f)
{
    double  mouse_re;
    double  mouse_im;

    /* マウス位置を複素座標に変換 */
    mouse_re = (x - WIN_WIDTH / 2.0) / f->zoom + f->offset_x;
    mouse_im = (y - WIN_HEIGHT / 2.0) / f->zoom + f->offset_y;

    if (button == MOUSE_SCROLL_UP)
    {
        /* ズームイン(マウス位置を中心に) */
        f->zoom *= ZOOM_FACTOR;
        f->offset_x = mouse_re - (x - WIN_WIDTH / 2.0) / f->zoom;
        f->offset_y = mouse_im - (y - WIN_HEIGHT / 2.0) / f->zoom;
        adjust_iterations(f, 1);
    }
    else if (button == MOUSE_SCROLL_DOWN)
    {
        /* ズームアウト */
        f->zoom /= ZOOM_FACTOR;
        f->offset_x = mouse_re - (x - WIN_WIDTH / 2.0) / f->zoom;
        f->offset_y = mouse_im - (y - WIN_HEIGHT / 2.0) / f->zoom;
        adjust_iterations(f, -1);
    }

    render(f);
    return (0);
}

/*
 * ズームレベルに応じて反復回数を調整
 */
void adjust_iterations(t_fractol *f, int direction)
{
    if (direction > 0)
        f->max_iter = (int)(f->max_iter * 1.1);
    else
        f->max_iter = (int)(f->max_iter / 1.1);

    /* 範囲制限 */
    if (f->max_iter < 50)
        f->max_iter = 50;
    if (f->max_iter > 5000)
        f->max_iter = 5000;
}

2.7.2 キーボード操作

/* key_hooks.c */

int key_hook(int keycode, t_fractol *f)
{
    if (keycode == KEY_ESC)
        return (close_hook(f));

    /* 移動 */
    if (keycode == KEY_LEFT)
        f->offset_x -= 50.0 / f->zoom;
    else if (keycode == KEY_RIGHT)
        f->offset_x += 50.0 / f->zoom;
    else if (keycode == KEY_UP)
        f->offset_y -= 50.0 / f->zoom;
    else if (keycode == KEY_DOWN)
        f->offset_y += 50.0 / f->zoom;

    /* ズーム */
    else if (keycode == KEY_PLUS)
    {
        f->zoom *= ZOOM_FACTOR;
        adjust_iterations(f, 1);
    }
    else if (keycode == KEY_MINUS)
    {
        f->zoom /= ZOOM_FACTOR;
        adjust_iterations(f, -1);
    }

    /* リセット */
    else if (keycode == KEY_R)
        init_defaults(f);

    /* カラースキーム切り替え */
    else if (keycode == KEY_C)
        f->color_scheme = (f->color_scheme + 1) % 5;

    /* フラクタル切り替え */
    else if (keycode == KEY_1)
        f->type = MANDELBROT;
    else if (keycode == KEY_2)
        f->type = JULIA;
    else if (keycode == KEY_3)
        f->type = BURNING_SHIP;

    render(f);
    return (0);
}

2.7.3 ジュリアのマウス追従(ボーナス)

/* ジュリアパラメータをマウスで操作 */
int mouse_move_hook(int x, int y, t_fractol *f)
{
    if (f->type != JULIA)
        return (0);

    /* マウス位置をパラメータに変換 */
    f->julia_c.real = (x - WIN_WIDTH / 2.0) / (WIN_WIDTH / 4.0);
    f->julia_c.imag = (y - WIN_HEIGHT / 2.0) / (WIN_HEIGHT / 4.0);

    render(f);
    return (0);
}

/* フック登録 */
void setup_mouse_move(t_fractol *f)
{
    mlx_hook(f->win, 6, 1L << 6, mouse_move_hook, f);
}

2.8 引数処理

2.8.1 コマンドライン解析

/* parse.c */

int parse_args(int argc, char **argv, t_fractol *f)
{
    if (argc < 2)
    {
        print_usage();
        return (0);
    }

    /* フラクタルタイプの判定 */
    if (ft_strcmp(argv[1], "mandelbrot") == 0)
        f->type = MANDELBROT;
    else if (ft_strcmp(argv[1], "julia") == 0)
    {
        f->type = JULIA;
        if (!parse_julia_params(argc, argv, f))
            return (0);
    }
    else if (ft_strcmp(argv[1], "burningship") == 0 ||
             ft_strcmp(argv[1], "burning_ship") == 0)
        f->type = BURNING_SHIP;
    else
    {
        ft_putendl_fd("Error: Unknown fractal type", 2);
        print_usage();
        return (0);
    }

    return (1);
}

int parse_julia_params(int argc, char **argv, t_fractol *f)
{
    /* デフォルトのジュリアパラメータ */
    f->julia_c.real = -0.7;
    f->julia_c.imag = 0.27015;

    /* カスタムパラメータが指定された場合 */
    if (argc >= 4)
    {
        f->julia_c.real = ft_atof(argv[2]);
        f->julia_c.imag = ft_atof(argv[3]);

        /* 範囲チェック */
        if (fabs(f->julia_c.real) > 2.0 || fabs(f->julia_c.imag) > 2.0)
        {
            ft_putendl_fd("Error: Julia parameters out of range (-2 to 2)", 2);
            return (0);
        }
    }

    return (1);
}

2.8.2 使用方法表示

void print_usage(void)
{
    ft_putendl_fd("\nUsage:", 1);
    ft_putendl_fd("  ./fractol <fractal_type> [options]\n", 1);
    ft_putendl_fd("Fractal types:", 1);
    ft_putendl_fd("  mandelbrot         - Mandelbrot set", 1);
    ft_putendl_fd("  julia [re] [im]    - Julia set (optional params)", 1);
    ft_putendl_fd("  burningship        - Burning Ship fractal\n", 1);
    ft_putendl_fd("Examples:", 1);
    ft_putendl_fd("  ./fractol mandelbrot", 1);
    ft_putendl_fd("  ./fractol julia", 1);
    ft_putendl_fd("  ./fractol julia -0.7 0.27015", 1);
    ft_putendl_fd("  ./fractol burningship\n", 1);
    ft_putendl_fd("Controls:", 1);
    ft_putendl_fd("  Scroll      - Zoom in/out", 1);
    ft_putendl_fd("  Arrows      - Move", 1);
    ft_putendl_fd("  +/-         - Zoom", 1);
    ft_putendl_fd("  C           - Change color scheme", 1);
    ft_putendl_fd("  R           - Reset view", 1);
    ft_putendl_fd("  ESC         - Exit\n", 1);
}

2.8.3 ft_atof実装

/* 文字列を浮動小数点数に変換 */
double ft_atof(const char *str)
{
    double  result;
    double  decimal;
    int     sign;
    int     i;

    result = 0.0;
    decimal = 0.1;
    sign = 1;
    i = 0;

    /* 空白スキップ */
    while (str[i] == ' ' || (str[i] >= '\t' && str[i] <= '\r'))
        i++;

    /* 符号 */
    if (str[i] == '-' || str[i] == '+')
    {
        if (str[i] == '-')
            sign = -1;
        i++;
    }

    /* 整数部 */
    while (str[i] >= '0' && str[i] <= '9')
    {
        result = result * 10.0 + (str[i] - '0');
        i++;
    }

    /* 小数部 */
    if (str[i] == '.')
    {
        i++;
        while (str[i] >= '0' && str[i] <= '9')
        {
            result += (str[i] - '0') * decimal;
            decimal *= 0.1;
            i++;
        }
    }

    return (result * sign);
}

2.9 メイン関数

/* main.c */

int main(int argc, char **argv)
{
    t_fractol   f;

    /* デフォルト初期化 */
    init_defaults(&f);

    /* 引数解析 */
    if (!parse_args(argc, argv, &f))
        return (1);

    /* MiniLibX初期化 */
    if (!init_fractol(&f))
        return (1);

    /* 初期描画 */
    render(&f);

    /* イベントフック設定 */
    mlx_key_hook(f.win, key_hook, &f);
    mlx_mouse_hook(f.win, mouse_hook, &f);
    mlx_hook(f.win, 17, 0, close_hook, &f);

    /* イベントループ */
    mlx_loop(f.mlx);

    return (0);
}

void init_defaults(t_fractol *f)
{
    f->zoom = DEFAULT_ZOOM;
    f->offset_x = 0.0;
    f->offset_y = 0.0;
    f->max_iter = DEFAULT_MAX_ITER;
    f->color_scheme = SCHEME_CLASSIC;
    f->julia_c.real = -0.7;
    f->julia_c.imag = 0.27015;
}

int init_fractol(t_fractol *f)
{
    f->mlx = mlx_init();
    if (!f->mlx)
        return (error_return("MLX init failed", 0));

    f->win = mlx_new_window(f->mlx, WIN_WIDTH, WIN_HEIGHT, WIN_TITLE);
    if (!f->win)
        return (error_return("Window creation failed", 0));

    f->img.ptr = mlx_new_image(f->mlx, WIN_WIDTH, WIN_HEIGHT);
    if (!f->img.ptr)
        return (error_return("Image creation failed", 0));

    f->img.addr = mlx_get_data_addr(f->img.ptr, &f->img.bpp,
                                     &f->img.line_len, &f->img.endian);

    /* フラクタル固有の初期位置 */
    if (f->type == MANDELBROT)
        f->offset_x = -0.5;
    else if (f->type == BURNING_SHIP)
    {
        f->offset_x = -0.4;
        f->offset_y = -0.5;
    }

    return (1);
}

2.10 本章のまとめ

fract-ol 実装チェックリスト:

必須機能:
□ マンデルブロ集合の表示
□ ジュリア集合の表示
□ 追加フラクタル(バーニングシップ等)
□ マウスホイールでズーム
□ 矢印キーで移動
□ ESCで終了
□ 色付け

ボーナス機能:
□ ズーム位置がマウス中心
□ カラースキーム切り替え
□ ジュリアパラメータの動的変更
□ スムースカラーリング
□ 反復回数の動的調整

コード品質:
□ メモリリークなし
□ Normチェック通過
□ 42ヘッダー
□ 適切なエラー処理

fract-olは、数学とプログラミングの美しい融合です。無限の複雑さを秘めたフラクタルの世界を探索しながら、グラフィックスプログラミングと最適化のスキルを磨きましょう。