typedef struct B2026_QuantColor {
    double r;
    double g;
    double b;
} B2026_QuantColor;

static void filter_quantize_copy_canvas_meta(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count) {
    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 = 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;
}

static ArrayVariable *filter_quantize_prepare_target(const char *target_name, ArrayVariable *source_var, int in_place, int logical_count) {
    ArrayVariable *target_var;
    int i;
    if (in_place) {
        target_var = source_var;
        ensure_array_unique(target_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]));
        }
    }
    return target_var;
}

static int filter_quantize_mode_kind(const char *text) {
    if (text == NULL || *text == '\0') return 0;
    if (b2026_strcasecmp(text, "NONE") == 0 || strcmp(text, "なし") == 0) return 0;
    if (b2026_strcasecmp(text, "ORDERED") == 0 || strcmp(text, "ディザ階調") == 0 || strcmp(text, "ディザ") == 0) return 1;
    if (b2026_strcasecmp(text, "FS") == 0 || strcmp(text, "誤差拡散法") == 0 || strcmp(text, "誤差拡散") == 0) return 2;
    return -1;
}

static double filter_quantize_color_distance(double r1, double g1, double b1, double r2, double g2, double b2) {
    double dr = r1 - r2;
    double dg = g1 - g2;
    double db = b1 - b2;
    return dr * dr + dg * dg + db * db;
}

static int filter_quantize_nearest_palette_index(const B2026_QuantColor *palette, int palette_count, double r, double g, double b) {
    int i;
    int best_index = 0;
    double best_distance = 0.0;
    if (palette_count <= 0) return 0;
    best_distance = filter_quantize_color_distance(r, g, b, palette[0].r, palette[0].g, palette[0].b);
    for (i = 1; i < palette_count; ++i) {
        double dist = filter_quantize_color_distance(r, g, b, palette[i].r, palette[i].g, palette[i].b);
        if (dist < best_distance) {
            best_distance = dist;
            best_index = i;
        }
    }
    return best_index;
}

static double filter_quantize_channel_bits(double value, double max_value, int bits) {
    double levels;
    double step;
    if (bits <= 0) return value;
    levels = (double)((1 << bits) - 1);
    if (levels <= 0.0) return value;
    if (!isfinite(max_value) || fabs(max_value) < 1e-12) max_value = 255.0;
    step = max_value / levels;
    if (fabs(step) < 1e-12) return value;
    return floor((value / step) + 0.5) * step;
}

static void filter_quantize_apply_direct(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count, int r_bits, int g_bits, int b_bits) {
    int i;
    double max_value = draw_resolve_image_value_max(source_var);
    for (i = 0; i + 3 < logical_count; i += 4) {
        int base = option_base + i;
        double r = b2026_var_as_number(&source_var->data[base + 0]);
        double g = b2026_var_as_number(&source_var->data[base + 1]);
        double b = b2026_var_as_number(&source_var->data[base + 2]);
        double a = b2026_var_as_number(&source_var->data[base + 3]);
        set_b2026_number(&target_var->data[base + 0], filter_quantize_channel_bits(r, max_value, r_bits));
        set_b2026_number(&target_var->data[base + 1], filter_quantize_channel_bits(g, max_value, g_bits));
        set_b2026_number(&target_var->data[base + 2], filter_quantize_channel_bits(b, max_value, b_bits));
        set_b2026_number(&target_var->data[base + 3], a);
    }
}

static void filter_quantize_apply_direct_ordered(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count, int r_bits, int g_bits, int b_bits) {
    static const int bayer4x4[16] = {
        0, 8, 2, 10,
        12, 4, 14, 6,
        3, 11, 1, 9,
        15, 7, 13, 5
    };
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    double max_value = draw_resolve_image_value_max(source_var);
    double r_step = max_value / (double)(((1 << r_bits) - 1) > 0 ? ((1 << r_bits) - 1) : 1);
    double g_step = max_value / (double)(((1 << g_bits) - 1) > 0 ? ((1 << g_bits) - 1) : 1);
    double b_step = max_value / (double)(((1 << b_bits) - 1) > 0 ? ((1 << b_bits) - 1) : 1);
    int x;
    int y;
    int index_pos = 0;
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            int base = option_base + index_pos * 4;
            double r = b2026_var_as_number(&source_var->data[base + 0]);
            double g = b2026_var_as_number(&source_var->data[base + 1]);
            double b = b2026_var_as_number(&source_var->data[base + 2]);
            double a = b2026_var_as_number(&source_var->data[base + 3]);
            double d = (((double)bayer4x4[(y & 3) * 4 + (x & 3)] + 0.5) / 16.0) - 0.5;
            set_b2026_number(&target_var->data[base + 0], filter_quantize_channel_bits(r + d * r_step, max_value, r_bits));
            set_b2026_number(&target_var->data[base + 1], filter_quantize_channel_bits(g + d * g_step, max_value, g_bits));
            set_b2026_number(&target_var->data[base + 2], filter_quantize_channel_bits(b + d * b_step, max_value, b_bits));
            set_b2026_number(&target_var->data[base + 3], a);
            index_pos++;
        }
    }
}

