Extract an Image's Dominant Color in Laravel 13.24
Laravel 13.24 shipped dominantColor() as part of the first-party image API, giving you an averaged hex color from any image without reaching for a third-party package. The most practical use case is filling the space a lazy-loaded image will eventually occupy, so the layout looks intentional before a single pixel of the real photo arrives.
Setup
The image API is backed by Intervention Image, which is a suggested dependency. Install it first:
composer require intervention/image:^4.0
Both the GD and Imagick drivers support dominant color detection.
Getting the Dominant Color
Call dominantColor() on any image instance to receive a hex string:
use Illuminate\Support\Facades\Image;
$color = Image::fromPath(storage_path('app/photo.jpg'))->dominantColor();
// "#8a6f4c"
Internally, the image is resized to a single pixel and that pixel's value is read. This produces an average rather than the most-frequent color, which is exactly what you want for a full-image placeholder. Images with an alpha channel return an eight-digit hex (#rrggbbaa), so size your database column to 9 characters.
Transformations applied before dominantColor() affect the sampled result:
$color = Image::fromStorage('uploads/photo.jpg')
->crop(800, 600, x: 200, y: 0)
->dominantColor();
Storing the Color at Upload Time
Compute the color once during upload and persist it on the model. Add a nullable column:
$table->string('dominant_color', 9)->nullable();
In the controller, call store() before dominantColor(). store() caches the processed bytes on the instance, so the subsequent color call samples those bytes at no extra cost:
$photo = $request->image('photo')
->orient()
->scale(width: 1600)
->optimize();
return Photo::create([
'path' => $photo->store('photos'),
'dominant_color' => $photo->dominantColor(),
]);
Reversing those two lines forces the pipeline to run twice — same result, double the work.
Using the Color as a Placeholder
With the color stored on the model, render a colored box that holds the correct aspect ratio while the image loads:
<div class="aspect-[3/2] overflow-hidden rounded-lg"
style="background-color: {{ $photo->dominant_color }}">
<img src="{{ Storage::url($photo->path) }}"
alt="{{ $photo->caption }}"
loading="lazy"
class="h-full w-full object-cover">
</div>
This pairs well with loading="lazy" in long galleries: the colored box is painted immediately, the image fades in over it, and the page never reflows.
Readable Text Over the Placeholder
A small helper using Rec. 709 relative luminance picks the right text color automatically:
class Color
{
public static function isLight(string $hex): bool
{
[$r, $g, $b] = sscanf(substr($hex, 0, 7), '#%02x%02x%02x');
return (0.2126 * $r + 0.7152 * $g + 0.0722 * $b) > 140;
}
}
<figcaption class="{{ Color::isLight($photo->dominant_color) ? 'text-gray-900' : 'text-white' }}">
{{ $photo->caption }}
</figcaption>
Letterbox and Rotation Fills
Pass 'dominant' to contain() or rotate() to fill exposed areas with the image's own color:
Image::fromUpload($request->file('photo'))
->contain(1200, 800, background: 'dominant')
->store('photos');
$image->rotate(8, background: 'dominant');
Sampling happens at that point in the pipeline, so an earlier crop changes the fill color.
What It Is Not
dominantColor() returns one averaged color. It is not a palette extractor and will not identify the accent color a designer would choose. For perceptual clustering or multi-swatch extraction, a dedicated library is still the right tool.
Key Takeaways
dominantColor()ships with Laravel 13.24 — no extra package beyond Intervention Image.- The method averages the image down to one pixel; it is ideal for placeholders, not palette work.
- Call
store()beforedominantColor()on the same instance to avoid processing the image twice. - Alpha-channel images return an 8-digit hex; store the column as
varchar(9). - Pass
background: 'dominant'tocontain()orrotate()for seamless letterbox fills.
Source: Extract an Image's Dominant Color in Laravel — Laravel News