static int image_color_model_kind(const char *model_name, const char **canonical_name) {
    if (canonical_name != NULL) *canonical_name = "HSV";
    if (model_name == NULL || *model_name == '\0') return 1;
    if (b2026_strcasecmp(model_name, "HSV") == 0 || b2026_strcasecmp(model_name, "HSB") == 0) {
        if (canonical_name != NULL) *canonical_name = "HSV";
        return 1;
    }
    if (b2026_strcasecmp(model_name, "HSL") == 0) {
        if (canonical_name != NULL) *canonical_name = "HSL";
        return 2;
    }
    return 0;
}


void image_apply_color_adjust_common(const char *target_name, const char *target_hash_name, const char *source_name, int in_place, const char *operation_name, double value, int has_model, const char *model_name, int produce_image, int produce_hash, ImageColorAdjustInfo *out_info) {
    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 = source_var->image_value_max;
    const char *resolved_model = has_model ? model_name : "HSV";
    const char *canonical_model = "HUE";
    const char *canonical_op = operation_name != NULL ? operation_name : "HUE";
    int model_kind = 1;
    int i;
    image_color_adjust_info_reset(out_info);
    (void)in_place;
    if (width < 0 || height < 0) runtime_error("IMAGE failed: invalid image metadata / 【IMAGE】画像メタ情報が不正です。");
    max_value = draw_resolve_image_value_max(source_var);
    if (is_null_number(value)) {
        if (produce_image && target_name != NULL) image_assign_empty_result(target_name);
        if (produce_hash && target_hash_name != NULL) image_color_adjust_info_to_hash(target_hash_name, out_info);
        return;
    }
    if (b2026_strcasecmp(canonical_op, "HUE") == 0) {
        canonical_model = "HUE";
    } else {
        model_kind = image_color_model_kind(resolved_model, &canonical_model);
        if (model_kind == 0) {
            if (produce_image && target_name != NULL) image_assign_empty_result(target_name);
            if (produce_hash && target_hash_name != NULL) image_color_adjust_info_to_hash(target_hash_name, out_info);
            return;
        }
    }
    if (out_info != NULL) {
        out_info->valid = 1;
        out_info->operation = canonical_op;
        out_info->value = value;
        out_info->color_model = canonical_model;
        out_info->image_value_max = max_value;
        out_info->changed_r = 1;
        out_info->changed_g = 1;
        out_info->changed_b = 1;
    }
    if (produce_hash && target_hash_name != NULL) image_color_adjust_info_to_hash(target_hash_name, out_info);
    if (logical_count <= 0) {
        if (produce_image && target_name != NULL && strcmp(target_name, source_name) != 0) assign_array_cow(target_name, source_name);
        return;
    }
    if (!produce_image || target_name == NULL) return;
    if (width > 0 && height > 0) {
        process_count = (long long)width * (long long)height * 4LL;
        if ((long long)logical_count < process_count) runtime_error("IMAGE failed: pixel buffer too small / 【IMAGE】画像配列のピクセル数が不足しています。");
    } else {
        process_count = ((long long)logical_count / 4LL) * 4LL;
    }
    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]));
        }
    }
    for (i = 0; i < (int)process_count; i += 4) {
        double r = b2026_var_as_number(&target_var->data[option_base + i + 0]);
        double g = b2026_var_as_number(&target_var->data[option_base + i + 1]);
        double b = b2026_var_as_number(&target_var->data[option_base + i + 2]);
        double a = b2026_var_as_number(&target_var->data[option_base + i + 3]);
        double nr = image_unit_clamp(r / max_value);
        double ng = image_unit_clamp(g / max_value);
        double nb = image_unit_clamp(b / max_value);
        if (b2026_strcasecmp(canonical_op, "HUE") == 0) {
            double h, s, v;
            image_rgb_to_hsv(nr, ng, nb, &h, &s, &v);
            h = image_hue_wrap(h + value);
            image_hsv_to_rgb(h, s, v, &nr, &ng, &nb);
            nr = image_unit_clamp(nr);
            ng = image_unit_clamp(ng);
            nb = image_unit_clamp(nb);
        } else if (b2026_strcasecmp(canonical_op, "COLORTEMPERATURE") == 0) {
            double shift = value / 100.0;
            nr = nr + shift * 0.25;
            ng = ng + shift * 0.05;
            nb = nb - shift * 0.25;
        } else if (b2026_strcasecmp(canonical_op, "EXPOSURE") == 0) {
            double factor_value = pow(2.0, value);
            nr = nr * factor_value;
            ng = ng * factor_value;
            nb = nb * factor_value;
        } else if (b2026_strcasecmp(canonical_op, "TINT") == 0) {
            double shift = value / 100.0;
            nr = nr + shift * 0.12;
            ng = ng - shift * 0.20;
            nb = nb + shift * 0.12;
        } else if (b2026_strcasecmp(canonical_op, "HIGHLIGHT") == 0) {
            double strength = value / 100.0;
            double luminance = nr * 0.299 + ng * 0.587 + nb * 0.114;
            double weight = luminance * luminance;
            if (strength >= 0.0) {
                nr = nr + (1.0 - nr) * strength * weight;
                ng = ng + (1.0 - ng) * strength * weight;
                nb = nb + (1.0 - nb) * strength * weight;
            } else {
                nr = nr + nr * strength * weight;
                ng = ng + ng * strength * weight;
                nb = nb + nb * strength * weight;
            }
        } else if (b2026_strcasecmp(canonical_op, "SHADOW") == 0) {
            double strength = value / 100.0;
            double luminance = nr * 0.299 + ng * 0.587 + nb * 0.114;
            double inv = 1.0 - luminance;
            double weight = inv * inv;
            if (strength >= 0.0) {
                nr = nr + (1.0 - nr) * strength * weight;
                ng = ng + (1.0 - ng) * strength * weight;
                nb = nb + (1.0 - nb) * strength * weight;
            } else {
                nr = nr + nr * strength * weight;
                ng = ng + ng * strength * weight;
                nb = nb + nb * strength * weight;
            }
        } else if (b2026_strcasecmp(canonical_op, "SATURATION") == 0) {
            double factor_value = value / 100.0;
            if (model_kind == 2) {
                double h, s, l;
                image_rgb_to_hsl(nr, ng, nb, &h, &s, &l);
                if (value > 0.0) s = s + (1.0 - s) * factor_value;
                else if (value < 0.0) s = s * (1.0 + factor_value);
                s = image_unit_clamp(s);
                image_hsl_to_rgb(h, s, l, &nr, &ng, &nb);
                nr = image_unit_clamp(nr);
                ng = image_unit_clamp(ng);
                nb = image_unit_clamp(nb);
            } else {
                double h, s, v;
                image_rgb_to_hsv(nr, ng, nb, &h, &s, &v);
                if (value > 0.0) s = s + (1.0 - s) * factor_value;
                else if (value < 0.0) s = s * (1.0 + factor_value);
                s = image_unit_clamp(s);
                image_hsv_to_rgb(h, s, v, &nr, &ng, &nb);
                nr = image_unit_clamp(nr);
                ng = image_unit_clamp(ng);
                nb = image_unit_clamp(nb);
            }
        } else {
            double factor_value = value / 100.0;
            if (model_kind == 2) {
                double h, s, l;
                image_rgb_to_hsl(nr, ng, nb, &h, &s, &l);
                if (value > 0.0) l = l + (1.0 - l) * factor_value;
                else if (value < 0.0) l = l * (1.0 + factor_value);
                l = image_unit_clamp(l);
                image_hsl_to_rgb(h, s, l, &nr, &ng, &nb);
                nr = image_unit_clamp(nr);
                ng = image_unit_clamp(ng);
                nb = image_unit_clamp(nb);
            } else {
                double h, s, v;
                image_rgb_to_hsv(nr, ng, nb, &h, &s, &v);
                if (value > 0.0) v = v + (1.0 - v) * factor_value;
                else if (value < 0.0) v = v * (1.0 + factor_value);
                v = image_unit_clamp(v);
                image_hsv_to_rgb(h, s, v, &nr, &ng, &nb);
                nr = image_unit_clamp(nr);
                ng = image_unit_clamp(ng);
                nb = image_unit_clamp(nb);
            }
        }
        set_b2026_number(&target_var->data[option_base + i + 0], nr * max_value);
        set_b2026_number(&target_var->data[option_base + i + 1], ng * max_value);
        set_b2026_number(&target_var->data[option_base + i + 2], nb * max_value);
        set_b2026_number(&target_var->data[option_base + i + 3], a);
    }
    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_saturation(const char *target_name, const char *source_name, int in_place, int has_model, const char *model_name, double value);