static void filter_quantize_apply_direct_fs(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count, int r_bits, int g_bits, int b_bits) {
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int pixel_count = width * height;
    double *work = (double *)malloc((size_t)pixel_count * 3U * sizeof(double));
    double max_value = draw_resolve_image_value_max(source_var);
    int x;
    int y;
    if (work == NULL) runtime_error("FILTER(\"減色\") failed: out of memory / 【FILTER】減色処理用メモリを確保できません。");
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            int idx = y * width + x;
            int base = option_base + idx * 4;
            work[idx * 3 + 0] = b2026_var_as_number(&source_var->data[base + 0]);
            work[idx * 3 + 1] = b2026_var_as_number(&source_var->data[base + 1]);
            work[idx * 3 + 2] = b2026_var_as_number(&source_var->data[base + 2]);
        }
    }
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            int idx = y * width + x;
            int base = option_base + idx * 4;
            double old_r = work[idx * 3 + 0];
            double old_g = work[idx * 3 + 1];
            double old_b = work[idx * 3 + 2];
            double a = b2026_var_as_number(&source_var->data[base + 3]);
            double new_r = filter_quantize_channel_bits(old_r, max_value, r_bits);
            double new_g = filter_quantize_channel_bits(old_g, max_value, g_bits);
            double new_b = filter_quantize_channel_bits(old_b, max_value, b_bits);
            double err_r = old_r - new_r;
            double err_g = old_g - new_g;
            double err_b = old_b - new_b;
            set_b2026_number(&target_var->data[base + 0], new_r);
            set_b2026_number(&target_var->data[base + 1], new_g);
            set_b2026_number(&target_var->data[base + 2], new_b);
            set_b2026_number(&target_var->data[base + 3], a);
            if (x + 1 < width) {
                int n = idx + 1;
                work[n * 3 + 0] += err_r * (7.0 / 16.0);
                work[n * 3 + 1] += err_g * (7.0 / 16.0);
                work[n * 3 + 2] += err_b * (7.0 / 16.0);
            }
            if (y + 1 < height) {
                if (x > 0) {
                    int n = idx + width - 1;
                    work[n * 3 + 0] += err_r * (3.0 / 16.0);
                    work[n * 3 + 1] += err_g * (3.0 / 16.0);
                    work[n * 3 + 2] += err_b * (3.0 / 16.0);
                }
                {
                    int n = idx + width;
                    work[n * 3 + 0] += err_r * (5.0 / 16.0);
                    work[n * 3 + 1] += err_g * (5.0 / 16.0);
                    work[n * 3 + 2] += err_b * (5.0 / 16.0);
                }
                if (x + 1 < width) {
                    int n = idx + width + 1;
                    work[n * 3 + 0] += err_r * (1.0 / 16.0);
                    work[n * 3 + 1] += err_g * (1.0 / 16.0);
                    work[n * 3 + 2] += err_b * (1.0 / 16.0);
                }
            }
        }
    }
    free(work);
}

static void filter_apply_quantize_direct(const char *target_name, const char *source_name, int in_place, int r_bits, int g_bits, int b_bits, const char *mode_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var;
    int logical_count;
    int mode_kind;
    if (source_var->screen_width <= 0 || source_var->screen_height <= 0) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    logical_count = array_logical_count(source_var);
    if (logical_count < source_var->screen_width * source_var->screen_height * 4) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    mode_kind = filter_quantize_mode_kind(mode_name);
    if (mode_kind < 0) runtime_error("FILTER(\"減色\") failed: unsupported mode / 【FILTER】未対応の減色方式です。");
    target_var = filter_quantize_prepare_target(target_name, source_var, in_place, logical_count);
    if (mode_kind == 0) filter_quantize_apply_direct(target_var, source_var, logical_count, r_bits, g_bits, b_bits);
    else if (mode_kind == 1) filter_quantize_apply_direct_ordered(target_var, source_var, logical_count, r_bits, g_bits, b_bits);
    else filter_quantize_apply_direct_fs(target_var, source_var, logical_count, r_bits, g_bits, b_bits);
    filter_quantize_copy_canvas_meta(target_var, source_var, logical_count);
}

static int filter_quantize_add_palette_color(B2026_QuantColor *palette, int palette_count, int palette_max, double r, double g, double b) {
    int i;
    for (i = 0; i < palette_count; ++i) {
        if (fabs(palette[i].r - r) < 1e-12 && fabs(palette[i].g - g) < 1e-12 && fabs(palette[i].b - b) < 1e-12) return palette_count;
    }
    if (palette_count >= palette_max) return palette_count;
    palette[palette_count].r = r;
    palette[palette_count].g = g;
    palette[palette_count].b = b;
    return palette_count + 1;
}

