static void image_split_info_reset(ImageSplitInfo *info) {
    if (info == NULL) return;
    memset(info, 0, sizeof(*info));
    info->color_type = "RGBA";
    info->channel = "R";
    info->image_value_max = 0.0;
    info->pixel_mode = 1;
    info->resolution = 72.0;
}

static void image_merge_info_reset(ImageMergeInfo *info) {
    if (info == NULL) return;
    memset(info, 0, sizeof(*info));
    info->color_type = "RGBA";
    info->image_value_max = 0.0;
    info->pixel_mode = 1;
    info->resolution = 72.0;
}

static double image_resolution_from_phys(int phys_value, int phys_unit) {
    if (phys_value <= 0) return 72.0;
    if (phys_unit == 1) return ((double)phys_value) * 0.0254;
    return (double)phys_value;
}

static int image_phys_from_resolution(double resolution) {
    if (!(resolution > 0.0)) return 0;
    return (int)llround(resolution / 0.0254);
}

static int image_split_channel_kind(const char *color_type_name, const char *channel_name, const char **normalized_color_type, const char **normalized_channel, int *channel_offset) {
    const char *canonical_color_type = "RGBA";
    const char *canonical_channel = "R";
    if (channel_offset != NULL) *channel_offset = 0;
    if (color_type_name != NULL && *color_type_name != '\0') {
        if (b2026_strcasecmp(color_type_name, "HSV") == 0 || b2026_strcasecmp(color_type_name, "HSB") == 0) canonical_color_type = "HSV";
        else if (b2026_strcasecmp(color_type_name, "HSL") == 0) canonical_color_type = "HSL";
        else if (b2026_strcasecmp(color_type_name, "YCBCR") == 0 || strcmp(color_type_name, "YCbCr") == 0 || strcmp(color_type_name, "YCbCr") == 0) canonical_color_type = "YCbCr";
        else if (b2026_strcasecmp(color_type_name, "YUV") == 0) canonical_color_type = "YUV";
        else if (b2026_strcasecmp(color_type_name, "YIQ") == 0) canonical_color_type = "YIQ";
        else if (b2026_strcasecmp(color_type_name, "CMYK") == 0) canonical_color_type = "CMYK";
        else if (b2026_strcasecmp(color_type_name, "XYZ") == 0) canonical_color_type = "XYZ";
        else if (b2026_strcasecmp(color_type_name, "LCH") == 0 || strcmp(color_type_name, "Lch") == 0) canonical_color_type = "Lch";
        else if (b2026_strcasecmp(color_type_name, "LAB") == 0 || strcmp(color_type_name, "Lab") == 0) canonical_color_type = "Lab";
        else if (b2026_strcasecmp(color_type_name, "RGBA") == 0) canonical_color_type = "RGBA";
        else return 0;
    }
    if (normalized_color_type != NULL) *normalized_color_type = canonical_color_type;
    if (strcmp(canonical_color_type, "RGBA") == 0) {
        if (channel_name == NULL || *channel_name == '\0') {
            if (normalized_channel != NULL) *normalized_channel = canonical_channel;
            return 1;
        }
        if (b2026_strcasecmp(channel_name, "R") == 0 || b2026_strcasecmp(channel_name, "RED") == 0 || strcmp(channel_name, "赤") == 0) { canonical_channel = "R"; if (channel_offset) *channel_offset = 0; if (normalized_channel) *normalized_channel = canonical_channel; return 1; }
        if (b2026_strcasecmp(channel_name, "G") == 0 || b2026_strcasecmp(channel_name, "GREEN") == 0 || strcmp(channel_name, "緑") == 0) { canonical_channel = "G"; if (channel_offset) *channel_offset = 1; if (normalized_channel) *normalized_channel = canonical_channel; return 2; }
        if (b2026_strcasecmp(channel_name, "B") == 0 || b2026_strcasecmp(channel_name, "BLUE") == 0 || strcmp(channel_name, "青") == 0) { canonical_channel = "B"; if (channel_offset) *channel_offset = 2; if (normalized_channel) *normalized_channel = canonical_channel; return 3; }
        if (b2026_strcasecmp(channel_name, "A") == 0 || b2026_strcasecmp(channel_name, "ALPHA") == 0 || strcmp(channel_name, "α") == 0 || strcmp(channel_name, "アルファ") == 0) { canonical_channel = "A"; if (channel_offset) *channel_offset = 3; if (normalized_channel) *normalized_channel = canonical_channel; return 4; }
        return 0;
    }
    if (strcmp(canonical_color_type, "HSV") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "H";
        else if (b2026_strcasecmp(channel_name, "H") == 0 || strcmp(channel_name, "色相") == 0) canonical_channel = "H";
        else if (b2026_strcasecmp(channel_name, "S") == 0 || strcmp(channel_name, "彩度") == 0) canonical_channel = "S";
        else if (b2026_strcasecmp(channel_name, "V") == 0 || b2026_strcasecmp(channel_name, "B") == 0 || strcmp(channel_name, "明度") == 0 || strcmp(channel_name, "明るさ") == 0) canonical_channel = "V";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "H") == 0) return 101;
        if (strcmp(canonical_channel, "S") == 0) return 102;
        return 103;
    }
    if (strcmp(canonical_color_type, "YCbCr") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "Y") == 0 || strcmp(channel_name, "輝度") == 0 || strcmp(channel_name, "明るさ") == 0) canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "CB") == 0 || strcmp(channel_name, "青差") == 0) canonical_channel = "Cb";
        else if (b2026_strcasecmp(channel_name, "CR") == 0 || strcmp(channel_name, "赤差") == 0) canonical_channel = "Cr";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "Y") == 0) return 301;
        if (strcmp(canonical_channel, "Cb") == 0) return 302;
        return 303;
    }
    if (strcmp(canonical_color_type, "YUV") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "Y") == 0 || strcmp(channel_name, "輝度") == 0 || strcmp(channel_name, "明るさ") == 0) canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "U") == 0 || strcmp(channel_name, "青差") == 0) canonical_channel = "U";
        else if (b2026_strcasecmp(channel_name, "V") == 0 || strcmp(channel_name, "赤差") == 0) canonical_channel = "V";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "Y") == 0) return 311;
        if (strcmp(canonical_channel, "U") == 0) return 312;
        return 313;
    }
    if (strcmp(canonical_color_type, "YIQ") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "Y") == 0 || strcmp(channel_name, "輝度") == 0 || strcmp(channel_name, "明るさ") == 0) canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "I") == 0 || strcmp(channel_name, "同相") == 0) canonical_channel = "I";
        else if (b2026_strcasecmp(channel_name, "Q") == 0 || strcmp(channel_name, "直交") == 0) canonical_channel = "Q";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "Y") == 0) return 321;
        if (strcmp(canonical_channel, "I") == 0) return 322;
        return 323;
    }
    if (strcmp(canonical_color_type, "CMYK") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "C";
        else if (b2026_strcasecmp(channel_name, "C") == 0 || strcmp(channel_name, "シアン") == 0) canonical_channel = "C";
        else if (b2026_strcasecmp(channel_name, "M") == 0 || strcmp(channel_name, "マゼンタ") == 0) canonical_channel = "M";
        else if (b2026_strcasecmp(channel_name, "Y") == 0 || strcmp(channel_name, "イエロー") == 0 || strcmp(channel_name, "黄") == 0) canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "K") == 0 || strcmp(channel_name, "ブラック") == 0 || strcmp(channel_name, "黒") == 0 || strcmp(channel_name, "墨") == 0) canonical_channel = "K";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "C") == 0) return 401;
        if (strcmp(canonical_channel, "M") == 0) return 402;
        if (strcmp(canonical_channel, "Y") == 0) return 403;
        return 404;
    }
    if (strcmp(canonical_color_type, "XYZ") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "X";
        else if (b2026_strcasecmp(channel_name, "X") == 0 || strcmp(channel_name, "X値") == 0) canonical_channel = "X";
        else if (b2026_strcasecmp(channel_name, "Y") == 0 || strcmp(channel_name, "Y値") == 0) canonical_channel = "Y";
        else if (b2026_strcasecmp(channel_name, "Z") == 0 || strcmp(channel_name, "Z値") == 0) canonical_channel = "Z";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "X") == 0) return 601;
        if (strcmp(canonical_channel, "Y") == 0) return 602;
        return 603;
    }
    if (strcmp(canonical_color_type, "Lch") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "L";
        else if (b2026_strcasecmp(channel_name, "L") == 0 || strcmp(channel_name, "明度") == 0 || strcmp(channel_name, "L値") == 0) canonical_channel = "L";
        else if (b2026_strcasecmp(channel_name, "C") == 0 || b2026_strcasecmp(channel_name, "CHROMA") == 0 || strcmp(channel_name, "クロマ") == 0 || strcmp(channel_name, "彩度") == 0) canonical_channel = "C";
        else if (b2026_strcasecmp(channel_name, "H") == 0 || b2026_strcasecmp(channel_name, "HUE") == 0 || strcmp(channel_name, "色相") == 0) canonical_channel = "H";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "L") == 0) return 701;
        if (strcmp(canonical_channel, "C") == 0) return 702;
        return 703;
    }
    if (strcmp(canonical_color_type, "Lab") == 0) {
        if (channel_name == NULL || *channel_name == '\0') canonical_channel = "L";
        else if (b2026_strcasecmp(channel_name, "L") == 0 || strcmp(channel_name, "明度") == 0 || strcmp(channel_name, "L値") == 0) canonical_channel = "L";
        else if (b2026_strcasecmp(channel_name, "A") == 0 || strcmp(channel_name, "赤緑") == 0 || strcmp(channel_name, "a") == 0) canonical_channel = "A";
        else if (b2026_strcasecmp(channel_name, "B") == 0 || strcmp(channel_name, "黄青") == 0 || strcmp(channel_name, "b") == 0) canonical_channel = "B";
        else return 0;
        if (normalized_channel != NULL) *normalized_channel = canonical_channel;
        if (strcmp(canonical_channel, "L") == 0) return 501;
        if (strcmp(canonical_channel, "A") == 0) return 502;
        return 503;
    }
    if (channel_name == NULL || *channel_name == '\0') canonical_channel = "H";
    else if (b2026_strcasecmp(channel_name, "H") == 0 || strcmp(channel_name, "色相") == 0) canonical_channel = "H";
    else if (b2026_strcasecmp(channel_name, "S") == 0 || strcmp(channel_name, "彩度") == 0) canonical_channel = "S";
    else if (b2026_strcasecmp(channel_name, "L") == 0 || strcmp(channel_name, "輝度") == 0) canonical_channel = "L";
    else return 0;
    if (normalized_channel != NULL) *normalized_channel = canonical_channel;
    if (strcmp(canonical_channel, "H") == 0) return 201;
    if (strcmp(canonical_channel, "S") == 0) return 202;
    return 203;
}

