static unsigned int filter_tvnoise_hash_u32(int x, int y, unsigned int seed) {
    unsigned int h = seed ^ 0x85ebca6bu;
    h ^= ((unsigned int)x * 0x27d4eb2du);
    h = (h << 13) | (h >> 19);
    h ^= ((unsigned int)y * 0x165667b1u);
    h *= 0x85ebca6bu;
    h ^= h >> 13;
    h *= 0xc2b2ae35u;
    h ^= h >> 16;
    return h;
}


static double filter_tvnoise_hash01(int x, int y, unsigned int seed) {
    return (double)(filter_tvnoise_hash_u32(x, y, seed) & 0x00ffffffu) / 16777215.0;
}


static double filter_filmnoise_hash_signed(int x, int y, unsigned int seed) {
    return filter_tvnoise_hash01(x, y, seed) * 2.0 - 1.0;
}


static double filter_find_edges_gray_sample(const double *snapshot, int width, int height, int x, int y, double max_value) {
    long long base;
    double r, g, b;
    if (x < 0) x = 0;
    else if (x >= width) x = width - 1;
    if (y < 0) y = 0;
    else if (y >= height) y = height - 1;
    base = ((long long)y * (long long)width + (long long)x) * 4LL;
    r = image_unit_clamp(snapshot[base + 0] / max_value);
    g = image_unit_clamp(snapshot[base + 1] / max_value);
    b = image_unit_clamp(snapshot[base + 2] / max_value);
    return 0.299 * r + 0.587 * g + 0.114 * b;
}


static double filter_backlight_circle(double x, double y, double cx, double cy, double radius, double power) {
    double dx = x - cx;
    double dy = y - cy;
    double dist = sqrt(dx * dx + dy * dy);
    double t;
    if (!(radius > 1e-9)) return 0.0;
    t = 1.0 - dist / radius;
    if (t <= 0.0) return 0.0;
    if (power < 0.05) power = 0.05;
    return pow(t, power);
}


static double filter_backlight_ring(double x, double y, double cx, double cy, double radius, double thickness, double power) {
    double dx = x - cx;
    double dy = y - cy;
    double dist = sqrt(dx * dx + dy * dy);
    double t;
    if (!(radius > 1e-9) || !(thickness > 1e-9)) return 0.0;
    t = 1.0 - fabs(dist - radius) / thickness;
    if (t <= 0.0) return 0.0;
    if (power < 0.05) power = 0.05;
    return pow(t, power);
}


static double filter_backlight_streak(double x, double y, double cx, double cy, double angle_deg, double half_len, double half_width, double power) {
    double dx = x - cx;
    double dy = y - cy;
    double rad = angle_deg * M_PI / 180.0;
    double ca = cos(rad);
    double sa = sin(rad);
    double along = fabs(dx * ca + dy * sa);
    double across = fabs(-dx * sa + dy * ca);
    double ta, tb;
    if (!(half_len > 1e-9) || !(half_width > 1e-9)) return 0.0;
    if (along > half_len || across > half_width) return 0.0;
    ta = 1.0 - along / half_len;
    tb = 1.0 - across / half_width;
    if (ta <= 0.0 || tb <= 0.0) return 0.0;
    if (power < 0.05) power = 0.05;
    return pow(ta, power) * pow(tb, 0.65);
}


void image_apply_tvnoise(const char *target_name, const char *source_name, int in_place, double strength_value, double seed_value, double density_value) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var = NULL;
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int logical_count = array_logical_count(source_var);
    long long process_count;
    double max_value = draw_resolve_image_value_max(source_var);
    double strength = is_null_number(strength_value) ? 0.0 : strength_value;
    double density = is_null_number(density_value) ? 0.0 : density_value;
    double strength_norm;
    double density_norm;
    double *snapshot = NULL;
    unsigned int seed_u32;
    int note_needed = 0;
    int i;
    char note_buffer[256];
    if (width < 0 || height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (logical_count <= 0) {
        if (target_name != NULL && strcmp(target_name, source_name) != 0) assign_array_cow(target_name, source_name);
        return;
    }
    if (width > 0 && height > 0) {
        process_count = (long long)width * (long long)height * 4LL;
        if ((long long)logical_count < process_count) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    } else {
        runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    }
    if (strength < 0.0) { strength = 0.0; note_needed = 1; }
    if (strength > 100.0) { strength = 100.0; note_needed = 1; }
    if (density < 0.0) { density = 0.0; note_needed = 1; }
    if (density > 100.0) { density = 100.0; note_needed = 1; }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"TVNOISE\") の strength と density は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    strength_norm = strength / 100.0;
    density_norm = density / 100.0;
    seed_u32 = (unsigned int)((long long)llround(is_null_number(seed_value) ? 0.0 : seed_value));
    if (strcmp(target_name, source_name) == 0) {
        ensure_array_unique(source_var);
        target_var = source_var;
    } else {
        dim_array(target_name, (double)(option_base + logical_count - 1));
        target_var = require_array_variable(target_name);
        for (i = 0; i < logical_count; ++i) set_b2026_number(&target_var->data[option_base + i], b2026_var_as_number(&source_var->data[option_base + i]));
    }
    if (strength_norm <= 0.0 || density_norm <= 0.0) {
        target_var->read_count = logical_count;
        target_var->screen_width = source_var->screen_width;
        target_var->screen_height = source_var->screen_height;
        target_var->image_phys_x = source_var->image_phys_x;
        target_var->image_phys_y = source_var->image_phys_y;
        target_var->image_phys_unit = source_var->image_phys_unit;
        target_var->image_pixel_mode = source_var->image_pixel_mode;
        target_var->image_value_max = source_var->image_value_max;
        target_var->fft_width = source_var->fft_width;
        target_var->fft_height = source_var->fft_height;
        target_var->fft_original_width = source_var->fft_original_width;
        target_var->fft_original_height = source_var->fft_original_height;
        target_var->fft_is_complex = source_var->fft_is_complex;
        target_var->fft_channels = source_var->fft_channels;
        target_var->fft_restore_255 = source_var->fft_restore_255;
        return;
    }
    snapshot = (double *)malloc((size_t)logical_count * sizeof(double));
    if (snapshot == NULL) runtime_error("Out of memory during FILTER TVNOISE / 【FILTER】TVNOISE 処理用メモリを確保できません。");
    for (i = 0; i < logical_count; ++i) snapshot[i] = b2026_var_as_number(&source_var->data[option_base + i]);
    for (i = 0; i < (int)process_count; i += 4) {
        int pixel_index = i / 4;
        int x = pixel_index % width;
        int y = pixel_index / width;
        long long base = (long long)i;
        double r = snapshot[base + 0];
        double g = snapshot[base + 1];
        double b = snapshot[base + 2];
        double a = snapshot[base + 3];
        double pick = filter_tvnoise_hash01(x, y, seed_u32);
        if (pick < density_norm) {
            double n = filter_tvnoise_hash01(x + 101, y + 211, seed_u32 ^ 0x9e3779b9u);
            double delta = (n * 2.0 - 1.0) * (0.45 * strength_norm * max_value);
            r += delta;
            g += delta;
            b += delta;
        }
        set_b2026_number(&target_var->data[option_base + i + 0], r);
        set_b2026_number(&target_var->data[option_base + i + 1], g);
        set_b2026_number(&target_var->data[option_base + i + 2], b);
        set_b2026_number(&target_var->data[option_base + i + 3], a);
    }
    free(snapshot);
    target_var->read_count = logical_count;
    target_var->screen_width = source_var->screen_width;
    target_var->screen_height = source_var->screen_height;
    target_var->image_phys_x = source_var->image_phys_x;
    target_var->image_phys_y = source_var->image_phys_y;
    target_var->image_phys_unit = source_var->image_phys_unit;
    target_var->image_pixel_mode = source_var->image_pixel_mode;
    target_var->image_value_max = source_var->image_value_max;
    target_var->fft_width = source_var->fft_width;
    target_var->fft_height = source_var->fft_height;
    target_var->fft_original_width = source_var->fft_original_width;
    target_var->fft_original_height = source_var->fft_original_height;
    target_var->fft_is_complex = source_var->fft_is_complex;
    target_var->fft_channels = source_var->fft_channels;
    target_var->fft_restore_255 = source_var->fft_restore_255;
}