static int filter_quantize_build_named_palette(const char *palette_name, double max_value, B2026_QuantColor *palette, int palette_max) {
    int palette_count = 0;
    int r, g, b, i;
    static const int html16[16][3] = {
        {0x00,0x00,0x00},{0xC0,0xC0,0xC0},{0x80,0x80,0x80},{0xFF,0xFF,0xFF},
        {0x80,0x00,0x00},{0xFF,0x00,0x00},{0x80,0x00,0x80},{0xFF,0x00,0xFF},
        {0x00,0x80,0x00},{0x00,0xFF,0x00},{0x80,0x80,0x00},{0xFF,0xFF,0x00},
        {0x00,0x00,0x80},{0x00,0x00,0xFF},{0x00,0x80,0x80},{0x00,0xFF,0xFF}
    };
    static const int html140[][3] = {
        {0x00,0x00,0x00},{0xC0,0xC0,0xC0},{0x80,0x80,0x80},{0xFF,0xFF,0xFF},
        {0x80,0x00,0x00},{0xFF,0x00,0x00},{0x80,0x00,0x80},{0xFF,0x00,0xFF},
        {0x00,0x80,0x00},{0x00,0xFF,0x00},{0x80,0x80,0x00},{0xFF,0xFF,0x00},
        {0x00,0x00,0x80},{0x00,0x00,0xFF},{0x00,0x80,0x80},{0x00,0xFF,0xFF},
        {0xF0,0xF8,0xFF},{0xFA,0xEB,0xD7},{0x7F,0xFF,0xD4},{0xF0,0xFF,0xFF},
        {0xF5,0xF5,0xDC},{0xFF,0xE4,0xC4},{0xFF,0xEB,0xCD},{0x8A,0x2B,0xE2},
        {0xA5,0x2A,0x2A},{0xDE,0xB8,0x87},{0x5F,0x9E,0xA0},{0x7F,0xFF,0x00},
        {0xD2,0x69,0x1E},{0xFF,0x7F,0x50},{0x64,0x95,0xED},{0xFF,0xF8,0xDC},
        {0xDC,0x14,0x3C},{0x00,0x00,0x8B},{0x00,0x8B,0x8B},{0xB8,0x86,0x0B},
        {0xA9,0xA9,0xA9},{0x00,0x64,0x00},{0xBD,0xB7,0x6B},{0x8B,0x00,0x8B},
        {0x55,0x6B,0x2F},{0xFF,0x8C,0x00},{0x99,0x32,0xCC},{0x8B,0x00,0x00},
        {0xE9,0x96,0x7A},{0x8F,0xBC,0x8F},{0x48,0x3D,0x8B},{0x2F,0x4F,0x4F},
        {0x00,0xCE,0xD1},{0x94,0x00,0xD3},{0xFF,0x14,0x93},{0x00,0xBF,0xFF},
        {0x69,0x69,0x69},{0x1E,0x90,0xFF},{0xB2,0x22,0x22},{0xFF,0xFA,0xF0},
        {0x22,0x8B,0x22},{0xFF,0x00,0xFF},{0xDC,0xDC,0xDC},{0xF8,0xF8,0xFF},
        {0xFF,0xD7,0x00},{0xDA,0xA5,0x20},{0xAD,0xFF,0x2F},{0x80,0x80,0x80},
        {0xF0,0xFF,0xF0},{0xFF,0x69,0xB4},{0xCD,0x5C,0x5C},{0x4B,0x00,0x82},
        {0xFF,0xFF,0xF0},{0xF0,0xE6,0x8C},{0xE6,0xE6,0xFA},{0xFF,0xF0,0xF5},
        {0x7C,0xFC,0x00},{0xFF,0xFA,0xCD},{0xAD,0xD8,0xE6},{0xF0,0x80,0x80},
        {0xE0,0xFF,0xFF},{0xFA,0xFA,0xD2},{0xD3,0xD3,0xD3},{0x90,0xEE,0x90},
        {0xFF,0xB6,0xC1},{0xFF,0xA0,0x7A},{0x20,0xB2,0xAA},{0x87,0xCE,0xFA},
        {0x77,0x88,0x99},{0xB0,0xC4,0xDE},{0xFF,0xFF,0xE0},{0x00,0xFF,0x00},
        {0x32,0xCD,0x32},{0xFA,0xF0,0xE6},{0x66,0xCD,0xAA},{0x00,0x00,0xCD},
        {0xBA,0x55,0xD3},{0x93,0x70,0xDB},{0x3C,0xB3,0x71},{0x7B,0x68,0xEE},
        {0x00,0xFA,0x9A},{0x48,0xD1,0xCC},{0xC7,0x15,0x85},{0x19,0x19,0x70},
        {0xF5,0xFF,0xFA},{0xFF,0xE4,0xE1},{0xFF,0xE4,0xB5},{0xFF,0xDE,0xAD},
        {0x00,0x00,0x80},{0xFD,0xF5,0xE6},{0x80,0x80,0x00},{0x6B,0x8E,0x23},
        {0xFF,0xA5,0x00},{0xFF,0x45,0x00},{0xDA,0x70,0xD6},{0xEE,0xE8,0xAA},
        {0x98,0xFB,0x98},{0xAF,0xEE,0xEE},{0xDB,0x70,0x93},{0xFF,0xEF,0xD5},
        {0xFF,0xDA,0xB9},{0xCD,0x85,0x3F},{0xFF,0xC0,0xCB},{0xDD,0xA0,0xDD},
        {0xB0,0xE0,0xE6},{0x80,0x00,0x80},{0x66,0x33,0x99},{0xBC,0x8F,0x8F},
        {0x41,0x69,0xE1},{0x8B,0x45,0x13},{0xFA,0x80,0x72},{0xF4,0xA4,0x60},
        {0x2E,0x8B,0x57},{0xFF,0xF5,0xEE},{0xA0,0x52,0x2D},{0x87,0xCE,0xEB},
        {0x6A,0x5A,0xCD},{0x70,0x80,0x90},{0xFF,0xFA,0xFA},{0x00,0xFF,0x7F},
        {0x46,0x82,0xB4},{0xD2,0xB4,0x8C},{0x00,0x80,0x80},{0xD8,0xBF,0xD8},
        {0xFF,0x63,0x47},{0x40,0xE0,0xD0},{0xEE,0x82,0xEE},{0xF5,0xDE,0xB3},
        {0xF5,0xF5,0xF5},{0x9A,0xCD,0x32}
    };
    if (palette_name == NULL || *palette_name == '\0') return 0;
    if (b2026_strcasecmp(palette_name, "WEBSAFE") == 0 || strcmp(palette_name, "WEBセーフ") == 0 || strcmp(palette_name, "ウェブセーフ") == 0) {
        static const int web_levels[6] = {0, 51, 102, 153, 204, 255};
        for (r = 0; r < 6; ++r) {
            for (g = 0; g < 6; ++g) {
                for (b = 0; b < 6; ++b) {
                    palette_count = filter_quantize_add_palette_color(
                        palette, palette_count, palette_max,
                        max_value * ((double)web_levels[r] / 255.0),
                        max_value * ((double)web_levels[g] / 255.0),
                        max_value * ((double)web_levels[b] / 255.0)
                    );
                }
            }
        }
        return palette_count;
    }
    if (b2026_strcasecmp(palette_name, "HTML16") == 0 || b2026_strcasecmp(palette_name, "BASIC16") == 0 || strcmp(palette_name, "HTML16色") == 0 || strcmp(palette_name, "基本16色") == 0) {
        for (i = 0; i < 16; ++i) {
            palette_count = filter_quantize_add_palette_color(
                palette, palette_count, palette_max,
                max_value * ((double)html16[i][0] / 255.0),
                max_value * ((double)html16[i][1] / 255.0),
                max_value * ((double)html16[i][2] / 255.0)
            );
        }
        return palette_count;
    }
    if (b2026_strcasecmp(palette_name, "HTML140") == 0 || b2026_strcasecmp(palette_name, "CSS140") == 0 || strcmp(palette_name, "HTML140色") == 0 || strcmp(palette_name, "CSS140色") == 0 || strcmp(palette_name, "Web140色") == 0 || strcmp(palette_name, "ウェブ140色") == 0) {
        for (i = 0; i < (int)(sizeof(html140) / sizeof(html140[0])); ++i) {
            palette_count = filter_quantize_add_palette_color(
                palette, palette_count, palette_max,
                max_value * ((double)html140[i][0] / 255.0),
                max_value * ((double)html140[i][1] / 255.0),
                max_value * ((double)html140[i][2] / 255.0)
            );
        }
        return palette_count;
    }
    if (b2026_strcasecmp(palette_name, "GRAY16") == 0 || strcmp(palette_name, "16階調グレー") == 0 || strcmp(palette_name, "16グレー") == 0) {
        for (i = 0; i < 16; ++i) {
            double value = max_value * ((double)i / 15.0);
            palette_count = filter_quantize_add_palette_color(palette, palette_count, palette_max, value, value, value);
        }
        return palette_count;
    }
    if (b2026_strcasecmp(palette_name, "GRAY256") == 0 || strcmp(palette_name, "256階調グレー") == 0 || strcmp(palette_name, "256グレー") == 0) {
        for (i = 0; i < 256; ++i) {
            double value = max_value * ((double)i / 255.0);
            palette_count = filter_quantize_add_palette_color(palette, palette_count, palette_max, value, value, value);
        }
        return palette_count;
    }
    if (b2026_strcasecmp(palette_name, "BW") == 0 || strcmp(palette_name, "白黒") == 0 || strcmp(palette_name, "2値") == 0 || strcmp(palette_name, "モノクロ") == 0) {
        palette_count = filter_quantize_add_palette_color(palette, palette_count, palette_max, 0.0, 0.0, 0.0);
        palette_count = filter_quantize_add_palette_color(palette, palette_count, palette_max, max_value, max_value, max_value);
        return palette_count;
    }
    return 0;
}