void image_apply_colortemperature(const char *target_name, const char *source_name, int in_place, double value);
void image_apply_exposure(const char *target_name, const char *source_name, int in_place, double value);
void image_apply_tint(const char *target_name, const char *source_name, int in_place, double value);
void image_apply_highlight(const char *target_name, const char *source_name, int in_place, double value);
void image_apply_shadow(const char *target_name, const char *source_name, int in_place, double value);

void image_apply_cinemalook(const char *target_name, const char *source_name, int in_place, double strength_value, double grain_flag_value, double sat_strength_value, const char *preset_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    double strength = is_null_number(strength_value) ? 60.0 : strength_value;
    double sat_strength = is_null_number(sat_strength_value) ? -20.0 : sat_strength_value;
    int grain_enabled = (!is_null_number(grain_flag_value) && grain_flag_value != 0.0) ? 1 : 0;
    double exposure_value;
    double contrast_value;
    double highlight_value;
    double shadow_value;
    double temp_value;
    double tint_value;
    const char *preset_canonical = "CLASSIC";
    char note_buffer[256];
    int note_needed = 0;
    int preset_recognized = 1;
    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 (sat_strength < -100.0) { sat_strength = -100.0; note_needed = 1; }
    if (sat_strength > 100.0) { sat_strength = 100.0; note_needed = 1; }
    if (preset_name == NULL || *preset_name == '\0' || b2026_strcasecmp(preset_name, "CLASSIC") == 0 || b2026_strcasecmp(preset_name, "標準") == 0) preset_canonical = "CLASSIC";
    else if (b2026_strcasecmp(preset_name, "WARM") == 0 || b2026_strcasecmp(preset_name, "暖色") == 0) preset_canonical = "WARM";
    else if (b2026_strcasecmp(preset_name, "COOL") == 0 || b2026_strcasecmp(preset_name, "寒色") == 0) preset_canonical = "COOL";
    else if (b2026_strcasecmp(preset_name, "DARK") == 0 || b2026_strcasecmp(preset_name, "ダーク") == 0) preset_canonical = "DARK";
    else if (b2026_strcasecmp(preset_name, "SOFT") == 0 || b2026_strcasecmp(preset_name, "ソフト") == 0) preset_canonical = "SOFT";
    else if (b2026_strcasecmp(preset_name, "MONO") == 0 || b2026_strcasecmp(preset_name, "モノクロ") == 0) preset_canonical = "MONO";
    else if (b2026_strcasecmp(preset_name, "VINTAGE") == 0 || b2026_strcasecmp(preset_name, "ヴィンテージ") == 0) preset_canonical = "VINTAGE";
    else { preset_canonical = "CLASSIC"; preset_recognized = 0; }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"CINEMALOOK\") の strength は 0〜100、satStrength は -100〜100 を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    if (!preset_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"CINEMALOOK\") の preset$ \"%s\" には未対応です。このため CLASSIC 扱いで処理しました。", preset_name != NULL ? preset_name : "");
        append_basic_note_log(note_buffer);
    }
    if (b2026_strcasecmp(preset_canonical, "WARM") == 0) {
        exposure_value = -0.18 * (strength / 100.0);
        contrast_value = 8.0 + strength * 0.14;
        highlight_value = -(8.0 + strength * 0.18);
        shadow_value = 4.0 + strength * 0.10;
        temp_value = 6.0 + strength * 0.10;
        tint_value = 1.5 + strength * 0.02;
    } else if (b2026_strcasecmp(preset_canonical, "COOL") == 0) {
        exposure_value = -0.28 * (strength / 100.0);
        contrast_value = 10.0 + strength * 0.16;
        highlight_value = -(10.0 + strength * 0.24);
        shadow_value = 5.0 + strength * 0.11;
        temp_value = -(8.0 + strength * 0.12);
        tint_value = 2.5 + strength * 0.03;
    } else if (b2026_strcasecmp(preset_canonical, "DARK") == 0) {
        exposure_value = -0.48 * (strength / 100.0);
        contrast_value = 14.0 + strength * 0.22;
        highlight_value = -(16.0 + strength * 0.30);
        shadow_value = 2.0 + strength * 0.06;
        temp_value = -(5.0 + strength * 0.08);
        tint_value = 3.5 + strength * 0.05;
    } else if (b2026_strcasecmp(preset_canonical, "SOFT") == 0) {
        exposure_value = -0.12 * (strength / 100.0);
        contrast_value = 3.0 + strength * 0.08;
        highlight_value = -(6.0 + strength * 0.12);
        shadow_value = 6.0 + strength * 0.10;
        temp_value = -(2.0 + strength * 0.04);
        tint_value = 1.0 + strength * 0.01;
    } else if (b2026_strcasecmp(preset_canonical, "MONO") == 0) {
        exposure_value = -0.16 * (strength / 100.0);
        contrast_value = 12.0 + strength * 0.16;
        highlight_value = -(10.0 + strength * 0.18);
        shadow_value = 5.0 + strength * 0.08;
        temp_value = 0.0;
        tint_value = 0.0;
        if (sat_strength > -100.0) sat_strength = -100.0;
    } else if (b2026_strcasecmp(preset_canonical, "VINTAGE") == 0) {
        exposure_value = -0.10 * (strength / 100.0);
        contrast_value = 5.0 + strength * 0.09;
        highlight_value = -(7.0 + strength * 0.13);
        shadow_value = 8.0 + strength * 0.09;
        temp_value = 8.0 + strength * 0.09;
        tint_value = 1.2 + strength * 0.01;
        if (sat_strength > -35.0) sat_strength = -35.0;
    } else {
        exposure_value = -0.35 * (strength / 100.0);
        contrast_value = 10.0 + strength * 0.18;
        highlight_value = -(12.0 + strength * 0.28);
        shadow_value = 5.0 + strength * 0.12;
        temp_value = -(4.0 + strength * 0.10);
        tint_value = 3.0 + strength * 0.04;
    }
    if (in_place) image_apply_exposure(target_name, source_name, 1, exposure_value);
    else image_apply_exposure(target_name, source_name, 0, exposure_value);
    image_apply_contrast(target_name, target_name, 1, 0, NULL, contrast_value, 0, 0.0);
    image_apply_highlight(target_name, target_name, 1, highlight_value);
    image_apply_shadow(target_name, target_name, 1, shadow_value);
    image_apply_colortemperature(target_name, target_name, 1, temp_value);
    image_apply_tint(target_name, target_name, 1, tint_value);
    image_apply_saturation(target_name, target_name, 1, 0, NULL, sat_strength);
    if (grain_enabled) {
        double grain_strength = 6.0 + strength * 0.12;
        double grain_density = 18.0 + strength * 0.20;
        image_apply_tvnoise(target_name, target_name, 1, grain_strength, 1.0, grain_density);
    }
}


void image_apply_hue(const char *target_name, const char *source_name, int in_place, double angle) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "HUE", angle, 0, NULL, 1, 0, NULL);
}


void image_apply_saturation(const char *target_name, const char *source_name, int in_place, int has_model, const char *model_name, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "SATURATION", value, has_model, model_name, 1, 0, NULL);
}


void image_apply_brightness(const char *target_name, const char *source_name, int in_place, int has_model, const char *model_name, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "BRIGHTNESS", value, has_model, model_name, 1, 0, NULL);
}


void image_apply_colortemperature(const char *target_name, const char *source_name, int in_place, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "COLORTEMPERATURE", value, 0, NULL, 1, 0, NULL);
}


void image_apply_exposure(const char *target_name, const char *source_name, int in_place, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "EXPOSURE", value, 0, NULL, 1, 0, NULL);
}


void image_apply_tint(const char *target_name, const char *source_name, int in_place, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "TINT", value, 0, NULL, 1, 0, NULL);
}


void image_apply_highlight(const char *target_name, const char *source_name, int in_place, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "HIGHLIGHT", value, 0, NULL, 1, 0, NULL);
}


void image_apply_shadow(const char *target_name, const char *source_name, int in_place, double value) {
    image_apply_color_adjust_common(target_name, NULL, source_name, in_place, "SHADOW", value, 0, NULL, 1, 0, NULL);
}