void image_apply_backlight(const char *target_name, const char *source_name, int in_place, double light_x_value, double light_y_value, double strength_value, const char *mode_name, const char *preset_name, const char *lens_name, double halo_strength_value, double ghost_strength_value, double ring_strength_value, double light_r_value, double light_g_value, double light_b_value, double light_a_value) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var = NULL;
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int logical_count = array_logical_count(source_var);
    long long process_count;
    double max_value = draw_resolve_image_value_max(source_var);
    double strength = is_null_number(strength_value) ? 0.0 : strength_value;
    double *snapshot = NULL;
    int x, y;
    int blend_mode = 4;
    int mode_recognized = 1;
    int preset_kind = 1;
    int preset_recognized = 1;
    int lens_kind = 1;
    int lens_recognized = 1;
    int note_needed = 0;
    double light_x = is_null_number(light_x_value) ? 0.0 : light_x_value;
    double light_y = is_null_number(light_y_value) ? 0.0 : light_y_value;
    double halo_strength = is_null_number(halo_strength_value) ? 100.0 : halo_strength_value;
    double ghost_strength = is_null_number(ghost_strength_value) ? 100.0 : ghost_strength_value;
    double ring_strength = is_null_number(ring_strength_value) ? 100.0 : ring_strength_value;
    double light_r = is_null_number(light_r_value) ? max_value : light_r_value;
    double light_g = is_null_number(light_g_value) ? max_value : light_g_value;
    double light_b = is_null_number(light_b_value) ? max_value : light_b_value;
    double light_a = is_null_number(light_a_value) ? max_value : light_a_value;
    double cx = (double)(width - 1) * 0.5;
    double cy = (double)(height - 1) * 0.5;
    double axis_dx, axis_dy, axis_len;
    double perp_dx, perp_dy;
    double s_norm;
    double halo_norm, ghost_norm, ring_norm;
    double light_alpha_norm;
    double light_r_norm, light_g_norm, light_b_norm;
    double core_radius, halo_radius, glow_radius;
    double ring_radius, ring_thickness;
    double streak_length, streak_width;
    double aberration_offset;
    double core_power, halo_power;
    double core_gain_r, core_gain_g, core_gain_b, core_alpha_gain;
    double halo_gain_r, halo_gain_g, halo_gain_b, halo_alpha_gain;
    double glow_gain_r, glow_gain_g, glow_gain_b, glow_alpha_gain;
    double ghost_base_gain, ring_gain;
    int ghost_count, gi;
    char note_buffer[256];
    if (width < 0 || height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (logical_count <= 0) {
        if (target_name != NULL && strcmp(target_name, source_name) != 0) assign_array_cow(target_name, source_name);
        return;
    }
    if (width > 0 && height > 0) {
        process_count = (long long)width * (long long)height * 4LL;
        if ((long long)logical_count < process_count) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    } else {
        runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    }
    if (strength < 0.0) { strength = 0.0; note_needed = 1; }
    if (strength > 100.0) { strength = 100.0; note_needed = 1; }
    if (halo_strength < 0.0) { halo_strength = 0.0; append_basic_note_log("[NOTE] FILTER(\"逆光\") の haloStrength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。"); }
    if (halo_strength > 100.0) { halo_strength = 100.0; append_basic_note_log("[NOTE] FILTER(\"逆光\") の haloStrength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。"); }
    if (ghost_strength < 0.0) { ghost_strength = 0.0; append_basic_note_log("[NOTE] FILTER(\"逆光\") の ghostStrength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。"); }
    if (ghost_strength > 100.0) { ghost_strength = 100.0; append_basic_note_log("[NOTE] FILTER(\"逆光\") の ghostStrength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。"); }
    if (ring_strength < 0.0) { ring_strength = 0.0; append_basic_note_log("[NOTE] FILTER(\"逆光\") の ringStrength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。"); }
    if (ring_strength > 100.0) { ring_strength = 100.0; append_basic_note_log("[NOTE] FILTER(\"逆光\") の ringStrength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。"); }
    if (mode_name == NULL || *mode_name == '\0' || b2026_strcasecmp(mode_name, "SCREEN") == 0 || b2026_strcasecmp(mode_name, "スクリーン") == 0) {
        blend_mode = 4;
    } else if (b2026_strcasecmp(mode_name, "ADD") == 0 || b2026_strcasecmp(mode_name, "加算") == 0) {
        blend_mode = 1;
    } else {
        blend_mode = 4;
        mode_recognized = 0;
    }
    if (preset_name == NULL || *preset_name == '\0' || b2026_strcasecmp(preset_name, "CLASSIC") == 0 || b2026_strcasecmp(preset_name, "NORMAL") == 0 || b2026_strcasecmp(preset_name, "標準") == 0 || b2026_strcasecmp(preset_name, "クラシック") == 0) {
        preset_kind = 1;
    } else if (b2026_strcasecmp(preset_name, "PHOTOSHOP") == 0 || b2026_strcasecmp(preset_name, "PHOTOSHOPWHITE") == 0 || b2026_strcasecmp(preset_name, "白寄り") == 0 || b2026_strcasecmp(preset_name, "フォトショップ") == 0 || b2026_strcasecmp(preset_name, "フォトショップ白") == 0) {
        preset_kind = 2;
    } else if (b2026_strcasecmp(preset_name, "PHOTOSHOPBLUE") == 0 || b2026_strcasecmp(preset_name, "青寄り") == 0 || b2026_strcasecmp(preset_name, "フォトショップ青") == 0) {
        preset_kind = 6;
    } else if (b2026_strcasecmp(preset_name, "PHOTOSHOPSUNSET") == 0 || b2026_strcasecmp(preset_name, "夕焼け寄り") == 0 || b2026_strcasecmp(preset_name, "フォトショップ夕焼け") == 0) {
        preset_kind = 7;
    } else if (b2026_strcasecmp(preset_name, "PHOTOSHOPRED") == 0 || b2026_strcasecmp(preset_name, "REDDWARF") == 0 || b2026_strcasecmp(preset_name, "赤寄り") == 0 || b2026_strcasecmp(preset_name, "赤色矮星") == 0 || b2026_strcasecmp(preset_name, "フォトショップ赤") == 0) {
        preset_kind = 8;
    } else if (b2026_strcasecmp(preset_name, "PHOTOSHOPCRIMSON") == 0 || b2026_strcasecmp(preset_name, "CRIMSON") == 0 || b2026_strcasecmp(preset_name, "深赤") == 0 || b2026_strcasecmp(preset_name, "クリムゾン") == 0 || b2026_strcasecmp(preset_name, "フォトショップ深赤") == 0) {
        preset_kind = 9;
    } else if (b2026_strcasecmp(preset_name, "PHOTOSHOPMAGMA") == 0 || b2026_strcasecmp(preset_name, "MAGMA") == 0 || b2026_strcasecmp(preset_name, "マグマ") == 0 || b2026_strcasecmp(preset_name, "溶岩") == 0 || b2026_strcasecmp(preset_name, "フォトショップマグマ") == 0) {
        preset_kind = 10;
    } else if (b2026_strcasecmp(preset_name, "SOFT") == 0 || b2026_strcasecmp(preset_name, "ソフト") == 0) {
        preset_kind = 3;
    } else if (b2026_strcasecmp(preset_name, "CINEMATIC") == 0 || b2026_strcasecmp(preset_name, "シネマ") == 0) {
        preset_kind = 4;
    } else if (b2026_strcasecmp(preset_name, "HARD") == 0 || b2026_strcasecmp(preset_name, "ハード") == 0) {
        preset_kind = 5;
    } else {
        preset_kind = 1;
        preset_recognized = 0;
    }
    if (lens_name == NULL || *lens_name == '\0' || b2026_strcasecmp(lens_name, "NORMAL") == 0 || b2026_strcasecmp(lens_name, "標準") == 0) {
        lens_kind = 1;
    } else if (b2026_strcasecmp(lens_name, "WIDE") == 0 || b2026_strcasecmp(lens_name, "広角") == 0) {
        lens_kind = 2;
    } else if (b2026_strcasecmp(lens_name, "TELE") == 0 || b2026_strcasecmp(lens_name, "望遠") == 0) {
        lens_kind = 3;
    } else if (b2026_strcasecmp(lens_name, "ANAMORPHIC") == 0 || b2026_strcasecmp(lens_name, "アナモルフィック") == 0) {
        lens_kind = 4;
    } else if (b2026_strcasecmp(lens_name, "STAR4") == 0 || b2026_strcasecmp(lens_name, "4本線") == 0) {
        lens_kind = 5;
    } else if (b2026_strcasecmp(lens_name, "STAR6") == 0 || b2026_strcasecmp(lens_name, "6本線") == 0) {
        lens_kind = 6;
    } else if (b2026_strcasecmp(lens_name, "NONE") == 0 || b2026_strcasecmp(lens_name, "なし") == 0) {
        lens_kind = 7;
    } else {
        lens_kind = 1;
        lens_recognized = 0;
    }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"逆光\") の strength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    if (!mode_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"逆光\") の mode$ \"%s\" には未対応です。このため SCREEN 扱いで処理しました。", mode_name != NULL ? mode_name : "");
        append_basic_note_log(note_buffer);
    }
    if (!preset_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"逆光\") の preset$ \"%s\" には未対応です。このため CLASSIC 扱いで処理しました。", preset_name != NULL ? preset_name : "");
        append_basic_note_log(note_buffer);
    }
    if (!lens_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"逆光\") の lens$ \"%s\" には未対応です。このため NORMAL 扱いで処理しました。", lens_name != NULL ? lens_name : "");
        append_basic_note_log(note_buffer);
    }
    s_norm = strength / 100.0;
    halo_norm = halo_strength / 100.0;
    ghost_norm = ghost_strength / 100.0;
    ring_norm = ring_strength / 100.0;
    axis_dx = cx - light_x;
    axis_dy = cy - light_y;
    axis_len = sqrt(axis_dx * axis_dx + axis_dy * axis_dy);
    if (axis_len <= 1e-9) { axis_dx = 1.0; axis_dy = 0.0; axis_len = 1.0; }
    axis_dx /= axis_len;
    axis_dy /= axis_len;
    perp_dx = -axis_dy;
    perp_dy = axis_dx;
    if (preset_kind == 1) {
        core_radius = 12.0 + 42.0 * s_norm;
        halo_radius = 40.0 + 180.0 * s_norm;
        glow_radius = 90.0 + 320.0 * s_norm;
        core_power = 1.4;
        halo_power = 2.2;
        ghost_count = 4;
        core_gain_r = 1.00; core_gain_g = 0.96; core_gain_b = 0.88; core_alpha_gain = 0.55;
        halo_gain_r = 0.85; halo_gain_g = 0.72; halo_gain_b = 0.46; halo_alpha_gain = 0.28;
        glow_gain_r = 0.35; glow_gain_g = 0.28; glow_gain_b = 0.18; glow_alpha_gain = 0.10;
        ghost_base_gain = 0.22;
        ring_gain = 0.10;
    } else if (preset_kind == 2) {
        core_radius = 18.0 + 30.0 * s_norm;
        halo_radius = 55.0 + 125.0 * s_norm;
        glow_radius = 110.0 + 220.0 * s_norm;
        core_power = 1.7;
        halo_power = 2.6;
        ghost_count = 2;
        core_gain_r = 1.00; core_gain_g = 0.99; core_gain_b = 0.97; core_alpha_gain = 0.82;
        halo_gain_r = 1.00; halo_gain_g = 0.86; halo_gain_b = 1.18; halo_alpha_gain = 0.56;
        glow_gain_r = 0.52; glow_gain_g = 0.58; glow_gain_b = 0.86; glow_alpha_gain = 0.22;
        ghost_base_gain = 0.12;
        ring_gain = 0.26;
    } else if (preset_kind == 6) {
        core_radius = 18.0 + 30.0 * s_norm;
        halo_radius = 58.0 + 132.0 * s_norm;
        glow_radius = 118.0 + 235.0 * s_norm;
        core_power = 1.7;
        halo_power = 2.5;
        ghost_count = 2;
        core_gain_r = 0.92; core_gain_g = 0.98; core_gain_b = 1.05; core_alpha_gain = 0.84;
        halo_gain_r = 0.72; halo_gain_g = 0.90; halo_gain_b = 1.28; halo_alpha_gain = 0.62;
        glow_gain_r = 0.34; glow_gain_g = 0.54; glow_gain_b = 0.96; glow_alpha_gain = 0.24;
        ghost_base_gain = 0.14;
        ring_gain = 0.28;
    } else if (preset_kind == 7) {
        core_radius = 19.0 + 31.0 * s_norm;
        halo_radius = 60.0 + 138.0 * s_norm;
        glow_radius = 125.0 + 245.0 * s_norm;
        core_power = 1.6;
        halo_power = 2.35;
        ghost_count = 3;
        core_gain_r = 1.06; core_gain_g = 0.97; core_gain_b = 0.86; core_alpha_gain = 0.84;
        halo_gain_r = 1.28; halo_gain_g = 0.88; halo_gain_b = 0.58; halo_alpha_gain = 0.62;
        glow_gain_r = 0.90; glow_gain_g = 0.44; glow_gain_b = 0.20; glow_alpha_gain = 0.24;
        ghost_base_gain = 0.16;
        ring_gain = 0.30;
    } else if (preset_kind == 8) {
        core_radius = 20.0 + 34.0 * s_norm;
        halo_radius = 68.0 + 150.0 * s_norm;
        glow_radius = 145.0 + 280.0 * s_norm;
        core_power = 1.45;
        halo_power = 2.15;
        ghost_count = 3;
        core_gain_r = 1.18; core_gain_g = 0.86; core_gain_b = 0.68; core_alpha_gain = 0.88;
        halo_gain_r = 1.42; halo_gain_g = 0.66; halo_gain_b = 0.34; halo_alpha_gain = 0.72;
        glow_gain_r = 1.08; glow_gain_g = 0.34; glow_gain_b = 0.10; glow_alpha_gain = 0.32;
        ghost_base_gain = 0.20;
        ring_gain = 0.36;
    } else if (preset_kind == 9) {
        core_radius = 21.0 + 36.0 * s_norm;
        halo_radius = 72.0 + 165.0 * s_norm;
        glow_radius = 155.0 + 305.0 * s_norm;
        core_power = 1.35;
        halo_power = 2.00;
        ghost_count = 4;
        core_gain_r = 1.26; core_gain_g = 0.70; core_gain_b = 0.42; core_alpha_gain = 0.92;
        halo_gain_r = 1.62; halo_gain_g = 0.44; halo_gain_b = 0.16; halo_alpha_gain = 0.84;
        glow_gain_r = 1.28; glow_gain_g = 0.20; glow_gain_b = 0.02; glow_alpha_gain = 0.40;
        ghost_base_gain = 0.24;
        ring_gain = 0.42;
    } else if (preset_kind == 10) {
        core_radius = 20.0 + 34.0 * s_norm;
        halo_radius = 70.0 + 158.0 * s_norm;
        glow_radius = 150.0 + 292.0 * s_norm;
        core_power = 1.40;
        halo_power = 2.08;
        ghost_count = 4;
        core_gain_r = 1.22; core_gain_g = 0.82; core_gain_b = 0.46; core_alpha_gain = 0.90;
        halo_gain_r = 1.54; halo_gain_g = 0.72; halo_gain_b = 0.18; halo_alpha_gain = 0.80;
        glow_gain_r = 1.18; glow_gain_g = 0.38; glow_gain_b = 0.03; glow_alpha_gain = 0.36;
        ghost_base_gain = 0.22;
        ring_gain = 0.40;
    } else if (preset_kind == 3) {
        core_radius = 15.0 + 34.0 * s_norm;
        halo_radius = 65.0 + 180.0 * s_norm;
        glow_radius = 130.0 + 300.0 * s_norm;
        core_power = 1.9;
        halo_power = 3.0;
        ghost_count = 2;
        core_gain_r = 0.98; core_gain_g = 0.99; core_gain_b = 1.00; core_alpha_gain = 0.50;
        halo_gain_r = 0.70; halo_gain_g = 0.82; halo_gain_b = 1.00; halo_alpha_gain = 0.34;
        glow_gain_r = 0.34; glow_gain_g = 0.44; glow_gain_b = 0.72; glow_alpha_gain = 0.14;
        ghost_base_gain = 0.07;
        ring_gain = 0.06;
    } else if (preset_kind == 4) {
        core_radius = 16.0 + 28.0 * s_norm;
        halo_radius = 48.0 + 135.0 * s_norm;
        glow_radius = 135.0 + 260.0 * s_norm;
        core_power = 1.35;
        halo_power = 2.3;
        ghost_count = 3;
        core_gain_r = 1.00; core_gain_g = 0.95; core_gain_b = 0.88; core_alpha_gain = 0.62;
        halo_gain_r = 0.92; halo_gain_g = 0.72; halo_gain_b = 0.48; halo_alpha_gain = 0.28;
        glow_gain_r = 0.55; glow_gain_g = 0.36; glow_gain_b = 0.18; glow_alpha_gain = 0.10;
        ghost_base_gain = 0.22;
        ring_gain = 0.24;
    } else {
        core_radius = 11.0 + 26.0 * s_norm;
        halo_radius = 34.0 + 110.0 * s_norm;
        glow_radius = 85.0 + 210.0 * s_norm;
        core_power = 1.0;
        halo_power = 1.7;
        ghost_count = 5;
        core_gain_r = 1.00; core_gain_g = 0.97; core_gain_b = 0.94; core_alpha_gain = 0.78;
        halo_gain_r = 0.90; halo_gain_g = 0.74; halo_gain_b = 0.52; halo_alpha_gain = 0.32;
        glow_gain_r = 0.42; glow_gain_g = 0.31; glow_gain_b = 0.18; glow_alpha_gain = 0.08;
        ghost_base_gain = 0.28;
        ring_gain = 0.30;
    }
    if (lens_kind == 2) { core_radius *= 1.18; halo_radius *= 1.20; glow_radius *= 1.18; ghost_count += 1; }
    else if (lens_kind == 3) { core_radius *= 0.88; halo_radius *= 0.88; glow_radius *= 0.92; if (ghost_count > 1) ghost_count -= 1; }
    else if (lens_kind == 7) { ghost_count = 0; }
    ring_radius = core_radius + (halo_radius - core_radius) * 0.55;
    ring_thickness = 4.0 + 14.0 * s_norm;
    streak_length = 80.0 + 260.0 * s_norm;
    streak_width = 2.5 + 10.0 * s_norm;
    aberration_offset = (1.4 + 10.0 * s_norm) * (0.30 + 0.70 * halo_norm);
    if (preset_kind == 2) aberration_offset *= 1.8;
    else if (preset_kind == 6) aberration_offset *= 2.2;
    else if (preset_kind == 7) aberration_offset *= 2.0;
    else if (preset_kind == 8) aberration_offset *= 2.4;
    else if (preset_kind == 9) aberration_offset *= 2.8;
    else if (preset_kind == 10) aberration_offset *= 2.6;
    else if (preset_kind == 4) aberration_offset *= 1.5;
    else if (preset_kind == 5) aberration_offset *= 1.35;
    if (lens_kind == 2) aberration_offset *= 1.25;
    else if (lens_kind == 3) aberration_offset *= 0.82;
    light_alpha_norm = max_value != 0.0 ? (light_a / max_value) : 1.0;
    if (!isfinite(light_alpha_norm)) light_alpha_norm = 1.0;
    if (light_alpha_norm < 0.0) light_alpha_norm = 0.0;
    light_r_norm = max_value != 0.0 ? (light_r / max_value) : 1.0;
    light_g_norm = max_value != 0.0 ? (light_g / max_value) : 1.0;
    light_b_norm = max_value != 0.0 ? (light_b / max_value) : 1.0;
    if (!isfinite(light_r_norm)) light_r_norm = 1.0;
    if (!isfinite(light_g_norm)) light_g_norm = 1.0;
    if (!isfinite(light_b_norm)) light_b_norm = 1.0;
    if (strcmp(target_name, source_name) == 0) {
        ensure_array_unique(source_var);
        target_var = source_var;
    } else {
        dim_array(target_name, (double)(option_base + logical_count - 1));
        target_var = require_array_variable(target_name);
        for (x = 0; x < logical_count; ++x) set_b2026_number(&target_var->data[option_base + x], b2026_var_as_number(&source_var->data[option_base + x]));
    }
    snapshot = (double *)malloc((size_t)logical_count * sizeof(double));
    if (snapshot == NULL) runtime_error("Out of memory during FILTER BACKLIGHT / 【FILTER】逆光処理用メモリを確保できません。");
    for (x = 0; x < logical_count; ++x) snapshot[x] = b2026_var_as_number(&source_var->data[option_base + x]);
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            long long base = ((long long)y * (long long)width + (long long)x) * 4LL;
            double dst_r = snapshot[base + 0];
            double dst_g = snapshot[base + 1];
            double dst_b = snapshot[base + 2];
            double dst_a = snapshot[base + 3];
            double flare_r = 0.0, flare_g = 0.0, flare_b = 0.0;
            double flare_alpha_n = 0.0;
            double px = (double)x;
            double py = (double)y;
            double core = filter_backlight_circle(px, py, light_x, light_y, core_radius, core_power);
            double halo = filter_backlight_circle(px, py, light_x, light_y, halo_radius, halo_power);
            double glow = filter_backlight_circle(px, py, light_x, light_y, glow_radius, 3.6);
            double halo_r = filter_backlight_circle(px, py, light_x - perp_dx * aberration_offset, light_y - perp_dy * aberration_offset, halo_radius * 0.98, halo_power);
            double halo_b = filter_backlight_circle(px, py, light_x + perp_dx * aberration_offset, light_y + perp_dy * aberration_offset, halo_radius * 1.02, halo_power);
            double glow_r = filter_backlight_circle(px, py, light_x - perp_dx * aberration_offset * 1.2, light_y - perp_dy * aberration_offset * 1.2, glow_radius * 0.98, 3.6);
            double glow_b = filter_backlight_circle(px, py, light_x + perp_dx * aberration_offset * 1.2, light_y + perp_dy * aberration_offset * 1.2, glow_radius * 1.02, 3.6);
            double ring = filter_backlight_ring(px, py, light_x, light_y, ring_radius, ring_thickness, (preset_kind == 5) ? 0.9 : 1.3);
            double streak0 = 0.0, streak1 = 0.0, streak2 = 0.0;
            if (core > 0.0 || halo > 0.0 || glow > 0.0) {
                flare_r += (core_gain_r * core + halo_gain_r * halo * halo_norm + glow_gain_r * glow * halo_norm) * max_value;
                flare_g += (core_gain_g * core + halo_gain_g * halo * halo_norm + glow_gain_g * glow * halo_norm) * max_value;
                flare_b += (core_gain_b * core + halo_gain_b * halo * halo_norm + glow_gain_b * glow * halo_norm) * max_value;
                flare_alpha_n += core_alpha_gain * core + halo_alpha_gain * halo * halo_norm + glow_alpha_gain * glow * halo_norm;
            }
            if (halo_norm > 0.0 && aberration_offset > 0.0) {
                double chroma_boost = (preset_kind == 9) ? 0.68 : ((preset_kind == 10) ? 0.62 : ((preset_kind == 8) ? 0.56 : ((preset_kind == 6) ? 0.48 : ((preset_kind == 7) ? 0.44 : ((preset_kind == 2) ? 0.34 : ((preset_kind == 4) ? 0.26 : ((preset_kind == 5) ? 0.22 : 0.14)))))));
                flare_r += (halo_r * 0.90 + glow_r * 0.44) * chroma_boost * halo_norm * max_value;
                flare_g += (halo * 0.10 + glow * 0.08) * chroma_boost * halo_norm * max_value;
                flare_b += (halo_b * 0.96 + glow_b * 0.50) * chroma_boost * halo_norm * max_value;
                flare_alpha_n += (halo_r + halo_b) * chroma_boost * 0.08 * halo_norm;
            }
            if (ring_norm > 0.0 && ring > 0.0) {
                flare_r += ring * ring_gain * 1.00 * ring_norm * max_value;
                flare_g += ring * ring_gain * 0.92 * ring_norm * max_value;
                flare_b += ring * ring_gain * 0.82 * ring_norm * max_value;
                flare_alpha_n += ring * ring_gain * 0.35 * ring_norm;
            }
            if (lens_kind == 4 || preset_kind == 4) {
                streak0 = filter_backlight_streak(px, py, light_x, light_y, 0.0, streak_length * 1.4, streak_width * 0.8, 1.6);
                if (streak0 > 0.0) {
                    flare_r += streak0 * 0.55 * max_value;
                    flare_g += streak0 * 0.38 * max_value;
                    flare_b += streak0 * 0.20 * max_value;
                    flare_alpha_n += streak0 * 0.08;
                }
            }
            if (lens_kind == 5 || lens_kind == 6) {
                streak0 = filter_backlight_streak(px, py, light_x, light_y, 0.0, streak_length, streak_width, 1.8);
                streak1 = filter_backlight_streak(px, py, light_x, light_y, 90.0, streak_length * 0.88, streak_width, 1.8);
                flare_r += (streak0 + streak1) * 0.30 * max_value;
                flare_g += (streak0 + streak1) * 0.30 * max_value;
                flare_b += (streak0 + streak1) * 0.34 * max_value;
                flare_alpha_n += (streak0 + streak1) * 0.07;
            }
            if (lens_kind == 6) {
                streak1 = filter_backlight_streak(px, py, light_x, light_y, 60.0, streak_length * 0.92, streak_width, 1.85);
                streak2 = filter_backlight_streak(px, py, light_x, light_y, -60.0, streak_length * 0.92, streak_width, 1.85);
                flare_r += (streak1 + streak2) * 0.26 * max_value;
                flare_g += (streak1 + streak2) * 0.28 * max_value;
                flare_b += (streak1 + streak2) * 0.34 * max_value;
                flare_alpha_n += (streak1 + streak2) * 0.06;
            }
            for (gi = 0; gi < ghost_count; ++gi) {
                double t = (double)(gi + 1) / (double)(ghost_count + 1);
                double lens_offset = (lens_kind == 2) ? 1.65 : ((lens_kind == 3) ? 1.10 : 1.45);
                double gx = light_x + axis_dx * axis_len * (0.30 + lens_offset * t);
                double gy = light_y + axis_dy * axis_len * (0.30 + lens_offset * t);
                double gr = (18.0 + 55.0 * (1.0 - t) + 20.0 * s_norm) * ((gi % 2 == 0) ? 1.0 : 0.7);
                double gv = filter_backlight_circle(px, py, gx, gy, gr, 2.0 + t * 2.5);
                if (gv > 0.0) {
                    double ghost_gain = ghost_base_gain * ghost_norm * (1.0 - 0.12 * (double)gi);
                    flare_r += gv * ghost_gain * (1.05 - 0.05 * (double)gi) * max_value;
                    flare_g += gv * ghost_gain * (0.78 - 0.03 * (double)gi) * max_value;
                    flare_b += gv * ghost_gain * (0.48 + 0.08 * (double)gi) * max_value;
                    flare_alpha_n += gv * ghost_gain * 0.6;
                }
            }
            if (flare_alpha_n > 0.0) {
                double src_r = flare_r;
                double src_g = flare_g;
                double src_b = flare_b;
                double src_a = flare_alpha_n * max_value;
                double sa, da, out_a, out_r, out_g, out_b;
                src_r *= light_r_norm;
                src_g *= light_g_norm;
                src_b *= light_b_norm;
                src_a *= light_alpha_norm;
                if (flare_alpha_n > 1.0) src_a = max_value;
                if (blend_mode == 4) {
                    double dst_r_n = max_value != 0.0 ? (dst_r / max_value) : 0.0;
                    double dst_g_n = max_value != 0.0 ? (dst_g / max_value) : 0.0;
                    double dst_b_n = max_value != 0.0 ? (dst_b / max_value) : 0.0;
                    double src_r_n = max_value != 0.0 ? (src_r / max_value) : 0.0;
                    double src_g_n = max_value != 0.0 ? (src_g / max_value) : 0.0;
                    double src_b_n = max_value != 0.0 ? (src_b / max_value) : 0.0;
                    if (dst_r_n < 0.0) dst_r_n = 0.0; if (dst_r_n > 1.0) dst_r_n = 1.0;
                    if (dst_g_n < 0.0) dst_g_n = 0.0; if (dst_g_n > 1.0) dst_g_n = 1.0;
                    if (dst_b_n < 0.0) dst_b_n = 0.0; if (dst_b_n > 1.0) dst_b_n = 1.0;
                    if (src_r_n < 0.0) src_r_n = 0.0; if (src_r_n > 1.0) src_r_n = 1.0;
                    if (src_g_n < 0.0) src_g_n = 0.0; if (src_g_n > 1.0) src_g_n = 1.0;
                    if (src_b_n < 0.0) src_b_n = 0.0; if (src_b_n > 1.0) src_b_n = 1.0;
                    src_r = (dst_r_n + src_r_n - (dst_r_n * src_r_n)) * max_value;
                    src_g = (dst_g_n + src_g_n - (dst_g_n * src_g_n)) * max_value;
                    src_b = (dst_b_n + src_b_n - (dst_b_n * src_b_n)) * max_value;
                } else if (blend_mode != 0) {
                    src_r = image_apply_draw_mode_value(dst_r, src_r, blend_mode);
                    src_g = image_apply_draw_mode_value(dst_g, src_g, blend_mode);
                    src_b = image_apply_draw_mode_value(dst_b, src_b, blend_mode);
                }
                sa = max_value != 0.0 ? (src_a / max_value) : 0.0;
                da = max_value != 0.0 ? (dst_a / max_value) : 0.0;
                out_a = sa + da * (1.0 - sa);
                if (out_a <= 0.0) {
                    out_r = 0.0; out_g = 0.0; out_b = 0.0; out_a = 0.0;
                } else {
                    out_r = ((src_r * sa) + (dst_r * da * (1.0 - sa))) / out_a;
                    out_g = ((src_g * sa) + (dst_g * da * (1.0 - sa))) / out_a;
                    out_b = ((src_b * sa) + (dst_b * da * (1.0 - sa))) / out_a;
                }
                set_b2026_number(&target_var->data[option_base + base + 0], out_r);
                set_b2026_number(&target_var->data[option_base + base + 1], out_g);
                set_b2026_number(&target_var->data[option_base + base + 2], out_b);
                set_b2026_number(&target_var->data[option_base + base + 3], out_a * max_value);
            } else {
                set_b2026_number(&target_var->data[option_base + base + 0], dst_r);
                set_b2026_number(&target_var->data[option_base + base + 1], dst_g);
                set_b2026_number(&target_var->data[option_base + base + 2], dst_b);
                set_b2026_number(&target_var->data[option_base + base + 3], dst_a);
            }
        }
    }
    free(snapshot);
    target_var->read_count = logical_count;
    target_var->screen_width = source_var->screen_width;
    target_var->screen_height = source_var->screen_height;
    target_var->image_phys_x = source_var->image_phys_x;
    target_var->image_phys_y = source_var->image_phys_y;
    target_var->image_phys_unit = source_var->image_phys_unit;
    target_var->image_pixel_mode = source_var->image_pixel_mode;
    target_var->image_value_max = source_var->image_value_max;
    target_var->fft_width = source_var->fft_width;
    target_var->fft_height = source_var->fft_height;
    target_var->fft_original_width = source_var->fft_original_width;
    target_var->fft_original_height = source_var->fft_original_height;
    target_var->fft_is_complex = source_var->fft_is_complex;
    target_var->fft_channels = source_var->fft_channels;
    target_var->fft_restore_255 = source_var->fft_restore_255;
}