static int filter_quantize_build_palette_from_image(const char *palette_name, B2026_QuantColor *palette, int palette_max) {
    ArrayVariable *palette_var = require_array_variable(palette_name);
    int logical_count;
    int width;
    int height;
    int pixel_count;
    int i;
    int palette_count = 0;
    if (palette_var->screen_width <= 0 || palette_var->screen_height <= 0) runtime_error("FILTER(\"減色\") failed: palette image required / 【FILTER】減色のパレット指定にはピクセル配列が必要です。");
    logical_count = array_logical_count(palette_var);
    width = palette_var->screen_width;
    height = palette_var->screen_height;
    pixel_count = width * height;
    if (logical_count < pixel_count * 4) runtime_error("FILTER(\"減色\") failed: palette image required / 【FILTER】減色のパレット指定にはピクセル配列が必要です。");
    for (i = 0; i < pixel_count; ++i) {
        int base = option_base + i * 4;
        double r = b2026_var_as_number(&palette_var->data[base + 0]);
        double g = b2026_var_as_number(&palette_var->data[base + 1]);
        double b = b2026_var_as_number(&palette_var->data[base + 2]);
        double a = b2026_var_as_number(&palette_var->data[base + 3]);
        if (fabs(a) < 1e-12) continue;
        palette_count = filter_quantize_add_palette_color(palette, palette_count, palette_max, r, g, b);
    }
    return palette_count;
}

