4rn0/statamic-image-colors
A Statamic addon that extracts dominant colors from uploaded images, so you can build palettes for backgrounds, overlays, and theme accents directly in your templates. Lightweight, easy to configure, and handy for dynamic, image-driven design.
Install the addon with Composer, publish any config it ships, then run the extract command against an asset container (or select images in the asset browser and run the Extract colors action). The addon samples each image and writes its result to a single imagecolors key on the asset — there is no separate table or model to learn.
Look first at:
imagecolors value in the CP or via $asset->get('imagecolors'). That one array is the whole data model: colors (the palette), stamp (a marker proving the addon wrote it), and color (the pinned choice, if an editor made one).imagecolors as a field handle.First use case: give a hero image a palette, pin a color, and render it — {{ hero:imagecolors }} for the pinned/derived color, {{ hero:imagecolors:colors }} to loop the palette.
Treat imagecolors as a reserved namespace, not a field you own. Read from it, and write to it only through the addon's action or command. Don't add your own imagecolors field to a blueprint or merge values into that array — see gotchas.
Color rendering. {{ hero:imagecolors }} prints the chosen color; {{ hero:imagecolors:color }} gives you the same color with per-format modifiers; loop {{ hero:imagecolors:colors }} for swatches, palette chips, or a gradient fallback. {{ hero:imagecolors:is_pinned }} is the signal that an editor deliberately picked a color — use it to decide whether to show a "pin color" affordance or to trust the value blindly.
Conditions must use properties, not methods. This is the pattern that actually works now:
{{ if hero:imagecolors:color:is_dark }}
<div class="overlay overlay--dark">
{{ /if }}
{{ if hero:imagecolors:is_pinned }}…{{ /if }}
{{ if hero:imagecolors:color:contrast >= 4.5 }}…{{ /if }}
Readable text over images: use a band, not :contrast. Since :contrast measures against the solid extracted color, it tells you nothing about white text sitting on the photo. Put the title on a band filled with the image's color and test contrast against that:
<div class="hero" style="background-image:url('{{ hero:url }}')">
<div class="hero__band" style="background: {{ hero:imagecolors }}">
<h1>{{ title }}</h1>
</div>
</div>
GraphQL. The ImageColors type exposes the same three fields (color, colors, isPinned-style equivalents). Request it directly on the asset; no separate query or resolver plumbing is needed. Remember to refresh the GraphQL schema cache after install.
Batch extraction. Run the action on a selection rather than per-asset. Handle the two failure shapes: a hard failure message when nothing could be read (treat as an error, surface it), and the informational "Colors extracted for 1 of 2 images" when only some succeeded (treat as a warning, log the rest). Pair this with the stamp value to re-extract only assets whose stamp is missing or stale.
Idempotency. Because results are keyed and stamped, re-running extraction is safe. Schedule it if editors upload in bulk, or hook asset save and dispatch extraction for images lacking a stamp.
imagecolors field, or an asset holds an imagecolors value the addon didn't write, the addon does nothing — no sampling, no swatches, no overwrite — and the only signal is a log line. If extraction "doesn't work" for exactly one container, grep the log for the image path before debugging anything else. Fix by renaming your field handle; the namespace is not configurable.{{ hero:color }} and {{ hero:colors }} are gone. Grep your Antlers views for :color }}-adjacent patterns and any old imagecolors-less usage when upgrading. Any .blade.php or cached view using the old tags will render empty rather than error.{{ if … }} blocks around is_dark, is_pinned, or contrast that "never seemed to fire," they're not your bug — and they may now start rendering for the first time. Re-check layouts that depended on those blocks never showing.:contrast scope. It's relative to the solid color. Don't use it to validate text on a photo; the README's band approach is the correct pattern.$asset->get('imagecolors') in tinker.colors rather than trying to add fields into imagecolors.How can I help you explore Laravel packages today?