void image_apply_find_edges(const char *target_name, const char *source_name, int in_place, const char *mode_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var = NULL;
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int logical_count = array_logical_count(source_var);
    long long process_count;
    double max_value = draw_resolve_image_value_max(source_var);
    double *snapshot = NULL;
    int mode_kind = 1;
    int mode_recognized = 1;
    int x, y;
    char note_buffer[256];
    if (width < 0 || height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (logical_count <= 0) {
        if (target_name != NULL && strcmp(target_name, source_name) != 0) assign_array_cow(target_name, source_name);
        return;
    }
    if (width > 0 && height > 0) {
        process_count = (long long)width * (long long)height * 4LL;
        if ((long long)logical_count < process_count) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    } else {
        runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    }
    if (mode_name == NULL || *mode_name == '\0' || b2026_strcasecmp(mode_name, "BLACK") == 0 || b2026_strcasecmp(mode_name, "黒線") == 0) {
        mode_kind = 1;
    } else if (b2026_strcasecmp(mode_name, "WHITE") == 0 || b2026_strcasecmp(mode_name, "白線") == 0) {
        mode_kind = 2;
    } else {
        mode_kind = 1;
        mode_recognized = 0;
    }
    if (strcmp(target_name, source_name) == 0) {
        ensure_array_unique(source_var);
        target_var = source_var;
    } else {
        dim_array(target_name, (double)(option_base + logical_count - 1));
        target_var = require_array_variable(target_name);
        for (x = 0; x < logical_count; ++x) set_b2026_number(&target_var->data[option_base + x], b2026_var_as_number(&source_var->data[option_base + x]));
    }
    if (!mode_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"輪郭検出\") の mode$ \"%s\" には未対応です。このため BLACK 扱いで処理しました。", mode_name != NULL ? mode_name : "");
        append_basic_note_log(note_buffer);
    }
    snapshot = (double *)malloc((size_t)logical_count * sizeof(double));
    if (snapshot == NULL) runtime_error("Out of memory during FILTER FIND_EDGES / 【FILTER】輪郭検出処理用メモリを確保できません。");
    for (x = 0; x < logical_count; ++x) snapshot[x] = b2026_var_as_number(&source_var->data[option_base + x]);
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            long long base = ((long long)y * (long long)width + (long long)x) * 4LL;
            double tl = filter_find_edges_gray_sample(snapshot, width, height, x - 1, y - 1, max_value);
            double tc = filter_find_edges_gray_sample(snapshot, width, height, x, y - 1, max_value);
            double tr = filter_find_edges_gray_sample(snapshot, width, height, x + 1, y - 1, max_value);
            double ml = filter_find_edges_gray_sample(snapshot, width, height, x - 1, y, max_value);
            double mr = filter_find_edges_gray_sample(snapshot, width, height, x + 1, y, max_value);
            double bl = filter_find_edges_gray_sample(snapshot, width, height, x - 1, y + 1, max_value);
            double bc = filter_find_edges_gray_sample(snapshot, width, height, x, y + 1, max_value);
            double br = filter_find_edges_gray_sample(snapshot, width, height, x + 1, y + 1, max_value);
            double gx = -tl + tr - 2.0 * ml + 2.0 * mr - bl + br;
            double gy = -tl - 2.0 * tc - tr + bl + 2.0 * bc + br;
            double mag = sqrt(gx * gx + gy * gy) / 5.6568542494923806;
            double edge_value;
            double alpha = snapshot[base + 3];
            if (mag < 0.0) mag = 0.0;
            if (mag > 1.0) mag = 1.0;
            edge_value = mag * max_value;
            if (mode_kind == 1) edge_value = max_value - edge_value;
            set_b2026_number(&target_var->data[option_base + base + 0], edge_value);
            set_b2026_number(&target_var->data[option_base + base + 1], edge_value);
            set_b2026_number(&target_var->data[option_base + base + 2], edge_value);
            set_b2026_number(&target_var->data[option_base + base + 3], alpha);
        }
    }
    free(snapshot);
    target_var->read_count = logical_count;
    target_var->screen_width = source_var->screen_width;
    target_var->screen_height = source_var->screen_height;
    target_var->image_phys_x = source_var->image_phys_x;
    target_var->image_phys_y = source_var->image_phys_y;
    target_var->image_phys_unit = source_var->image_phys_unit;
    target_var->image_pixel_mode = source_var->image_pixel_mode;
    target_var->image_value_max = source_var->image_value_max;
    target_var->fft_width = source_var->fft_width;
    target_var->fft_height = source_var->fft_height;
    target_var->fft_original_width = source_var->fft_original_width;
    target_var->fft_original_height = source_var->fft_original_height;
    target_var->fft_is_complex = source_var->fft_is_complex;
    target_var->fft_channels = source_var->fft_channels;
    target_var->fft_restore_255 = source_var->fft_restore_255;
}