static int filter_quantize_parse_named_color_count(const char *text) {
    char *endptr = NULL;
    long value;
    if (text == NULL || *text == '\0') return 0;
    value = strtol(text, &endptr, 10);
    if (endptr != NULL && *endptr == '\0') return (int)value;
    if (endptr != NULL && strcmp(endptr, "色") == 0) return (int)value;
    return 0;
}

static int filter_quantize_sample_limit(int pixel_count, int target_count) {
    int limit = pixel_count;
    if (limit > 4096) limit = 4096;
    if (limit < target_count) limit = target_count;
    if (limit < 1) limit = 1;
    return limit;
}

static int filter_quantize_build_adaptive_palette(ArrayVariable *source_var, int color_count, B2026_QuantColor *palette, int palette_max) {
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int pixel_count = width * height;
    int sample_limit = filter_quantize_sample_limit(pixel_count, color_count);
    int stride = 1;
    int sample_count = 0;
    int i;
    int iter;
    double *samples = NULL;
    if (color_count < 1) return 0;
    if (color_count > palette_max) color_count = palette_max;
    if (pixel_count <= 0) return 0;
    if (sample_limit < pixel_count) {
        stride = (pixel_count + sample_limit - 1) / sample_limit;
    }
    samples = (double *)malloc((size_t)sample_limit * 3U * sizeof(double));
    if (samples == NULL) runtime_error("FILTER(\"減色\") failed: out of memory / 【FILTER】減色処理用メモリを確保できません。");
    for (i = 0; i < pixel_count && sample_count < sample_limit; i += stride) {
        int base = option_base + i * 4;
        samples[sample_count * 3 + 0] = b2026_var_as_number(&source_var->data[base + 0]);
        samples[sample_count * 3 + 1] = b2026_var_as_number(&source_var->data[base + 1]);
        samples[sample_count * 3 + 2] = b2026_var_as_number(&source_var->data[base + 2]);
        sample_count++;
    }
    if (sample_count <= 0) {
        free(samples);
        return 0;
    }
    if (sample_count < color_count) color_count = sample_count;
    for (i = 0; i < color_count; ++i) {
        int index = (i * sample_count) / color_count;
        palette[i].r = samples[index * 3 + 0];
        palette[i].g = samples[index * 3 + 1];
        palette[i].b = samples[index * 3 + 2];
    }
    for (iter = 0; iter < 6; ++iter) {
        double *sum_r = (double *)calloc((size_t)color_count, sizeof(double));
        double *sum_g = (double *)calloc((size_t)color_count, sizeof(double));
        double *sum_b = (double *)calloc((size_t)color_count, sizeof(double));
        int *counts = (int *)calloc((size_t)color_count, sizeof(int));
        if (sum_r == NULL || sum_g == NULL || sum_b == NULL || counts == NULL) {
            if (sum_r != NULL) free(sum_r);
            if (sum_g != NULL) free(sum_g);
            if (sum_b != NULL) free(sum_b);
            if (counts != NULL) free(counts);
            free(samples);
            runtime_error("FILTER(\"減色\") failed: out of memory / 【FILTER】減色処理用メモリを確保できません。");
        }
        for (i = 0; i < sample_count; ++i) {
            double r = samples[i * 3 + 0];
            double g = samples[i * 3 + 1];
            double b = samples[i * 3 + 2];
            int nearest = filter_quantize_nearest_palette_index(palette, color_count, r, g, b);
            sum_r[nearest] += r;
            sum_g[nearest] += g;
            sum_b[nearest] += b;
            counts[nearest] += 1;
        }
        for (i = 0; i < color_count; ++i) {
            if (counts[i] > 0) {
                palette[i].r = sum_r[i] / (double)counts[i];
                palette[i].g = sum_g[i] / (double)counts[i];
                palette[i].b = sum_b[i] / (double)counts[i];
            }
        }
        free(sum_r);
        free(sum_g);
        free(sum_b);
        free(counts);
    }
    free(samples);
    return color_count;
}