static int image_merge_color_type_kind(const char *name, const char **normalized_name, int *channel_count) {
    if (normalized_name != NULL) *normalized_name = "RGBA";
    if (channel_count != NULL) *channel_count = 4;
    if (name == NULL || *name == '\0') return 0;
    if (b2026_strcasecmp(name, "RGB") == 0) { if (normalized_name) *normalized_name = "RGB"; if (channel_count) *channel_count = 3; return 1; }
    if (b2026_strcasecmp(name, "RGBA") == 0) { if (normalized_name) *normalized_name = "RGBA"; if (channel_count) *channel_count = 4; return 2; }
    if (b2026_strcasecmp(name, "HSV") == 0 || b2026_strcasecmp(name, "HSB") == 0) { if (normalized_name) *normalized_name = "HSV"; if (channel_count) *channel_count = 3; return 3; }
    if (b2026_strcasecmp(name, "HSL") == 0) { if (normalized_name) *normalized_name = "HSL"; if (channel_count) *channel_count = 3; return 4; }
    if (b2026_strcasecmp(name, "YCBCR") == 0 || strcmp(name, "YCbCr") == 0 || strcmp(name, "YCbCr") == 0 || strcmp(name, "YCbCr") == 0) { if (normalized_name) *normalized_name = "YCbCr"; if (channel_count) *channel_count = 3; return 5; }
    if (b2026_strcasecmp(name, "YUV") == 0) { if (normalized_name) *normalized_name = "YUV"; if (channel_count) *channel_count = 3; return 6; }
    if (b2026_strcasecmp(name, "YIQ") == 0) { if (normalized_name) *normalized_name = "YIQ"; if (channel_count) *channel_count = 3; return 11; }
    if (b2026_strcasecmp(name, "CMYK") == 0) { if (normalized_name) *normalized_name = "CMYK"; if (channel_count) *channel_count = 4; return 7; }
    if (b2026_strcasecmp(name, "XYZ") == 0) { if (normalized_name) *normalized_name = "XYZ"; if (channel_count) *channel_count = 3; return 8; }
    if (b2026_strcasecmp(name, "LCH") == 0 || strcmp(name, "Lch") == 0) { if (normalized_name) *normalized_name = "Lch"; if (channel_count) *channel_count = 3; return 9; }
    if (b2026_strcasecmp(name, "LAB") == 0 || strcmp(name, "Lab") == 0) { if (normalized_name) *normalized_name = "Lab"; if (channel_count) *channel_count = 3; return 10; }
    return 0;
}

