Notes on the performance characteristics of selected Image operations.

Dominant colors: :histogram vs :imagequant

Image.dominant_color/2 supports two methods:

  • :histogram (default) — a coarse 3D RGB histogram via vips_hist_find_ndim. Returns the centres of the most populated bins.

  • :imagequant — routes through vips_gifsave_buffer so that libvips runs libimagequant and writes a quantised Global Color Table. The GCT is parsed back into a list of RGB tuples ordered by perceptual importance.

Kip_small.png (300×328×3)

Warm timings, 20 iterations each, macOS arm64.

top_n:histogram:imagequantratio
10.09 ms3.00 ms~33×
80.16 ms3.82 ms~24×
160.13 ms4.70 ms~36×

Imagequant effort sweep (top_n: 8):

:efforttime
12.95 ms
32.95 ms
53.89 ms
73.98 ms
104.15 ms

Sample output (top_n: 5):

histogram : [[40, 40, 40], [56, 56, 56], [8, 8, 8], [184, 184, 184], [168, 168, 168]]
imagequant: [{195, 195, 195}, {125, 125, 125}, {91, 91, 91}, {153, 153, 153}, {176, 176, 176}]

Singapore-2016-09-5887.jpg (1000×590×3)

Warm timings, 20 iterations each, macOS arm64.

top_n:histogram:imagequantratio
10.09 ms55.29 ms~614×
80.13 ms145.79 ms~1120×
160.13 ms156.01 ms~1200×

Imagequant effort sweep (top_n: 8):

:efforttime
129.80 ms
329.92 ms
563.39 ms
7147.57 ms
10210.41 ms

Sample output (top_n: 5):

histogram : [[8, 24, 40], [8, 40, 56], [24, 104, 168], [40, 104, 168], [40, 120, 184]]
imagequant: [{224, 213, 207}, {187, 154, 121}, {136, 151, 178}, {106, 98, 93}, {63, 122, 180}]

Takeaways

  • :histogram is two to three orders of magnitude faster than :imagequant. Its cost is dominated by a single pass over the pixels plus a small sort, and it barely scales with :top_n.

  • :histogram also scales well with image size. On the 300×328 PNG and the 1000×590 JPEG the histogram time is essentially unchanged (~0.1 ms), because hist_find_ndim is cheap relative to libvips' per-call overhead.

  • :imagequant has a fixed cost plus a component that scales with both pixel count and palette size. On the small PNG the fixed libvips/GIF-encode overhead (~3 ms) dominates. On the larger JPEG the quantise itself dominates: 55 ms for a 2-color palette rising to 156 ms for 16 colors.

  • :effort is a strong lever on the quantise itself. On the Singapore image, dropping from the default effort: 7 to effort: 3 cuts runtime by ~5× (148 ms → 30 ms) with only a small perceived quality loss. Effort 1 and 3 are indistinguishable in timing; effort 5 is about halfway; 10 roughly doubles effort 7.

  • Output quality differs in character. :histogram quantises to the centres of a fixed 3D grid (visible as the [8, 24, 40], [24, 104, 168], ... clustering), which is good enough for "what is the overall dominant color" questions. :imagequant returns perceptually representative colors suitable for building palettes, swatches, or UI accents from photographic input.

  • Rule of thumb: keep :histogram as the default for hot paths or bulk processing. Reach for :imagequant when palette quality matters more than latency, and consider effort: 3 if you want most of the quality benefit at a fraction of the CPU cost.