static void filter_quantize_apply_palette_none(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count, const B2026_QuantColor *palette, int palette_count) {
    int i;
    for (i = 0; i + 3 < logical_count; i += 4) {
        int base = option_base + i;
        double r = b2026_var_as_number(&source_var->data[base + 0]);
        double g = b2026_var_as_number(&source_var->data[base + 1]);
        double b = b2026_var_as_number(&source_var->data[base + 2]);
        double a = b2026_var_as_number(&source_var->data[base + 3]);
        int index = filter_quantize_nearest_palette_index(palette, palette_count, r, g, b);
        set_b2026_number(&target_var->data[base + 0], palette[index].r);
        set_b2026_number(&target_var->data[base + 1], palette[index].g);
        set_b2026_number(&target_var->data[base + 2], palette[index].b);
        set_b2026_number(&target_var->data[base + 3], a);
    }
}

static void filter_quantize_apply_palette_ordered(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count, const B2026_QuantColor *palette, int palette_count) {
    static const int bayer4x4[16] = {
        0, 8, 2, 10,
        12, 4, 14, 6,
        3, 11, 1, 9,
        15, 7, 13, 5
    };
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    double max_value = draw_resolve_image_value_max(source_var);
    double amount = max_value / (double)((palette_count > 1 ? palette_count - 1 : 1) * 2);
    int x;
    int y;
    int index_pos = 0;
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            int base = option_base + index_pos * 4;
            double r = b2026_var_as_number(&source_var->data[base + 0]);
            double g = b2026_var_as_number(&source_var->data[base + 1]);
            double b = b2026_var_as_number(&source_var->data[base + 2]);
            double a = b2026_var_as_number(&source_var->data[base + 3]);
            double d = (((double)bayer4x4[(y & 3) * 4 + (x & 3)] + 0.5) / 16.0) - 0.5;
            int nearest = filter_quantize_nearest_palette_index(palette, palette_count, r + d * amount, g + d * amount, b + d * amount);
            set_b2026_number(&target_var->data[base + 0], palette[nearest].r);
            set_b2026_number(&target_var->data[base + 1], palette[nearest].g);
            set_b2026_number(&target_var->data[base + 2], palette[nearest].b);
            set_b2026_number(&target_var->data[base + 3], a);
            index_pos++;
        }
    }
}

static void filter_quantize_apply_palette_fs(ArrayVariable *target_var, ArrayVariable *source_var, int logical_count, const B2026_QuantColor *palette, int palette_count) {
    int width = source_var->screen_width;
    int height = source_var->screen_height;
    int pixel_count = width * height;
    double *work = (double *)malloc((size_t)pixel_count * 3U * sizeof(double));
    int x;
    int y;
    if (work == NULL) runtime_error("FILTER(\"減色\") failed: out of memory / 【FILTER】減色処理用メモリを確保できません。");
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            int idx = y * width + x;
            int base = option_base + idx * 4;
            work[idx * 3 + 0] = b2026_var_as_number(&source_var->data[base + 0]);
            work[idx * 3 + 1] = b2026_var_as_number(&source_var->data[base + 1]);
            work[idx * 3 + 2] = b2026_var_as_number(&source_var->data[base + 2]);
        }
    }
    for (y = 0; y < height; ++y) {
        for (x = 0; x < width; ++x) {
            int idx = y * width + x;
            int base = option_base + idx * 4;
            double old_r = work[idx * 3 + 0];
            double old_g = work[idx * 3 + 1];
            double old_b = work[idx * 3 + 2];
            double a = b2026_var_as_number(&source_var->data[base + 3]);
            int nearest = filter_quantize_nearest_palette_index(palette, palette_count, old_r, old_g, old_b);
            double new_r = palette[nearest].r;
            double new_g = palette[nearest].g;
            double new_b = palette[nearest].b;
            double err_r = old_r - new_r;
            double err_g = old_g - new_g;
            double err_b = old_b - new_b;
            set_b2026_number(&target_var->data[base + 0], new_r);
            set_b2026_number(&target_var->data[base + 1], new_g);
            set_b2026_number(&target_var->data[base + 2], new_b);
            set_b2026_number(&target_var->data[base + 3], a);
            if (x + 1 < width) {
                int n = idx + 1;
                work[n * 3 + 0] += err_r * (7.0 / 16.0);
                work[n * 3 + 1] += err_g * (7.0 / 16.0);
                work[n * 3 + 2] += err_b * (7.0 / 16.0);
            }
            if (y + 1 < height) {
                if (x > 0) {
                    int n = idx + width - 1;
                    work[n * 3 + 0] += err_r * (3.0 / 16.0);
                    work[n * 3 + 1] += err_g * (3.0 / 16.0);
                    work[n * 3 + 2] += err_b * (3.0 / 16.0);
                }
                {
                    int n = idx + width;
                    work[n * 3 + 0] += err_r * (5.0 / 16.0);
                    work[n * 3 + 1] += err_g * (5.0 / 16.0);
                    work[n * 3 + 2] += err_b * (5.0 / 16.0);
                }
                if (x + 1 < width) {
                    int n = idx + width + 1;
                    work[n * 3 + 0] += err_r * (1.0 / 16.0);
                    work[n * 3 + 1] += err_g * (1.0 / 16.0);
                    work[n * 3 + 2] += err_b * (1.0 / 16.0);
                }
            }
        }
    }
    free(work);
}

