Skip to content

Color Palette Extraction - #1509

Open
olivervogel wants to merge 99 commits into
developfrom
feat/color-palette-lazy
Open

Color Palette Extraction#1509
olivervogel wants to merge 99 commits into
developfrom
feat/color-palette-lazy

Conversation

@olivervogel

@olivervogel olivervogel commented Aug 10, 2026

Copy link
Copy Markdown
Member

The ability to generate color palettes from an image instance.

// palette extraction
$popular = $image->colors()->popular(limit: 512);
$dominant = $image->colors()->dominant(limit: 5);
$theme = $image->colors()->theme(Intervention\Image\Colors\Theme::VIBRANT_MUTED);

// theme
$vibrant = $theme->vibrant;
$muted = $theme->muted;
$darkVibrant = $theme->darkVibrant;
$darkMuted = $theme->darkMuted;
$lightVibrant = $theme->lightVibrant;
$lightMuted = $theme->lightMuted;

// get color from palettes
$mostPopular = $popular->first();
$mostDominant = $dominant->first();

// color space transformation
$cmykPopular = $popular->toColorspace(Cmyk::class)

And more ...

@olivervogel olivervogel changed the title Color Palettes Color Palette Extraction Aug 10, 2026
@olivervogel
olivervogel marked this pull request as draft August 10, 2026 15:19
@olivervogel

Copy link
Copy Markdown
Member Author

@deluxetom I've moved a few things around. Mainly put the quantization logic directly to the palette and added a method to quantize but keep original channel values.

Thanks again for the support.

@deluxetom

Copy link
Copy Markdown
Contributor

@olivervogel last review, almost there :)

Worth fixing before merge (both cheap, both hit the documented $swatches['...'] API):

1. src/Colors/Swatches/AbstractSwatches.php:55 - offsetExists() uses property_exists(), so isset($swatches['vibrant']) returns true when the swatch is null (the normal "no match" result, see testFilterColorsNotFound). if (isset($swatches['lightMuted'])) { $swatches['lightMuted']->toHex(); } fatals. Use $this->{$offset} !== null.
2. AbstractSwatches.php:75 and :85 - offsetSet()/offsetUnset() accept any offset, so $swatches['foo'] = $color creates a dynamic property. Deprecated on PHP 8.3+, an Error in PHP 9. Guard against unknown offsets.

Worth a maintainer's opinion:

3. src/Analyzers/AbstractPaletteAnalyzer.php:25 - every sampled pixel calls $image->colorAt(), which goes through Image::analyze() and a full AbstractDriver::specialize(). Up to ~111k specializations per analysis on a 10000x10000 image. Reuse one specialized analyzer, or do a driver-level bulk pixel read.

@olivervogel

olivervogel commented Aug 16, 2026

Copy link
Copy Markdown
Member Author

Unfinished Business

### 1. Optimization of sample pixel collection

~~src/Analyzers/AbstractPaletteAnalyzer.php:25 - every sampled pixel calls $image->colorAt(), which goes through Image::analyze() and a full AbstractDriver::specialize(). Up to ~111k specializations per analysis on a 10000x10000 image. Reuse one specialized analyzer, or do a driver-level bulk pixel read.~~

✅ Done

### 2. Standardize SwatchesInterface and PaletteInterface

We could consider extending SwatchesInterface from PaletteInterface to standardize the handling, since they both do almost the same thing.

The main difference is that Palette objects use integers as iteration keys, while Swatches objects use strings.

The implementation of sorting functions in Swatches is also still undecided.

Swatches and palettes stay separated. If more functionality is needed it is always possible to convert to a palette. Renamed "swatches" to "theme".

### 3. Palettes and ColorFilters

I think it makes more sense overall if ColorFilterInterface::filterColors() returns its input (Palette objects) in the same type. Currently the method returns SwatchesInterface.

A new PaletteInterface::filter() method is a good option; it accepts ColorFilterInterface objects and returns PaletteInterface objects.

It's unclear where to put the swatch object instantiation.

Implemented PaletteInterface::filter() with simple callback. "Color filters" are now Palette definitions.

### 4. Pick most used color in reduce()

Pick most used color instead of just the first one.

✅ Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants