brianmcdo/image-palette
ImagePalette is a PHP package for extracting color palettes from images. It analyzes JPEG, PNG, and GIF files to return dominant colors with percentages, making it easy to build palettes for thumbnails, design tools, or Laravel applications.
The package is a thin wrapper around imagine/imagine that reduces an image to a set of representative colors, optionally snapping them to a whitelist. It is not a Laravel package — there is no service provider, no config, no facade. You install it with Composer and instantiate it yourself:
composer require brianmcdo/image-palette
If you don't already have an Imagine adapter, add one (imagine/imagine plus ext-gd or ext-imagick):
composer require imagine/imagine
First use case — pull the dominant colors from an uploaded image:
use BrianMcDo\ImagePalette\ImagePalette;
use Imagine\Gd\Imagine;
$palette = new ImagePalette(
new Imagine(),
Storage::path('uploads/cover.jpg')
);
$colors = $palette->getColors(5); // array of Color objects
$hex = array_map(fn ($c) => $c->getHex(), $colors);
// ['#1a2b3c', '#e5e5e5', ...]
The README on GitHub is the only documentation — read it once, then read src/ImagePalette.php and src/Color.php directly. The whole library is a few hundred lines; the source is the real reference. Look first at getColors(), getPalette(), addColor(), and setWhitelist() — those four methods cover 95% of real usage.
Pick a first use case that is low stakes: generating a placeholder gradient from a hero image, tinting a badge based on a product photo, or picking a fallback button color. Don't start by feeding user uploads through it in a hot path — see the Gotchas section.
Whitelist snapping for brand consistency. The package's differentiator is mapping extracted colors against a fixed set (e.g. your brand palette) so output is on-brand rather than arbitrarily extracted:
$palette = new ImagePalette(new Imagine(), $path);
$palette->setWhitelist([
[255, 0, 0], // brand red
[0, 0, 0], // black
[255, 255, 255],
]);
$brandColors = $palette->getColors(3);
Use this when you want "which brand color best represents this image" — e.g. picking a category tile background.
Wrap it in a small service, not scattered across controllers. Because the library is unmaintained, you want one seam to swap later:
namespace App\Services;
class PaletteExtractor
{
public function __construct(private int $limit = 5) {}
public function fromPath(string $path): array
{
$palette = new \BrianMcDo\ImagePalette\ImagePalette(
new \Imagine\Gd\Imagine(),
$path
);
return array_map(
fn ($c) => $c->getHex(),
$palette->getColors($this->limit)
);
}
public function fromUploadedFile(UploadedFile $file): array
{
// ImagePalette wants a filesystem path, not an UploadedFile
return $this->fromPath($file->getRealPath());
}
}
Bind this in a service provider so tests can fake it. This is the single most important pattern with an abandoned dependency: isolate it.
Cache aggressively. Extraction is CPU-bound (it decodes the image pixel by pixel). Store results keyed by the image's content hash:
$key = 'palette:' . sha1_file($path);
$hexes = Cache::rememberForever($key, fn () => $extractor->fromPath($path));
Store an array of hex strings, not Color objects — the objects aren't serializable in a meaningful way.
Queue it for uploads. In a ProcessImagePalette job after upload, write the palette to the model. Controllers should not block on image decoding for a 4 MB JPEG.
Combine with intervention/image for everything else. Don't stretch ImagePalette beyond color extraction. Use Intervention (or Imagine directly) for resize/crop/format, then hand the final path to ImagePalette. Two tools, two jobs.
Integration tip: guard with a feature check. Since maintenance is dead, wrap calls in a config flag (config('features.image_palette')) so you can disable it per-environment if a PHP upgrade breaks it, rather than the whole app.
It is abandoned since 2015. Last release was Nov 2015. Expect friction on PHP 8.1+ and modern Imagine versions. Run composer why-not brianmcdo/image-palette and test on your target PHP version before committing. If getColors() returns empty or throws a deprecation error, the fix often lives in your Imagine adapter, not in this package.
Gd vs Imagick matters. The Imagine adapter you pass in is what actually reads pixels. Gd is more available but slower and memory-hungry on large images. Imagick is faster but a dependency. Always downscale large images before extraction — a 4000×3000 JPEG will happily exhaust memory. Resize to ~200px on the long edge first; the palette is identical.
getColors() vs getPalette(). Read the source — the two methods have different semantics around whitelist application and sorting. Don't assume one is an alias of the other. Test both against a known image and inspect the output.
Color objects are not strings. getColors() returns Color instances. You must call getHex() or getRgb() (check src/Color.php for the exact accessors). Casting or json_encode-ing the array will not do what you want. Map to primitives at the boundary.
Whitelist is set, not merged, and persists on the instance. setWhitelist() replaces the internal list. If you reuse a palette instance across images (you shouldn't), state leaks. Instantiate fresh per image.
Memory limits bite before the exception does. Image decoding failures surface as fatal OOM errors, not catchable exceptions. Set a sane memory_limit in the queue worker and reject oversized uploads at validation, before the job runs. Do not rely on try/catch here.
Transparency and grayscale inputs. PNGs with alpha and near-monochrome images can produce surprising/extreme palettes. If you need "the dominant non-white color," you may need to filter near-white/near-black results yourself — there is no built-in filter.
Test with fixtures, not user uploads. Add a couple of small fixture images (tests/Fixtures/) with known palettes and assert against them. This gives you an early warning if a future PHP or Imagine bump breaks extraction, which is the realistic failure mode for an abandoned dependency.
Know when to replace it. For new work, or if this package fights your PHP version, the equivalents are league/color-extractor (actively small, similar scope) or doing k-means on Imagick pixels yourself. Because you isolated the package behind PaletteExtractor, swapping is a one-file change — that's the whole point of the service wrapper.
How can I help you explore Laravel packages today?