static void filter_apply_quantize_palette(const char *target_name, const char *source_name, int in_place, const B2026_QuantColor *palette, int palette_count, const char *mode_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    ArrayVariable *target_var;
    int logical_count;
    int mode_kind;
    if (source_var->screen_width <= 0 || source_var->screen_height <= 0) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    logical_count = array_logical_count(source_var);
    if (logical_count < source_var->screen_width * source_var->screen_height * 4) runtime_error("FILTER error: ピクセル配列ではない配列が指定されました。SCREEN等でピクセル配列に変換してください。");
    if (palette_count <= 0) runtime_error("FILTER(\"減色\") failed: palette is empty / 【FILTER】減色用パレットが空です。");
    mode_kind = filter_quantize_mode_kind(mode_name);
    if (mode_kind < 0) runtime_error("FILTER(\"減色\") failed: unsupported mode / 【FILTER】未対応の減色方式です。");
    target_var = filter_quantize_prepare_target(target_name, source_var, in_place, logical_count);
    if (mode_kind == 0) filter_quantize_apply_palette_none(target_var, source_var, logical_count, palette, palette_count);
    else if (mode_kind == 1) filter_quantize_apply_palette_ordered(target_var, source_var, logical_count, palette, palette_count);
    else filter_quantize_apply_palette_fs(target_var, source_var, logical_count, palette, palette_count);
    filter_quantize_copy_canvas_meta(target_var, source_var, logical_count);
}

static void filter_apply_quantize_by_count(const char *target_name, const char *source_name, int in_place, double color_count_value, const char *mode_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    int requested = (int)floor(color_count_value + 0.5);
    if (requested < 2 || requested > 256) runtime_error("FILTER(\"減色\") failed: color count must be 2 to 256 / 【FILTER】色数は 2〜256 を指定してください。");
    {
        B2026_QuantColor palette[256];
        int palette_count = filter_quantize_build_adaptive_palette(source_var, requested, palette, 256);
        filter_apply_quantize_palette(target_name, source_name, in_place, palette, palette_count, mode_name);
    }
}

static void filter_apply_quantize_by_named_spec(const char *target_name, const char *source_name, int in_place, const char *spec_name, const char *mode_name);

static void filter_apply_quantize_by_numeric(const char *target_name, const char *source_name, int in_place, double value, const char *mode_name) {
    int requested = (int)floor(value + 0.5);
    if (requested >= 2 && requested <= 256) {
        filter_apply_quantize_by_count(target_name, source_name, in_place, (double)requested, mode_name);
        return;
    }
    if (requested == 4096 || requested == 32768 || requested == 65536) {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "%d色", requested);
        filter_apply_quantize_by_named_spec(target_name, source_name, in_place, buffer, mode_name);
        return;
    }
    runtime_error("FILTER(\"減色\") failed: unsupported color count / 【FILTER】未対応の色数です。");
}

static void filter_apply_quantize_by_named_spec(const char *target_name, const char *source_name, int in_place, const char *spec_name, const char *mode_name) {
    int color_count = filter_quantize_parse_named_color_count(spec_name);
    if (color_count >= 2 && color_count <= 256) {
        filter_apply_quantize_by_count(target_name, source_name, in_place, (double)color_count, mode_name);
        return;
    }
    if (color_count == 4096) {
        filter_apply_quantize_direct(target_name, source_name, in_place, 4, 4, 4, mode_name);
        return;
    }
    if (color_count == 32768) {
        filter_apply_quantize_direct(target_name, source_name, in_place, 5, 5, 5, mode_name);
        return;
    }
    if (color_count == 65536) {
        filter_apply_quantize_direct(target_name, source_name, in_place, 5, 6, 5, mode_name);
        return;
    }
    {
        ArrayVariable *source_var = require_array_variable(source_name);
        B2026_QuantColor palette[256];
        int palette_count = filter_quantize_build_named_palette(spec_name, draw_resolve_image_value_max(source_var), palette, 256);
        if (palette_count <= 0) runtime_error("FILTER(\"減色\") failed: unsupported palette name / 【FILTER】未対応のパレット名です。");
        filter_apply_quantize_palette(target_name, source_name, in_place, palette, palette_count, mode_name);
    }
}

static void filter_apply_quantize_by_palette_image(const char *target_name, const char *source_name, int in_place, const char *palette_name, const char *mode_name) {
    B2026_QuantColor palette[4096];
    int palette_count = filter_quantize_build_palette_from_image(palette_name, palette, 4096);
    if (palette_count <= 0) runtime_error("FILTER(\"減色\") failed: palette image has no colors / 【FILTER】パレット画像に有効な色がありません。");
    filter_apply_quantize_palette(target_name, source_name, in_place, palette, palette_count, mode_name);
}

static void image_palette_prepare_result(ArrayVariable *target_var, int width, int height, double image_value_max) {
    target_var->screen_width = width;
    target_var->screen_height = height;
    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;
    target_var->image_value_max = image_value_max;
    if (!(image_value_max > 0.0) || !isfinite(image_value_max)) target_var->image_pixel_mode = 1;
    else if (image_value_max <= 1.0) target_var->image_pixel_mode = 0;
    else if (image_value_max <= 255.0) target_var->image_pixel_mode = 1;
    else target_var->image_pixel_mode = 2;
    target_var->read_count = width * height * 4;
}