static void image_clear_hash_output(const char *name) {
    Frame *frame;
    HashVariable *hash_var;
    if (name == NULL) return;
    frame = get_hash_frame(name);
    hash_var = ensure_local_hash(frame, name);
    clear_hash_entries(hash_var);
}

static void image_prepare_plain_numeric_array(const char *target_name, long long logical_count) {
    ArrayVariable *target_var;
    if (logical_count <= 0) {
        dim_array(target_name, (double)(option_base - 1));
        target_var = require_array_variable(target_name);
        target_var->read_count = 0;
    } else {
        dim_array(target_name, (double)(option_base + (int)logical_count - 1));
        target_var = require_array_variable(target_name);
        target_var->read_count = (int)logical_count;
    }
    target_var->screen_width = 0;
    target_var->screen_height = 0;
    target_var->image_phys_x = 0;
    target_var->image_phys_y = 0;
    target_var->image_phys_unit = 0;
    target_var->image_pixel_mode = 0;
    target_var->image_value_max = 0.0;
    target_var->fft_width = 0;
    target_var->fft_height = 0;
    target_var->fft_original_width = 0;
    target_var->fft_original_height = 0;
    target_var->fft_is_complex = 0;
    target_var->fft_channels = 0;
    target_var->fft_restore_255 = 0;
}

void image_split_info_to_hash(const char *name, const ImageSplitInfo *info) {
    Frame *frame = get_hash_frame(name);
    HashVariable *hash_var = ensure_local_hash(frame, name);
    clear_hash_entries(hash_var);
    if (info == NULL || !info->valid) return;
    hash_set_number(name, "width", (double)info->width);
    hash_set_number(name, "height", (double)info->height);
    hash_set_string(name, "colorType", info->color_type != NULL ? info->color_type : "RGBA");
    hash_set_string(name, "channel", info->channel != NULL ? info->channel : "R");
    hash_set_number(name, "imageValueMax", info->image_value_max);
    hash_set_number(name, "pixelMode", (double)info->pixel_mode);
    hash_set_number(name, "resolution", info->resolution);
    hash_set_number(name, "sampleCount", (double)info->sample_count);
}

void image_merge_info_to_hash(const char *name, const ImageMergeInfo *info) {
    Frame *frame = get_hash_frame(name);
    HashVariable *hash_var = ensure_local_hash(frame, name);
    clear_hash_entries(hash_var);
    if (info == NULL || !info->valid) return;
    hash_set_number(name, "width", (double)info->width);
    hash_set_number(name, "height", (double)info->height);
    hash_set_string(name, "colorType", info->color_type != NULL ? info->color_type : "RGBA");
    hash_set_number(name, "imageValueMax", info->image_value_max);
    hash_set_number(name, "pixelMode", (double)info->pixel_mode);
    hash_set_number(name, "resolution", info->resolution);
    hash_set_number(name, "sampleCount", (double)info->sample_count);
    hash_set_number(name, "usedR", (double)info->used_r);
    hash_set_number(name, "usedG", (double)info->used_g);
    hash_set_number(name, "usedB", (double)info->used_b);
    hash_set_number(name, "usedA", (double)info->used_a);
}

static double image_merge_hash_number_with_default(const char *hash_name, const char *camel_key, const char *snake_key, double default_value) {
    double value;
    if (hash_name == NULL) return default_value;
    value = hash_get_number(hash_name, camel_key);
    if (!is_null_number(value)) return value;
    value = hash_get_number(hash_name, snake_key);
    if (!is_null_number(value)) return value;
    return default_value;
}

static int image_merge_hash_int_with_default(const char *hash_name, const char *camel_key, const char *snake_key, int default_value) {
    double value = image_merge_hash_number_with_default(hash_name, camel_key, snake_key, null_value());
    if (is_null_number(value)) return default_value;
    return (int)llround(value);
}

static double image_merge_channel_value(const char *array_name, int has_array, long long logical_index, double default_value) {
    ArrayVariable *array_var;
    int logical_count;
    B2026_Var *slot;
    if (!has_array || array_name == NULL) return default_value;
    array_var = require_array_variable(array_name);
    logical_count = array_logical_count(array_var);
    if (logical_index < 0 || logical_index >= (long long)logical_count) return default_value;
    slot = &array_var->data[option_base + (int)logical_index];
    if (b2026_var_is_null(slot)) return default_value;
    return b2026_var_as_number(slot);
}

