Add custom color palette support to bitmap tracing with novel color quantization algorithm
NOTE: This PR has been co-authored by Claude.ai. I could not find the official policy of Inkscape regarding AI contributions. Please discard this PR if it contravenes Inkscape policies. The code has been checked and tested by myself (in addition to running ninja check).
Allow users to define their own color palette (or modify the auto-generated one) for multi-color tracing (Trace Bitmap > Multicolor > Colors), instead of relying solely on automatic octree color quantization.
This addresses a pain point when vectorizing a bitmap and getting a lot of similar colors, missing important hues present in the image.
When "Colors" mode is selected in the Multicolor tab, a palette editor replaces the scans slider. The palette is automatically populated by analyzing the selected image (sampling 10K pixels out of the image, so that it remains always reponsive). The optimal number of colors and their values are determined without user interaction.
Each color is shown as a row with:
- A color swatch: hover to see the eyedropper icon, click to pick a color directly from the canvas (reuses the dropper tool's one-time pick mechanism, same as Fill & Stroke)
- A hex label with disclosure arrow: click to expand an inline color picker with HSL/RGB sliders (reuses ColorPickerPanel, the same widget as Fill & Stroke)
- A delete button to remove individual colors
The "+" button uses constrained k-means to find the best next color to add (the most distinctive color not yet covered by the palette). The refresh button re-extracts the optimal palette from the image.
The custom palette is then passed through to the tracing engine, which maps each pixel to the nearest palette color instead of running octree quantization. The rest of the tracing pipeline (per-color mask creation, potrace vectorization, stacking) works unchanged.
The attached SVG shows the old vs new color quantization algorithm on a range of different bitmap types:
Perceptual color quantization
The previous octree quantization operated in raw RGB space and allocated colors proportionally to pixel count: a large gray background would consume most of the palette while small vivid regions (e.g. colored flowers) were lost.
The new approach uses saliency-weighted k-means in Oklab perceptual color space:
- Oklab color space: Euclidean distance in Oklab closely matches perceived color difference, so the algorithm naturally preserves hue diversity rather than clustering around brightness variations.
- Local chroma saliency weighting: Before running k-means, each pixel is weighted by how much its chroma differs from its local neighborhood. Pixels at color boundaries and in small distinctive regions get ~20× more influence than uniform background pixels. This steers k-means toward visually important colors without sacrificing clustering quality.
- Automatic palette size: The elbow method on k-means inertia determines the optimal number of colors (2–10), biased slightly toward richer palettes.
- Pixel-to-palette assignment also uses Oklab distance, so each pixel maps to the color that looks closest, not just the geometrically nearest in RGB.
The old octree quantization is preserved for QUANT_MONO and BRIGHTNESS_MULTI modes where the output is converted to grayscale anyway.
Implementation notes
- Oklab conversion is implemented directly in quantize.cpp rather than reusing ok_color::linear_srgb_to_oklab() from src/colors/spaces/ok-color.cpp, because those functions are internal to the color picker widget (not declared in the public header), use different types, and operate on linear sRGB without handling the transfer function. A self-contained implementation avoids coupling between unrelated subsystems.
- Palette swatches use GtkSnapshot (snapshot_vfunc with gtk_snapshot_append_color) instead of Cairo, following the approach established in !7765 (merged).
- The inline color picker uses a shared ColorPickerPanel instance (with PlateType::None) that is inserted below the active row and bound to its ColorSet, ensuring full UI consistency with Fill & Stroke.

