void image_apply_filmnoise(const char *target_name, const char *source_name, int in_place, double strength_value, double seed_value, const char *mode_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var = NULL;
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int logical_count = array_logical_count(source_var);
    long long process_count;
    double max_value = draw_resolve_image_value_max(source_var);
    double strength = is_null_number(strength_value) ? 60.0 : strength_value;
    double strength_norm;
    double *snapshot = NULL;
    unsigned int seed_u32;
    int note_needed = 0;
    int mode_kind = 1;
    int mode_recognized = 1;
    int x, y;
    char note_buffer[256];
    if (width < 0 || height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (logical_count <= 0) {
        if (target_name != NULL && strcmp(target_name, source_name) != 0) assign_array_cow(target_name, source_name);
        return;
    }
    if (width > 0 && height > 0) {
        process_count = (long long)width * (long long)height * 4LL;
        if ((long long)logical_count < process_count) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    } else {
        runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    }
    if (strength < 0.0) { strength = 0.0; note_needed = 1; }
    if (strength > 100.0) { strength = 100.0; note_needed = 1; }
    if (mode_name == NULL || *mode_name == '\0' || b2026_strcasecmp(mode_name, "CLASSIC") == 0 || b2026_strcasecmp(mode_name, "標準") == 0) {
        mode_kind = 1;
    } else if (b2026_strcasecmp(mode_name, "SCRATCH") == 0 || b2026_strcasecmp(mode_name, "傷") == 0) {
        mode_kind = 2;
    } else if (b2026_strcasecmp(mode_name, "DUST") == 0 || b2026_strcasecmp(mode_name, "ゴミ") == 0) {
        mode_kind = 3;
    } else if (b2026_strcasecmp(mode_name, "GRAIN") == 0 || b2026_strcasecmp(mode_name, "粒子") == 0) {
        mode_kind = 4;
    } else {
        mode_kind = 1;
        mode_recognized = 0;
    }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"FILMNOISE\") の strength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    if (!mode_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"FILMNOISE\") の mode$ \"%s\" には未対応です。このため CLASSIC 扱いで処理しました。", mode_name != NULL ? mode_name : "");
        append_basic_note_log(note_buffer);
    }
    strength_norm = strength / 100.0;
    seed_u32 = (unsigned int)((long long)llround(is_null_number(seed_value) ? 0.0 : seed_value));
    if (strcmp(target_name, source_name) == 0) {
        ensure_array_unique(source_var);
        target_var = source_var;
    } else {
        dim_array(target_name, (double)(option_base + logical_count - 1));
        target_var = require_array_variable(target_name);
        for (x = 0; x < logical_count; ++x) set_b2026_number(&target_var->data[option_base + x], b2026_var_as_number(&source_var->data[option_base + x]));
    }
    if (strength_norm <= 0.0) {
        target_var->read_count = logical_count;
        target_var->screen_width = source_var->screen_width;
        target_var->screen_height = source_var->screen_height;
        target_var->image_phys_x = source_var->image_phys_x;
        target_var->image_phys_y = source_var->image_phys_y;
        target_var->image_phys_unit = source_var->image_phys_unit;
        target_var->image_pixel_mode = source_var->image_pixel_mode;
        target_var->image_value_max = source_var->image_value_max;
        target_var->fft_width = source_var->fft_width;
        target_var->fft_height = source_var->fft_height;
        target_var->fft_original_width = source_var->fft_original_width;
        target_var->fft_original_height = source_var->fft_original_height;
        target_var->fft_is_complex = source_var->fft_is_complex;
        target_var->fft_channels = source_var->fft_channels;
        target_var->fft_restore_255 = source_var->fft_restore_255;
        return;
    }
    snapshot = (double *)malloc((size_t)logical_count * sizeof(double));
    if (snapshot == NULL) runtime_error("Out of memory during FILTER FILMNOISE / 【FILTER】FILMNOISE 処理用メモリを確保できません。");
    for (x = 0; x < logical_count; ++x) snapshot[x] = b2026_var_as_number(&source_var->data[option_base + x]);
    for (y = 0; y < height; ++y) {
        double row_flicker = 1.0 + filter_filmnoise_hash_signed(-17, y, seed_u32 ^ 0x13579bdfu) * (0.01 + 0.07 * strength_norm) * ((mode_kind == 4) ? 0.35 : (mode_kind == 3 ? 1.15 : 1.0));
        double scratch_pick = filter_tvnoise_hash01(-91, y, seed_u32 ^ 0x2468ace1u);
        int scratch_row = (mode_kind == 2 || mode_kind == 1) && scratch_pick < (0.02 + 0.10 * strength_norm * ((mode_kind == 2) ? 1.3 : 0.8));
        double scratch_delta = filter_filmnoise_hash_signed(-23, y, seed_u32 ^ 0xdeadbeefu) * (0.18 + 0.35 * strength_norm) * max_value;
        for (x = 0; x < width; ++x) {
            long long base = ((long long)y * (long long)width + (long long)x) * 4LL;
            double r = snapshot[base + 0];
            double g = snapshot[base + 1];
            double b = snapshot[base + 2];
            double a = snapshot[base + 3];
            double grain = filter_filmnoise_hash_signed(x, y, seed_u32 ^ 0x31415926u) * (0.02 + 0.18 * strength_norm) * max_value;
            double coarse_grain = filter_filmnoise_hash_signed(x / 2, y / 2, seed_u32 ^ 0x27182818u) * (0.01 + 0.10 * strength_norm) * max_value;
            double dust_pick = filter_tvnoise_hash01(x, y, seed_u32 ^ 0xa55aa55au);
            double dust = 0.0;
            if (mode_kind == 1 || mode_kind == 3) {
                double dust_threshold = (mode_kind == 3) ? (0.004 + 0.055 * strength_norm) : (0.0004 + 0.010 * strength_norm);
                if (dust_pick < dust_threshold) {
                    double white_pick = filter_tvnoise_hash01(x + 11, y + 17, seed_u32 ^ 0x10293847u);
                    dust = ((white_pick < 0.72) ? 1.0 : -1.0) * (0.20 + 0.78 * strength_norm) * max_value;
                }
                if (mode_kind == 3) {
                    double cluster_pick = filter_tvnoise_hash01(x / 3, y / 3, seed_u32 ^ 0x55667788u);
                    if (cluster_pick < (0.006 + 0.050 * strength_norm)) {
                        double cluster_tone = filter_tvnoise_hash01(x / 5 + 31, y / 5 + 47, seed_u32 ^ 0x88776655u);
                        dust += ((cluster_tone < 0.70) ? 1.0 : -1.0) * (0.10 + 0.45 * strength_norm) * max_value;
                    }
                    if (filter_tvnoise_hash01(x / 2 + 7, 0, seed_u32 ^ 0x1234abcdu) < (0.010 + 0.040 * strength_norm)) {
                        double streak_shape = filter_tvnoise_hash01(x / 2 + 19, y / 11 + 23, seed_u32 ^ 0xabcdef01u);
                        if (streak_shape < 0.55) {
                            dust += (0.05 + 0.22 * strength_norm) * max_value;
                        }
                    }
                }
            }
            if (mode_kind == 4) grain *= 1.8;
            else if (mode_kind == 2) grain *= 0.55;
            else if (mode_kind == 3) {
                grain *= 0.35;
                coarse_grain *= 1.75;
            } else {
                coarse_grain *= 0.35;
            }
            r = r * row_flicker + grain + coarse_grain + dust;
            g = g * row_flicker + grain + coarse_grain + dust;
            b = b * row_flicker + grain + coarse_grain + dust;
            if (scratch_row) {
                double scratch_mask = 1.0;
                if (mode_kind == 1) scratch_mask = 0.65 + 0.35 * filter_tvnoise_hash01(x, y + 101, seed_u32 ^ 0x456789abu);
                r += scratch_delta * scratch_mask;
                g += scratch_delta * scratch_mask;
                b += scratch_delta * scratch_mask;
            }
            set_b2026_number(&target_var->data[option_base + base + 0], r);
            set_b2026_number(&target_var->data[option_base + base + 1], g);
            set_b2026_number(&target_var->data[option_base + base + 2], b);
            set_b2026_number(&target_var->data[option_base + base + 3], a);
        }
    }
    free(snapshot);
    target_var->read_count = logical_count;
    target_var->screen_width = source_var->screen_width;
    target_var->screen_height = source_var->screen_height;
    target_var->image_phys_x = source_var->image_phys_x;
    target_var->image_phys_y = source_var->image_phys_y;
    target_var->image_phys_unit = source_var->image_phys_unit;
    target_var->image_pixel_mode = source_var->image_pixel_mode;
    target_var->image_value_max = source_var->image_value_max;
    target_var->fft_width = source_var->fft_width;
    target_var->fft_height = source_var->fft_height;
    target_var->fft_original_width = source_var->fft_original_width;
    target_var->fft_original_height = source_var->fft_original_height;
    target_var->fft_is_complex = source_var->fft_is_complex;
    target_var->fft_channels = source_var->fft_channels;
    target_var->fft_restore_255 = source_var->fft_restore_255;
}

