Image Color Picker Developer Reference
Technical map for Image Color Picker: the page shell, canvas sampling runtime, K-means++ palette extraction, color metric helpers, chart renderers, export surfaces, state API and Library integration.
Overview
Technical map for Image Color Picker: the page shell, canvas sampling runtime, K-means++ palette extraction, color metric helpers, chart renderers, export surfaces, state API and Library integration.
1. Code map
+| File | Role |
|---|---|
tool/general/tools/image-picker/index.html |
SEO shell, route markup, hero, tabs, upload controls, result panels, action panels, canvases and fullscreen chart overlay. |
js/tool/image-picker.js |
Self-contained runtime for image loading, canvas sampling, palette extraction, rendering, exports, batch analysis, keyboard shortcuts and state API. |
js/common/tool-gates.js |
Has no gate for /tool/general/tools/image-picker/: its note lists Image Color Picker
among the tools that open and run free on every
tier. |
js/library/tool-bindings.js |
Registers Image Color Picker as a Library palette tool and connects shared state capture
through window.AAImagePicker. |
js/generated-inline/644c56b21472624ee959.js |
Generated helper for fullscreen chart behavior used by the canvas chart wrappers. |
js/generated-inline/771583026d107068e662.js |
Shared generated bootstrap used across documentation and tool pages. |
The runtime is an IIFE. It does not import modules and does not send image data to a server. Any
remote image URL is loaded by the browser image element and then sampled only if canvas security
allows getImageData.
2. Page shell and UI contract
+
The route exposes ipl-app as the application root and uses three tab panels:
p-ipl-lab (Workbench), p-ipl-analysis (Analysis) and p-ipl-actions (Actions). There is no quick-start guide;
until an image loads, the right rail (ipl-readout-empty) shows the keyboard map.
| Area | Primary IDs |
|---|---|
| Source loading | ipl-drop-zone, ipl-file-input,
ipl-url-input, ipl-url-btn,
ipl-file-name, ipl-img-meta |
| Extraction controls | ipl-clusters-slider, ipl-quality-slider,
ipl-merge-slider, ipl-ignore-dark,
ipl-ignore-light, ipl-btn-extract,
ipl-btn-reset |
| Eyedropper | ipl-picker-swatch, ipl-picker-text,
ipl-picker-meta, ipl-picker-add,
ipl-picked-count, ipl-picked-strip |
| Palette output | ipl-empty-state, ipl-lab-content,
ipl-canvas, ipl-palette-swatches,
ipl-tone-shadows, ipl-tone-midtones,
ipl-tone-highlights, ipl-wcag-pairs,
ipl-harmony-host |
| Charts | ipl-hist-canvas, ipl-dist-canvas,
ipl-hsl-canvas, ipl-lum-canvas,
ipl-chart-fs-overlay, ipl-chart-fs-title,
ipl-chart-fs-close, ipl-chart-fs-canvas |
| Actions | ipl-btn-copy-hex, ipl-btn-copy-css,
ipl-btn-copy-json, ipl-btn-copy-link,
ipl-btn-download-json, ipl-btn-download-svg,
ipl-btn-download-csv, ipl-batch-input,
ipl-btn-batch, ipl-batch-results |
The runtime also maps ipl-btn-compare and ipl-compare-results if a
comparison surface is present. The current shell tracks history but does not render those two
comparison IDs in the visible Actions markup.
3. Runtime state
+
The central STATE object stores image, palette, eyedropper and settings data:
{
imageLoaded: false,
imageInfo: null,
imageData: null,
palette: [],
picks: [],
liveColor: null,
sampleCount: 0,
clusterCount: 6,
quality: 3,
mergeThreshold: 25,
ignoreDark: false,
ignoreLight: false,
history: []
}
imageInfostores original width, original height, canvas width, canvas height and source label.imageDatastores the sampled canvas pixels returned bygetImageData.paletteentries containr,g,b,hex,countandfraction.picksentries contain exact pinned RGB and HEX values.historystores extraction snapshots with label, palette and timestamp for comparison workflows.
4. Image loading and canvas sampling
+
loadFile(file) validates file.type.startsWith("image/"), creates an
object URL, decodes an Image and revokes the object URL after
drawImageToCanvas. loadUrl(url) creates an image with
crossOrigin = "anonymous" and asks the browser to load the URL directly.
drawImageToCanvas(img, label) scales the image to a maximum working canvas of
800 by 500 pixels, draws the decoded image and then calls getImageData. If the
canvas is tainted by CORS, sampling fails and the user sees the local-upload fallback message.
Pointer sampling uses getBoundingClientRect() to map viewport coordinates to
canvas pixels. The sampled byte index is (y * canvasWidth + x) * 4. Pinned colors
ignore pixels with alpha below 16.
5. Color math helpers
+The file implements the color conversions required for extraction, analysis and export:
rgbToHexandhexToRgbfor palette and batch I/O.rgbToHslandhslToRgbfor hue, saturation and lightness output.srgbToLin,linToSrgbandrgbToXyzfor linearized sRGB to XYZ conversion.xyzToLabandrgbToLabwith a D65 white point.labToLchfor cylindrical Lab output.deltaE00for CIEDE2000 palette comparison.relLum,contrastRatio,bestTextColorandwcagLevelfor accessibility summaries.
Lab and LCH values are practical browser-side metrics for comparing palette colors. They should be treated as computed analysis values from sRGB source pixels, not as device-profile-aware color-managed measurements.
6. Palette extraction pipeline
+
computePalette() is the main extraction pipeline. It reads STATE.imageData,
chooses a sampling step from QUALITY_STEP, spreads samples across the full image
and caps the estimated grid through MAX_SAMPLES = 50000.
- Loop through canvas pixels on the calculated sample grid.
- Skip nearly transparent pixels.
- Optionally skip near-black or near-white pixels using WCAG relative luminance.
- Record sampled RGB values in a flat
Float32Arrayand exact HEX counts. - If exact unique sampled colors are no more than the requested clusters, use exact colors.
- Otherwise run
kmeanspp(samples, n, clusterCount, 20). - Apply
mergeClusters(clusters, mergeThreshold). - Sort clusters by population count and compute
fraction. - Push a snapshot into
STATE.historyand render results.
This exact-color fallback matters for logos, icons, UI screenshots and flat illustrations. It prevents K-means from inventing averaged colors when the image already has a small set of real discrete colors.
7. Rendering and charts
+
renderPalette() coordinates the result UI. It toggles empty/content states,
writes palette and sample counts, renders swatches, builds tone bands, computes WCAG pairs,
renders harmony rows and draws the four charts once the Analysis tab is visible.
drawHistogramrenders cluster population bars.drawDistributionrenders the color distribution donut.drawHslScatterplots hue against saturation.drawLuminanceBarsrenders sorted WCAG relative luminance values.
prepCanvas(id) handles device pixel ratio scaling and canvas setup. The
chartColors() helper returns fixed grid, label and stroke colors for the workbench,
which is dark in both themes. Resize handling redraws charts after a short debounce.
Tone bands are created by classifyTone(L). Harmony rows come from
analyseHarmony(palette), which looks for approximate hue relationships such as
complementary, analogous, triadic, split-complementary and tetradic matches.
8. Exports, batch analysis and shortcuts
+
Export helpers build data from the current runtime state and use a shared
downloadBlob(data, name, mime) helper for file downloads.
getPaletteHexes()returns extracted palette HEX values.getCSSVars()writes a:rootblock using--palette-01style variables.getJSON()includes image info, settings, sample count, RGB, HSL, Lab, LCH, luminance, population, count, pins and export timestamp.getSVGPalette()creates a simple labeled SVG palette strip.exportCSV()writes rows with index, HEX, RGB, HSL, luminance, population and count.
runBatchAnalysis() reads ipl-batch-input, tokenizes hex colors by
newline, comma, semicolon or whitespace and renders a table with RGB, HSL, Lab, LCH, luminance
and tone classification.
Keyboard shortcuts are registered globally but ignored when focus is inside inputs, textareas or
selects. Supported keys are J for JSON, H for HEX, R for
reset and E for re-extract.
9. Public API and restore contract
+
The tool exposes window.AAImagePicker for Library and integration workflows:
window.AAImagePicker = {
getState: function () {
return {
imageInfo: STATE.imageInfo,
palette: STATE.palette.slice(),
picks: STATE.picks.slice(),
sampleCount: STATE.sampleCount,
settings: {
clusterCount: STATE.clusterCount,
quality: STATE.quality,
mergeThreshold: STATE.mergeThreshold,
ignoreDark: STATE.ignoreDark,
ignoreLight: STATE.ignoreLight
},
history: STATE.history.slice(-8)
};
},
restoreState: function (saved) { ... }
};
restoreState(saved) restores settings, image info, palette, picks, sample count and
recent history, updates slider and checkbox controls, updates visible setting labels, rerenders
palette and pins, and clears the live eyedropper preview. It does not restore raw
ImageData; source images must be reloaded before canvas sampling can continue.
10. Library binding
+The Library binding registers the route as a palette-producing creative tool:
{
id: "image-picker",
name: "Image Color Picker",
href: "/tool/general/tools/image-picker/",
category: "Creative",
asset_type: "palette"
}
- Shared state: the global capture map stores
imagePicker: window.AAImagePicker?.getState. - Shared restore: the restore hook maps
imagePickerpayloads towindow.AAImagePicker?.restoreState. - Palette asset: capture reads
window.AAImagePicker.getState()and stores the unique HEX values of the palette and the pins underasset_data.colors, with the image's file name assource_label. With nothing extracted yet it saves an Image Color Picker settings preset instead. - Restore: a save made in the picker comes back through the runtime-state
pass. A palette or color saved elsewhere (the binding accepts
paletteandcolorassets) is passed toAAImagePicker.restoreState()as the palette, each color an equal share because there is no image behind it; 3- and 8-digit HEX values are normalized to 6. - Source images: large image pixels are not stored. If the payload includes a source image reference, restore messaging asks the user to reload it for further picking.
Implementation note: capture reads AAImagePicker.getState() rather than DOM IDs, so
changes to the ipl- markup do not affect it. Keep the shape of
getState() stable instead.
11. Testing checklist
+- Open
/tool/general/tools/image-picker/and verify it opens with no plan gate. - Load PNG, JPEG, WebP and at least one transparent image through file upload.
- Load a remote image URL that permits CORS and one that blocks CORS, then verify messaging.
- Change cluster count, quality, merge threshold, Ignore dark and Ignore light; confirm extraction reruns and sample counts update.
- Hover canvas pixels and verify HEX, RGB, HSL, luminance and pixel coordinates.
- Pin colors, avoid duplicate pins and click swatches to copy HEX.
- Verify palette swatches, tone bands, WCAG pairs and harmony output for photo and flat art images.
- Check all four canvases in light and dark mode and after window resize.
- Copy HEX, CSS and JSON; download JSON, SVG and CSV.
- Run batch analysis with newline, comma, semicolon and space separated hex colors.
- Test keyboard shortcuts
J,H,RandEoutside focused form controls. - Save to Library and restore a palette through
AAImagePicker.restoreState(). - If exposing palette comparison in the UI, add
ipl-btn-compareandipl-compare-results, then load two or more images and run the comparison matrix. - Regenerate documentation RSS, feed, sitemap, PWA cache manifest and search index after docs changes.
For user-facing workflow details, see the Image Picker User Guide.