void image_apply_solarize(const char *target_name, const char *source_name, int in_place, double threshold_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 threshold;
    int i;
    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等でピクセル配列に変換してください。");
    }
    threshold = is_null_number(threshold_value) ? (max_value / 2.0) : threshold_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]));
        }
    }
    for (i = 0; i < (int)process_count; i += 4) {
        double r = b2026_var_as_number(&target_var->data[option_base + i + 0]);
        double g = b2026_var_as_number(&target_var->data[option_base + i + 1]);
        double b = b2026_var_as_number(&target_var->data[option_base + i + 2]);
        double a = b2026_var_as_number(&target_var->data[option_base + i + 3]);
        if (r > threshold) r = max_value - r;
        if (g > threshold) g = max_value - g;
        if (b > threshold) b = max_value - b;
        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);
    }
    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_ntsccolor(const char *target_name, const char *source_name, int in_place, double strength_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 strength_norm;
    double *snapshot = NULL;
    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 (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(\"NTSCカラー\") の strength は 0〜100 を指定してください。このため範囲内に丸めて処理しました。");
        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) {
        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 NTSCCOLOR / 【FILTER】NTSCカラー処理用メモリを確保できません。");
    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;
        int left_x = (x > 0) ? (x - 1) : x;
        int right_x = (x + 1 < width) ? (x + 1) : x;
        long long cur_base = ((long long)y * (long long)width + (long long)x) * 4LL;
        long long left_base = ((long long)y * (long long)width + (long long)left_x) * 4LL;
        long long right_base = ((long long)y * (long long)width + (long long)right_x) * 4LL;
        double r = image_unit_clamp(snapshot[cur_base + 0] / max_value);
        double g = image_unit_clamp(snapshot[cur_base + 1] / max_value);
        double b = image_unit_clamp(snapshot[cur_base + 2] / max_value);
        double a = snapshot[cur_base + 3];
        double lr = image_unit_clamp(snapshot[left_base + 0] / max_value);
        double lg = image_unit_clamp(snapshot[left_base + 1] / max_value);
        double lb = image_unit_clamp(snapshot[left_base + 2] / max_value);
        double rr = image_unit_clamp(snapshot[right_base + 0] / max_value);
        double rg = image_unit_clamp(snapshot[right_base + 1] / max_value);
        double rb = image_unit_clamp(snapshot[right_base + 2] / max_value);
        double yv, iv, qv, yl, il, ql, yr, ir, qr;
        double bleed_i, bleed_q, bleed_y, nr, ng, nb;
        image_rgb_to_yiq(r, g, b, &yv, &iv, &qv);
        image_rgb_to_yiq(lr, lg, lb, &yl, &il, &ql);
        image_rgb_to_yiq(rr, rg, rb, &yr, &ir, &qr);
        bleed_y = yv * (1.0 - 0.06 * strength_norm) + ((yl + yr) * 0.5) * (0.06 * strength_norm);
        bleed_i = iv * (1.0 - 0.55 * strength_norm) + ((il + ir) * 0.5) * (0.55 * strength_norm);
        bleed_q = qv * (1.0 - 0.70 * strength_norm) + ((ql + qr) * 0.5) * (0.70 * strength_norm);
        bleed_i *= (1.0 - 0.35 * strength_norm);
        bleed_q *= (1.0 - 0.40 * strength_norm);
        image_yiq_to_rgb(bleed_y, bleed_i, bleed_q, &nr, &ng, &nb);
        set_b2026_number(&target_var->data[option_base + i + 0], nr * max_value);
        set_b2026_number(&target_var->data[option_base + i + 1], ng * max_value);
        set_b2026_number(&target_var->data[option_base + i + 2], nb * max_value);
        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_colorbleed(const char *target_name, const char *source_name, int in_place, const char *dir_name, double strength_value, double range_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 range = is_null_number(range_value) ? 0.0 : range_value;
    double strength_norm;
    double radius;
    double *snapshot = NULL;
    int mode_kind = 1;
    int dir_recognized = 1;
    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 (range < 0.0) { range = 0.0; note_needed = 1; }
    if (dir_name == NULL || *dir_name == '\0' || b2026_strcasecmp(dir_name, "H") == 0 || b2026_strcasecmp(dir_name, "HORIZONTAL") == 0 || b2026_strcasecmp(dir_name, "横") == 0) {
        mode_kind = 1;
    } else if (b2026_strcasecmp(dir_name, "V") == 0 || b2026_strcasecmp(dir_name, "VERTICAL") == 0 || b2026_strcasecmp(dir_name, "縦") == 0) {
        mode_kind = 2;
    } else if (b2026_strcasecmp(dir_name, "HV") == 0 || b2026_strcasecmp(dir_name, "BOTH") == 0 || b2026_strcasecmp(dir_name, "両方") == 0) {
        mode_kind = 3;
    } else {
        mode_kind = 1;
        dir_recognized = 0;
    }
    if (note_needed) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"COLORBLEED\") の strength は 0〜100、range は 0 以上を指定してください。このため範囲内に丸めて処理しました。");
        append_basic_note_log(note_buffer);
    }
    if (!dir_recognized) {
        snprintf(note_buffer, sizeof(note_buffer), "[NOTE] FILTER(\"COLORBLEED\") の dir$ \"%s\" には未対応です。このため H 扱いで処理しました。", dir_name != NULL ? dir_name : "");
        append_basic_note_log(note_buffer);
    }
    strength_norm = strength / 100.0;
    radius = range;
    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 || radius <= 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 COLORBLEED / 【FILTER】COLORBLEED 処理用メモリを確保できません。");
    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 = image_unit_clamp(snapshot[base + 0] / max_value);
        double g = image_unit_clamp(snapshot[base + 1] / max_value);
        double b = image_unit_clamp(snapshot[base + 2] / max_value);
        double a = snapshot[base + 3];
        double yv, iv, qv;
        double blur_i = 0.0, blur_q = 0.0;
        double weight_sum = 0.0;
        double nr, ng, nb;
        int step, int_radius = (int)floor(radius);
        double frac = radius - (double)int_radius;
        image_rgb_to_yiq(r, g, b, &yv, &iv, &qv);
        blur_i += iv;
        blur_q += qv;
        weight_sum += 1.0;
        for (step = 1; step <= int_radius + ((frac > 1e-12) ? 1 : 0); ++step) {
            double step_weight = 1.0 - (((double)step - 1.0) / (radius + 1.0));
            double use_weight = step_weight;
            if (step > int_radius) use_weight *= frac;
            if (use_weight <= 0.0) continue;
            if (mode_kind == 1 || mode_kind == 3) {
                int x1 = x - step; if (x1 < 0) x1 = 0;
                int x2 = x + step; if (x2 >= width) x2 = width - 1;
                long long b1 = ((long long)y * (long long)width + (long long)x1) * 4LL;
                long long b2 = ((long long)y * (long long)width + (long long)x2) * 4LL;
                double ry, ri, rq;
                image_rgb_to_yiq(image_unit_clamp(snapshot[b1 + 0] / max_value), image_unit_clamp(snapshot[b1 + 1] / max_value), image_unit_clamp(snapshot[b1 + 2] / max_value), &ry, &ri, &rq);
                blur_i += ri * use_weight * 0.5;
                blur_q += rq * use_weight * 0.5;
                weight_sum += use_weight * 0.5;
                image_rgb_to_yiq(image_unit_clamp(snapshot[b2 + 0] / max_value), image_unit_clamp(snapshot[b2 + 1] / max_value), image_unit_clamp(snapshot[b2 + 2] / max_value), &ry, &ri, &rq);
                blur_i += ri * use_weight * 0.5;
                blur_q += rq * use_weight * 0.5;
                weight_sum += use_weight * 0.5;
            }
            if (mode_kind == 2 || mode_kind == 3) {
                int y1 = y - step; if (y1 < 0) y1 = 0;
                int y2 = y + step; if (y2 >= height) y2 = height - 1;
                long long b1 = ((long long)y1 * (long long)width + (long long)x) * 4LL;
                long long b2 = ((long long)y2 * (long long)width + (long long)x) * 4LL;
                double ry, ri, rq;
                image_rgb_to_yiq(image_unit_clamp(snapshot[b1 + 0] / max_value), image_unit_clamp(snapshot[b1 + 1] / max_value), image_unit_clamp(snapshot[b1 + 2] / max_value), &ry, &ri, &rq);
                blur_i += ri * use_weight * 0.5;
                blur_q += rq * use_weight * 0.5;
                weight_sum += use_weight * 0.5;
                image_rgb_to_yiq(image_unit_clamp(snapshot[b2 + 0] / max_value), image_unit_clamp(snapshot[b2 + 1] / max_value), image_unit_clamp(snapshot[b2 + 2] / max_value), &ry, &ri, &rq);
                blur_i += ri * use_weight * 0.5;
                blur_q += rq * use_weight * 0.5;
                weight_sum += use_weight * 0.5;
            }
        }
        if (weight_sum > 0.0) {
            blur_i /= weight_sum;
            blur_q /= weight_sum;
        } else {
            blur_i = iv;
            blur_q = qv;
        }
        blur_i = iv * (1.0 - strength_norm) + blur_i * strength_norm;
        blur_q = qv * (1.0 - strength_norm) + blur_q * strength_norm;
        blur_i *= (1.0 - 0.12 * strength_norm);
        blur_q *= (1.0 - 0.20 * strength_norm);
        image_yiq_to_rgb(yv, blur_i, blur_q, &nr, &ng, &nb);
        set_b2026_number(&target_var->data[option_base + i + 0], nr * max_value);
        set_b2026_number(&target_var->data[option_base + i + 1], ng * max_value);
        set_b2026_number(&target_var->data[option_base + i + 2], nb * max_value);
        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;
}