void image_apply_interlace(const char *target_name, const char *source_name, int in_place, int has_next_frame, const char *next_frame_name, const char *mode_name, double strength_value);

void image_apply_crtntsc(const char *target_name, const char *source_name, int in_place, double strength_value) {
    ArrayVariable *source_var = require_array_variable(source_name);
    double strength = is_null_number(strength_value) ? 60.0 : strength_value;
    double ntsc_strength, bleed_strength, interlace_strength, noise_strength;
    char note_buffer[256];
    int note_needed = 0;
    if (source_var->screen_width < 0 || source_var->screen_height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (strength < 0.0) { strength = 0.0; note_needed = 1; }
    if (strength > 100.0) { strength = 100.0; note_needed = 1; }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"CRTNTSC\") の strength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    ntsc_strength = strength * 0.8;
    bleed_strength = strength * 0.7;
    interlace_strength = strength * 0.6;
    noise_strength = strength * 0.5;
    if (in_place) {
        image_apply_ntsccolor(target_name, source_name, 1, ntsc_strength);
    } else {
        image_apply_ntsccolor(target_name, source_name, 0, ntsc_strength);
    }
    image_apply_colorbleed(target_name, target_name, 1, "H", bleed_strength, 2.5);
    image_apply_interlace(target_name, target_name, 1, 0, "", "MIX", interlace_strength);
    image_apply_tvnoise(target_name, target_name, 1, noise_strength, 1.0, strength * 0.5);
}


static double filter_rfvideo_sample_channel(const double *snapshot, int width, int height, int x, int y, int channel) {
    long long index;
    if (x < 0) x = 0;
    else if (x >= width) x = width - 1;
    if (y < 0) y = 0;
    else if (y >= height) y = height - 1;
    index = ((long long)y * (long long)width + (long long)x) * 4LL + (long long)channel;
    return snapshot[index];
}


void image_apply_rfvideo(const char *target_name, const char *source_name, int in_place, double strength_value, double seed_value, int has_next_frame, const char *next_frame_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var;
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int logical_count;
    long long process_count;
    double *snapshot = NULL;
    double strength = is_null_number(strength_value) ? 60.0 : strength_value;
    double strength_norm;
    double max_value;
    unsigned int seed_u32;
    char note_buffer[256];
    int note_needed = 0;
    int i;
    if (width < 0 || height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (strength < 0.0) { strength = 0.0; note_needed = 1; }
    if (strength > 100.0) { strength = 100.0; note_needed = 1; }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"RFVIDEO\") の strength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    strength_norm = strength / 100.0;
    seed_u32 = (unsigned int)((long long)llround(is_null_number(seed_value) ? 1.0 : seed_value));
    if (in_place) image_apply_ntsccolor(target_name, source_name, 1, strength * 0.9);
    else image_apply_ntsccolor(target_name, source_name, 0, strength * 0.9);
    image_apply_colorbleed(target_name, target_name, 1, "H", strength * 0.9, 1.5 + strength * 0.03);
    image_apply_interlace(target_name, target_name, 1, has_next_frame, next_frame_name != NULL ? next_frame_name : "", "MIX", strength * 0.5);
    image_apply_tvnoise(target_name, target_name, 1, strength * 0.45, is_null_number(seed_value) ? 1.0 : seed_value, 20.0 + strength * 0.35);
    target_var = require_array_variable(target_name);
    logical_count = array_logical_count(target_var);
    if (logical_count <= 0) return;
    process_count = (long long)width * (long long)height * 4LL;
    if ((long long)logical_count < process_count) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    snapshot = (double *)malloc((size_t)logical_count * sizeof(double));
    if (snapshot == NULL) runtime_error("Out of memory during FILTER RFVIDEO / 【FILTER】RFVIDEO 処理用メモリを確保できません。");
    for (i = 0; i < logical_count; ++i) snapshot[i] = b2026_var_as_number(&target_var->data[option_base + i]);
    max_value = draw_resolve_image_value_max(target_var);
    for (i = 0; i < width * height; ++i) {
        int x = i % width;
        int y = i / width;
        int base = i * 4;
        double jitter_pick = filter_tvnoise_hash01(y, x + 37, seed_u32 ^ 0x7f4a7c15u);
        double ripple_pick = filter_tvnoise_hash01(x / 4, y + 19, seed_u32 ^ 0x1b873593u);
        int max_shift = (int)floor(0.5 + 3.0 * strength_norm);
        int shift = (int)llround((jitter_pick * 2.0 - 1.0) * (double)max_shift);
        double line_gain = 1.0 + (ripple_pick * 2.0 - 1.0) * (0.08 * strength_norm);
        int sx = x + shift;
        int c;
        for (c = 0; c < 3; ++c) {
            double left1 = filter_rfvideo_sample_channel(snapshot, width, height, sx - 1, y, c);
            double center = filter_rfvideo_sample_channel(snapshot, width, height, sx, y, c);
            double right1 = filter_rfvideo_sample_channel(snapshot, width, height, sx + 1, y, c);
            double right2 = filter_rfvideo_sample_channel(snapshot, width, height, sx + 2, y, c);
            double ghost1 = filter_rfvideo_sample_channel(snapshot, width, height, sx - 3, y, c);
            double ghost2 = filter_rfvideo_sample_channel(snapshot, width, height, sx - 6, y, c);
            double blur = left1 * 0.24 + center * 0.52 + right1 * 0.24;
            double ghost = ghost1 * (0.18 * strength_norm) + ghost2 * (0.10 * strength_norm);
            double ring = (center * 2.0 - left1 - right2) * (0.06 * strength_norm);
            double value = (blur + ghost + ring) * line_gain;
            if (filter_tvnoise_hash01(x + c * 17, y, seed_u32 ^ 0x85ebca6bu) < 0.012 * strength_norm) value *= (1.0 - 0.35 * strength_norm);
            set_b2026_number(&target_var->data[option_base + base + c], value);
        }
        set_b2026_number(&target_var->data[option_base + base + 3], snapshot[base + 3]);
    }
    free(snapshot);
    target_var->read_count = logical_count;
    target_var->screen_width = source_var->screen_width;
    target_var->screen_height = source_var->screen_height;
    target_var->image_phys_x = source_var->image_phys_x;
    target_var->image_phys_y = source_var->image_phys_y;
    target_var->image_phys_unit = source_var->image_phys_unit;
    target_var->image_pixel_mode = source_var->image_pixel_mode;
    target_var->image_value_max = source_var->image_value_max;
    target_var->fft_width = source_var->fft_width;
    target_var->fft_height = source_var->fft_height;
    target_var->fft_original_width = source_var->fft_original_width;
    target_var->fft_original_height = source_var->fft_original_height;
    target_var->fft_is_complex = source_var->fft_is_complex;
    target_var->fft_channels = source_var->fft_channels;
    target_var->fft_restore_255 = source_var->fft_restore_255;
}


void image_apply_interlace(const char *target_name, const char *source_name, int in_place, int has_next_frame, const char *next_frame_name, const char *mode_name, double strength_value) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var = NULL;
    ArrayVariable *next_var = NULL;
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int logical_count = array_logical_count(source_var);
    int next_logical_count = 0;
    long long process_count;
    double strength = is_null_number(strength_value) ? 0.0 : strength_value;
    double strength_norm;
    double *source_snapshot = NULL;
    double *next_snapshot = NULL;
    int mode_kind = 0;
    int mode_recognized = 1;
    int i;
    char note_buffer[256];
    int note_needed = 0;
    if (width < 0 || height < 0) runtime_error("FILTER failed: invalid image metadata / 【FILTER】画像メタ情報が不正です。");
    if (logical_count <= 0) {
        if (target_name != NULL && strcmp(target_name, source_name) != 0) assign_array_cow(target_name, source_name);
        return;
    }
    if (width > 0 && height > 0) {
        process_count = (long long)width * (long long)height * 4LL;
        if ((long long)logical_count < process_count) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    } else {
        runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    }
    if (has_next_frame) {
        next_var = require_array_variable(next_frame_name);
        next_logical_count = array_logical_count(next_var);
        if (next_var->screen_width <= 0 || next_var->screen_height <= 0) runtime_error("FILTER(INTERLACE) nextFrame must be a pixel array / 【FILTER】INTERLACE の nextFrame にはピクセル配列を指定してください。");
        if (next_var->screen_width != width || next_var->screen_height != height) runtime_error("FILTER(INTERLACE) source and nextFrame must have the same size / 【FILTER】INTERLACE の src() と nextFrame() は同じサイズである必要があります。");
        if ((long long)next_logical_count < process_count) runtime_error("FILTER(INTERLACE) nextFrame pixel buffer too small / 【FILTER】INTERLACE の nextFrame() のピクセル数が不足しています。");
    }
    if (strength < 0.0) { strength = 0.0; note_needed = 1; }
    if (strength > 100.0) { strength = 100.0; note_needed = 1; }
    if (mode_name == NULL || *mode_name == '\0' || b2026_strcasecmp(mode_name, "MIX") == 0 || b2026_strcasecmp(mode_name, "混合") == 0) {
        mode_kind = 0;
    } else if (b2026_strcasecmp(mode_name, "ODD") == 0 || b2026_strcasecmp(mode_name, "奇数") == 0) {
        mode_kind = 1;
    } else if (b2026_strcasecmp(mode_name, "EVEN") == 0 || b2026_strcasecmp(mode_name, "偶数") == 0) {
        mode_kind = 2;
    } else {
        mode_kind = 0;
        mode_recognized = 0;
    }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"インターレース\") の strength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    if (!mode_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"インターレース\") の mode$ \"%s\" には未対応です。このため MIX 扱いで処理しました。", mode_name != NULL ? mode_name : "");
        append_basic_note_log(note_buffer);
    }
    strength_norm = strength / 100.0;
    if (strcmp(target_name, source_name) == 0) {
        ensure_array_unique(source_var);
        target_var = source_var;
    } else {
        dim_array(target_name, (double)(option_base + logical_count - 1));
        target_var = require_array_variable(target_name);
        for (i = 0; i < logical_count; ++i) set_b2026_number(&target_var->data[option_base + i], b2026_var_as_number(&source_var->data[option_base + i]));
    }
    if (strength_norm <= 0.0 && !has_next_frame) {
        target_var->read_count = logical_count;
        target_var->screen_width = source_var->screen_width;
        target_var->screen_height = source_var->screen_height;
        target_var->image_phys_x = source_var->image_phys_x;
        target_var->image_phys_y = source_var->image_phys_y;
        target_var->image_phys_unit = source_var->image_phys_unit;
        target_var->image_pixel_mode = source_var->image_pixel_mode;
        target_var->image_value_max = source_var->image_value_max;
        target_var->fft_width = source_var->fft_width;
        target_var->fft_height = source_var->fft_height;
        target_var->fft_original_width = source_var->fft_original_width;
        target_var->fft_original_height = source_var->fft_original_height;
        target_var->fft_is_complex = source_var->fft_is_complex;
        target_var->fft_channels = source_var->fft_channels;
        target_var->fft_restore_255 = source_var->fft_restore_255;
        return;
    }
    source_snapshot = (double *)malloc((size_t)logical_count * sizeof(double));
    if (source_snapshot == NULL) runtime_error("Out of memory during FILTER INTERLACE / 【FILTER】INTERLACE 処理用メモリを確保できません。");
    for (i = 0; i < logical_count; ++i) source_snapshot[i] = b2026_var_as_number(&source_var->data[option_base + i]);
    if (has_next_frame) {
        next_snapshot = (double *)malloc((size_t)next_logical_count * sizeof(double));
        if (next_snapshot == NULL) { free(source_snapshot); runtime_error("Out of memory during FILTER INTERLACE / 【FILTER】INTERLACE 処理用メモリを確保できません。"); }
        for (i = 0; i < next_logical_count; ++i) next_snapshot[i] = b2026_var_as_number(&next_var->data[option_base + i]);
    }
    for (i = 0; i < (int)process_count; i += 4) {
        int pixel_index = i / 4;
        int y = pixel_index / width;
        int odd_line = ((y % 2) == 0);
        double line_factor = 1.0;
        const double *pixels = source_snapshot;
        long long base = (long long)i;
        double r, g, b, a;
        if (has_next_frame) {
            int use_current = 1;
            if (mode_kind == 1) use_current = odd_line;
            else if (mode_kind == 2) use_current = !odd_line;
            else use_current = odd_line;
            if (mode_kind == 0) {
                line_factor = odd_line ? (1.0 - 0.18 * strength_norm) : (1.0 - 0.38 * strength_norm);
            } else {
                line_factor = use_current ? 1.0 : (1.0 - 0.45 * strength_norm);
            }
            pixels = use_current ? source_snapshot : next_snapshot;
            r = pixels[base + 0] * line_factor;
            g = pixels[base + 1] * line_factor;
            b = pixels[base + 2] * line_factor;
            a = pixels[base + 3];
        } else {
            if (mode_kind == 1) line_factor = odd_line ? 1.0 : (1.0 - 0.85 * strength_norm);
            else if (mode_kind == 2) line_factor = odd_line ? (1.0 - 0.85 * strength_norm) : 1.0;
            else line_factor = odd_line ? (1.0 - 0.20 * strength_norm) : (1.0 - 0.60 * strength_norm);
            r = source_snapshot[base + 0] * line_factor;
            g = source_snapshot[base + 1] * line_factor;
            b = source_snapshot[base + 2] * line_factor;
            a = source_snapshot[base + 3];
        }
        set_b2026_number(&target_var->data[option_base + i + 0], r);
        set_b2026_number(&target_var->data[option_base + i + 1], g);
        set_b2026_number(&target_var->data[option_base + i + 2], b);
        set_b2026_number(&target_var->data[option_base + i + 3], a);
    }
    if (next_snapshot != NULL) free(next_snapshot);
    free(source_snapshot);
    target_var->read_count = logical_count;
    target_var->screen_width = source_var->screen_width;
    target_var->screen_height = source_var->screen_height;
    target_var->image_phys_x = source_var->image_phys_x;
    target_var->image_phys_y = source_var->image_phys_y;
    target_var->image_phys_unit = source_var->image_phys_unit;
    target_var->image_pixel_mode = source_var->image_pixel_mode;
    target_var->image_value_max = source_var->image_value_max;
    target_var->fft_width = source_var->fft_width;
    target_var->fft_height = source_var->fft_height;
    target_var->fft_original_width = source_var->fft_original_width;
    target_var->fft_original_height = source_var->fft_original_height;
    target_var->fft_is_complex = source_var->fft_is_complex;
    target_var->fft_channels = source_var->fft_channels;
    target_var->fft_restore_255 = source_var->fft_restore_255;
}

