-
Notifications
You must be signed in to change notification settings - Fork 0
Facet Type Visual DNA
🎨 Shipped (experimental).
Drop an image, paste an image URL, or eyedrop any color visible on screen — the catalog filters to products in the closest matching color term.
A "view" facet that drives an existing color taxonomy or meta facet. Three input modalities collapse to the same pipeline:
image → 96×96 canvas → quantize → dominant hex → LAB ΔE → nearest term → store.set(target, [slug])
No new resolver path, no per-product image processing at index time. The match runs entirely on the values that already live in the target color facet's index, so it's instant and deterministic.
- Storefronts where shoppers come in with a color in mind ("find me something this color")
- Inspiration-driven shopping — drop a Pinterest image, find catalog matches
- Visual-first product categories: apparel, home goods, art, paint, furniture
- Mobile, where typing the right color name is the wrong UX
- Stores without color attributes — there's nothing to match against
- Catalogs where "color" isn't the primary visual axis (pattern, texture, shape matter more) — Visual DNA only matches on dominant color
- Catalogs that need true visual similarity (not just nearest color) — that's Visual DNA v2 → indexed ΔE, still on the roadmap
{
"name": "visual_dna",
"kind": "view",
"display": "visual_dna",
"label": "Visual DNA",
"settings": {
"target_facet": "color"
}
}| Field | Values | What |
|---|---|---|
kind |
"view" |
Required |
display |
"visual_dna" |
Required |
settings.target_facet |
facet name | The color-bearing facet to drive. Must have display in {checkbox, radio, dropdown, swatch, swiper}. |
source |
— | Not used |
| Modality | How |
|---|---|
| Drop a file | Drag an image onto the drop zone, or click it to open a file picker. Works for any browser. |
| Paste a URL | Type / paste an image URL into the field and press Enter. The image host must send permissive CORS — otherwise the browser blocks the canvas read and the user sees a friendly fallback message. |
| EyeDropper | Click the 🎨 Pick button to enter the browser's native EyeDropper, then click any pixel anywhere on screen. Chromium-only (Chrome, Edge, Brave, Opera) as of late 2025. The button is feature-detected and hidden where unsupported. |
The frontend extracts one dominant color from the input via canvas sampling:
- Downsample the source image to 96×96 (cheap, GPU-friendly, kills JPEG noise).
- Quantize each pixel to 4 bits per channel → 4096 buckets total.
- Pick the heaviest bucket that isn't near-white or near-black — unless those are a clear majority, in which case keep them.
- Return the bucket-averaged RGB as a hex.
That hex is then compared against the target facet's color terms in CIE LAB ΔE76 space. The smallest ΔE wins; that term's slug is applied as a filter via the normal store pathway.
The renderer builds the term-color map server-side and inlines it on the wrapper element so the JS has no extra network round-trip:
| Target facet kind | Source of hex per term |
|---|---|
| Taxonomy |
term_meta.swatch_color (same key the Color Swatch facet uses), falling back to a built-in CSS-name table. |
| Meta / field | Distinct facet_value rows in the index, looked up against the built-in CSS-name table. |
The built-in table covers ~35 common color words (red, navy, olive, burgundy, salmon, khaki, mint, etc.) — enough that a vanilla catalog with no per-term color meta still works. Terms that can't be resolved are silently dropped.
Visual DNA writes through to the underlying color facet — it has no URL key of its own.
?_hof_color=red
Identical wire format to a normal checkbox/radio/dropdown selection. Deep links survive a display swap; the chip in the active-filters bar reads the same.
When the indexer has run since plugin v0.1.x landed, every product with a featured image carries a stored dominant-color LAB triplet. Visual DNA upgrades automatically:
| Step | What happens |
|---|---|
| 1. Schema |
wp_hof_index gains lab_l, lab_a, lab_b columns (DB version 1.2.0, dbDelta auto-migration). |
| 2. Extract | At index time, VisualDna\ColorExtractor loads the product's featured image via WP_Image_Editor, downsamples to 96 px on the long side, runs the same quantize → dominant-bucket → average algorithm as the storefront JS, and writes one _visual_dna_lab row per product. |
| 3. Query | The storefront posts to /wp-json/hof/v1/visual-dna {hex, limit}. The endpoint converts the hex to LAB and returns the top-K products ranked by ΔE76, computed in SQL. |
| 4. Apply | The frontend pushes the ranked ID list through the store as _visual_ids. The resolver intersects with whatever other facet filters are active, preserving ΔE order so subsequent facet narrowing doesn't break the ranking. |
| 5. Fallback | If no LAB rows are indexed yet (no featured images, indexer hasn't run since the migration), the endpoint reports indexed_count: 0 and the frontend gracefully falls back to v1 (snap to nearest term). |
Result: a burnt-orange couch ranks higher than a sage-green couch when the input is salmon — even when both share the same "orange" color term — because the per-product LAB lives in the index.
Even with v2:
- Color only. Pattern, shape, texture still aren't factors. The dominant color is one point in LAB space; a striped red-and-blue shirt and a solid violet shirt may land near each other.
- One color per product. Products with multiple distinct colors collapse to their heaviest non-extreme bucket. Per-product palette matching is a future v3.
- Index time cost. Each reindex'd product with an image pays ~30–80 ms for the extraction. On a 100k-product catalog the initial extraction is meaningful — plan for it on import, or batch via cron. Subsequent saves only re-extract the affected product.
| Modality | Support |
|---|---|
| Drop file | Universal |
| Paste URL | Universal — but cross-origin images require permissive CORS on the host |
| EyeDropper | Chromium-only (~80% global usage). Gracefully hidden in Firefox/Safari. |
apply_filters( 'hof_visual_dna_color_map', $map, $target_facet );
apply_filters( 'hof_visual_dna_css_name_map', $fallback );
apply_filters( 'hof_visual_dna_match', $term, $hex, $map );- Color Swatch — the visual-by-attribute alternative (shopper picks from a set of squares, not from arbitrary input)
- Ask — natural-language alternative when "find me things this color" lives in a longer sentence
- Architecture — how view facets fit
hooked on facets · Filtering, finally fun. · GitHub · hookedonfacets.com
Filtering, finally fun.
📖 docs
🧠 concepts
🎛️ facet types
- All Types
- Checkbox
- Radio
- Dropdown
- Range Slider
- Date Range
- Search
- Hierarchy
- Color Swatch
- Swipe Deck
- Spin the Wheel
- Intersection Matrix
- Ask
- Visual DNA
- Toggle
- Saved Bin
- Pagination
🔧 develop
🗺️ project