static void image_palette_store_colors(const char *target_name, const B2026_QuantColor *palette, int palette_count, double image_value_max) {
    ArrayVariable *target_var;
    int i;
    if (palette_count < 1) palette_count = 1;
    dim_array(target_name, (double)(option_base + palette_count * 4 - 1));
    target_var = require_array_variable(target_name);
    image_palette_prepare_result(target_var, palette_count, 1, image_value_max);
    for (i = 0; i < palette_count; ++i) {
        int base = option_base + i * 4;
        double r = 0.0;
        double g = 0.0;
        double b = 0.0;
        if (palette != NULL && i < palette_count) {
            r = palette[i].r;
            g = palette[i].g;
            b = palette[i].b;
        }
        set_b2026_number(&target_var->data[base + 0], r);
        set_b2026_number(&target_var->data[base + 1], g);
        set_b2026_number(&target_var->data[base + 2], b);
        set_b2026_number(&target_var->data[base + 3], image_value_max);
    }
}

static void image_apply_palette_extract_by_numeric(const char *target_name, const char *source_name, int in_place, double value) {
    ArrayVariable *source_var = require_array_variable(source_name);
    int requested = (int)floor(value + 0.5);
    double image_value_max = draw_resolve_image_value_max(source_var);
    if (requested >= 2 && requested <= 4096) {
        B2026_QuantColor *palette = (B2026_QuantColor *)malloc((size_t)requested * sizeof(B2026_QuantColor));
        int palette_count;
        if (palette == NULL) runtime_error("IMAGE(\"パレット抽出\") failed: out of memory / 【IMAGE】パレット抽出用メモリを確保できません。");
        palette_count = filter_quantize_build_adaptive_palette(source_var, requested, palette, requested);
        image_palette_store_colors(in_place ? source_name : target_name, palette, palette_count, image_value_max);
        free(palette);
        return;
    }
    if (requested == 4096 || requested == 32768 || requested == 65536) {
        int r_bits = requested == 4096 ? 4 : 5;
        int g_bits = requested == 65536 ? 6 : (requested == 4096 ? 4 : 5);
        int b_bits = requested == 4096 ? 4 : 5;
        int r_levels = (1 << r_bits);
        int g_levels = (1 << g_bits);
        int b_levels = (1 << b_bits);
        int palette_count = r_levels * g_levels * b_levels;
        B2026_QuantColor *palette = (B2026_QuantColor *)malloc((size_t)palette_count * sizeof(B2026_QuantColor));
        int ri, gi, bi, idx = 0;
        if (palette == NULL) runtime_error("IMAGE(\"パレット抽出\") failed: out of memory / 【IMAGE】パレット抽出用メモリを確保できません。");
        for (ri = 0; ri < r_levels; ++ri) {
            for (gi = 0; gi < g_levels; ++gi) {
                for (bi = 0; bi < b_levels; ++bi) {
                    palette[idx].r = image_value_max * ((double)ri / (double)(r_levels - 1));
                    palette[idx].g = image_value_max * ((double)gi / (double)(g_levels - 1));
                    palette[idx].b = image_value_max * ((double)bi / (double)(b_levels - 1));
                    idx++;
                }
            }
        }
        image_palette_store_colors(in_place ? source_name : target_name, palette, palette_count, image_value_max);
        free(palette);
        return;
    }
    runtime_error("IMAGE(\"パレット抽出\") failed: unsupported color count / 【IMAGE】未対応の色数です。");
}

static void image_apply_palette_extract_by_named(const char *target_name, const char *source_name, int in_place, const char *spec_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    double image_value_max = draw_resolve_image_value_max(source_var);
    int color_count = filter_quantize_parse_named_color_count(spec_name);
    if (color_count > 0) {
        image_apply_palette_extract_by_numeric(target_name, source_name, in_place, (double)color_count);
        return;
    }
    {
        B2026_QuantColor palette[256];
        int palette_count = filter_quantize_build_named_palette(spec_name, image_value_max, palette, 256);
        if (palette_count <= 0) runtime_error("IMAGE(\"パレット抽出\") failed: unsupported palette name / 【IMAGE】未対応のパレット名です。");
        image_palette_store_colors(in_place ? source_name : target_name, palette, palette_count, image_value_max);
    }
}

static void image_apply_palette_extract_from_image(const char *target_name, const char *source_name, int in_place, const char *palette_name) {
    ArrayVariable *source_var = require_array_variable(source_name);
    double image_value_max = draw_resolve_image_value_max(source_var);
    B2026_QuantColor palette[4096];
    int palette_count = filter_quantize_build_palette_from_image(palette_name, palette, 4096);
    if (palette_count <= 0) runtime_error("IMAGE(\"パレット抽出\") failed: palette image has no colors / 【IMAGE】パレット画像に有効な色がありません。");
    image_palette_store_colors(in_place ? source_name : target_name, palette, palette_count, image_value_max);
}
