Privacy choices

Hue Codex uses local settings and privacy-aware measurement.

Required storage keeps tools and your local workspace working. Analytics helps improve popular tools; stricter consent regions stay opt-in unless you allow it. Marketing and personalized ads stay off until you choose them.

Abstract image sampling and processing methodology workspace with a sampled canvas image, eyedropper target, pixel grid, transparent alpha layers, extracted palette buckets, and duotone preview panels.

Short answer

Hue Codex processes user images locally in browser canvas, samples exact or averaged pixels, extracts palettes through coarse bucket aggregation followed by deterministic weighted OKLab centroid refinement, and renders duotone PNG output from canvas pixels.

  • Selected image files are not uploaded or sent to Hue Codex servers; they are read into the browser and processed locally by canvas APIs.
  • That image-file claim does not mean the entire page makes no network requests; ordinary page requests, scripts, fonts, analytics, or external assets are governed by the site privacy policy and should not receive selected image file contents.
  • Eyedropper samples can use exact pixels or averaged sample windows depending on the selected sample size.
  • Image palette extraction skips mostly transparent pixels and composites partial transparency over the Hue Codex paper color, #FBFAF5 (RGB 251, 250, 245).
  • Every accepted sample contributes to a final perceptual cluster; role suggestions select unmodified final cluster centroids and do not recolor the source palette. A centroid is a weighted average and may not match one literal source pixel.
  • Extracted image palettes are starting-point palettes for production review and should be refined against the final brand, UI, accessibility, and content context.
  • Duotone processing maps source luminance into a shadow-to-highlight ramp, with intensity, contrast, and midtone controls.

Standards status

These badges identify which parts of this methodology are standards-backed, draft-track, source-origin references, Hue Codex-specific, approximate, or dependent on browser behavior.

Browser-dependentApproximationHue Codex heuristic
Browser-dependentOutput depends on browser APIs, rendering, color management, canvas pixels, clipboard, download, or CSS support.
ApproximationModel or estimate with known limits, including CMYK, color-vision simulation, image palette extraction, or CSS duotone output.
Hue Codex heuristicHue Codex ranking, role hints, bands, labels, or workflow guidance rather than an external standard.

Formulas, choices, heuristics, and limits

This separates standards-based formulas from Hue Codex implementation decisions, product heuristics, and known limitations for this methodology.

Standards-based formulas

Formula, threshold, syntax, or data behavior taken from a cited standard. Primary and model sources can inform implementation, but they are not automatically standards.

  • WCAG contrast math can be applied after sampled or extracted colors are resolved.
  • CSS color conversion context supports the reported HEX, RGB, HSL, and OKLCH values.

Implementation choices

How Hue Codex chooses to parse, normalize, round, export, or sequence calculations.

  • Images are processed locally in browser canvas when browser security rules allow pixel access.
  • Selected image files for Image Eyedropper, Image Palette Extractor, and Duotone Generator are not uploaded or sent to Hue Codex servers.
  • Hue Codex requests ordinary 2D canvas contexts with willReadFrequently enabled and does not request display-p3 or other explicit canvas color-space settings.
  • Canvas pixel channels returned by getImageData() are treated as encoded sRGB-style 0-255 RGBA values for HEX, palette, contrast, and duotone calculations.
  • Image Eyedropper maps pointer coordinates from CSS pixels to intrinsic canvas pixels, then to browser-decoded source-image pixels using the drawn-image bounds.
  • Mostly transparent pixels are skipped and partial alpha is composited over the Hue Codex paper color, #FBFAF5 (RGB 251, 250, 245), before bucketing.
  • Duotone PNG export renders from canvas pixels at source dimensions when available.

Hue Codex heuristics

Product rankings, bands, labels, suggestions, or role hints that are useful guidance but not external standards.

  • Palette seed ranking, focus score, neutral merging, Delta E diversity thresholds, weighted OKLab refinement, and source-derived role selection are Hue Codex heuristics.
  • Duotone recommendations and overlay-text guidance are product review hints.

Known limitations

Caveats, edge cases, browser dependencies, approximations, or contexts the method does not prove.

  • Canvas pixels, image decoding, color management, and cross-origin rules are browser-dependent.
  • Hue Codex samples the browser-converted canvas pixel buffer, not original embedded ICC profile values, raw Display P3 pixels, or a profile-aware color-managed source representation.
  • Hue Codex does not parse EXIF orientation itself; source coordinates refer to the browser-decoded image orientation and canvas draw result.
  • Extracted image palettes are starting-point palettes for design review, not finished production color systems.
  • Palette extraction is approximate and depends on sampling quality, compression artifacts, and image content.
  • CSS duotone output is an approximation and may not match exported PNG pixels.

Representative automated fixture coverage

Hue Codex imports versioned JSON fixtures and tests the corresponding pure production functions directly. The groups below are the representative regression evidence most relevant to this methodology.

  • Image-sampling fixtures: Known RGBA pixel grids, coordinate bounds, alpha handling, averages, and deterministic samples. View the JSON fixtures.
  • Export fixtures: Displayed-versus-exported values, parseable CSS and JSON, and safe allowlisted SVG output. View the JSON fixtures.

What a pass means. A passing group confirms the named vectors and tested properties still match the documented implementation. It does not prove every possible input, final browser rendering, full accessibility conformance, profile-aware print output, or every downstream export consumer.

Local processing and privacy

Browser-dependent

Selected image files are not uploaded or sent to Hue Codex servers. Image Eyedropper, Image Palette Extractor, and Duotone Generator load the user-selected file in the browser, draw it to canvas, and calculate samples, palettes, previews, and PNG downloads from browser-held pixel data.

This claim covers the selected image file used by the image tools; ordinary page requests, analytics, fonts, scripts, or external assets are governed by the site privacy policy and should not receive image file contents.

Browser limitations, cross-origin image rules, and user-selected files still determine whether canvas pixel data can be read.

Verifiable local-image workflow.
StepWhat happens
File selectionThe tool reads the selected local file through the browser file input workflow and creates a local object URL.
Image decodingThe browser decodes the image locally and draws it into a canvas preview or source canvas.
Pixel accessHue Codex reads canvas pixels with getImageData() for eyedropper samples, palette extraction, and duotone processing.
PNG exportDuotone PNG download is generated in the browser with canvas.toBlob() and a temporary object URL.
Network behaviorThe selected image file is not posted, uploaded, or sent to a Hue Codex server by these tools.

Canvas color behavior

StandardBrowser-dependentApproximation

Hue Codex samples the pixels returned by the browser canvas, not the original file bytes or an embedded-profile color-managed source representation. The image tools create 2D canvas contexts with willReadFrequently enabled and do not pass a colorSpace option such as display-p3.

Because Hue Codex does not request a wide-gamut canvas, sampled channels are treated as encoded sRGB-style RGBA values in the 0..255 range. HEX, RGB, HSL, OKLCH, contrast, palette buckets, and duotone math are derived from those returned channel values.

If a browser decodes an ICC-profiled image, wide-gamut image, EXIF-oriented image, or compressed image differently before drawing it to canvas, Hue Codex reports the browser-converted canvas pixels. The image tools do not preserve Display P3 source coordinates, expose raw profile values, or perform profile-aware conversion.

Canvas color-space handling in Hue Codex image tools.
QuestionHue Codex behavior
Canvas context requestUses getContext("2d", { willReadFrequently: true }); no display-p3 or custom colorSpace option is requested.
Pixel sourceUses getImageData() from the browser-drawn canvas after image decoding, resizing, orientation handling, and browser color handling.
Reported color spaceTreats returned R, G, and B channels as encoded sRGB-style 0..255 values and serializes visible samples to HEX/RGB-derived formats.
Display P3 or wide-gamut imagesNot sampled as raw Display P3 coordinates. Any conversion into the canvas pixel buffer is browser behavior.
ICC/profile-aware conversionNot performed by Hue Codex image tools. Use a color-managed imaging workflow for profile-accurate source values.
Reproducibility scopeExact vectors are reproducible once canvas pixels are known; browser image decoding and canvas color handling can still affect real uploaded images.

Coordinate mapping

Browser-dependentApproximationHue Codex heuristic

Image Eyedropper converts the pointer location from viewport CSS pixels to the canvas backing-store coordinate system, then maps that point into the contained image bounds and finally to a browser-decoded source-image pixel.

Hue Codex does not multiply pointer coordinates by devicePixelRatio directly. It uses the ratio between the intrinsic canvas size and the rendered CSS box, so CSS scaling is handled by canvas.width / rect.width and canvas.height / rect.height.

Source coordinates are nearest source pixels after browser image decoding and drawImage() behavior. Hue Codex does not parse EXIF orientation itself, so coordinates follow the image orientation and pixel data exposed by the browser-rendered canvas.

CSS pixel to source pixel
rect = canvas.getBoundingClientRect()
canvasX = (event.clientX - rect.left) * (canvas.width / rect.width)
canvasY = (event.clientY - rect.top) * (canvas.height / rect.height)

if canvasX < bounds.x or canvasX > bounds.x + bounds.width:
  return no_sample
if canvasY < bounds.y or canvasY > bounds.y + bounds.height:
  return no_sample

normalizedX = (canvasX - bounds.x) / bounds.width
normalizedY = (canvasY - bounds.y) / bounds.height
sourceX = round(clamp(normalizedX * (sourceWidth - 1), 0, sourceWidth - 1))
sourceY = round(clamp(normalizedY * (sourceHeight - 1), 0, sourceHeight - 1))
Image Eyedropper coordinate mapping.
StepFormula or behavior
CSS pointer to canvas pixelcanvasX = (clientX - rect.left) * (canvas.width / rect.width); canvasY = (clientY - rect.top) * (canvas.height / rect.height).
Device pixel ratioNo explicit devicePixelRatio multiplier is applied; the canvas-to-CSS ratio handles scaling between the DOM box and the intrinsic preview canvas size used by that tool.
Image Eyedropper implementation noteThe current Image Eyedropper preview canvas is 900x520 intrinsic pixels. Other image tools or future responsive canvases should use their own intrinsic canvas size with the same canvas-to-CSS ratio formula.
Contained image boundsThe preview uses scale = min(canvas.width / naturalWidth, canvas.height / naturalHeight), then centers the drawn image. The paper-colored letterbox area is outside the source image.
Edge handlingPointer positions outside the drawn-image bounds return no sample. The right and bottom drawn-image edges are accepted, then clamped to the last source pixel.
Canvas to source pixelsourceX = round(clamp(((canvasX - bounds.x) / bounds.width) * (sourceWidth - 1), 0, sourceWidth - 1)); sourceY uses the same formula.
Sample windowExact mode samples 1x1. The 3x3 and 5x5 modes use radius = floor(sampleSize / 2); sample-window edges are clamped to the source image.
EXIF orientationHue Codex does not parse EXIF orientation; coordinates follow the browser-decoded image and drawImage() result, not the raw file pixel matrix.

Eyedropper sampling

Browser-dependentApproximation

Image Eyedropper maps pointer coordinates to the rendered image canvas, reads the selected pixel area, and returns HEX, RGB, HSL, OKLCH, contrast, and export metadata.

A sample size of one pixel reports the exact canvas pixel. Larger sample sizes average nearby pixels and can smooth JPEG artifacts, antialiasing, and texture noise.

Palette extraction

ApproximationHue Codex heuristicBrowser-dependent

Image Palette Extractor scans the sampled canvas at a quality-dependent pixel step. Pixels with alpha below 0.5 are skipped. Partially transparent pixels are composited over the Hue Codex paper color, #FBFAF5 (RGB 251, 250, 245), before bucketing.

Colors first enter deterministic rounded RGB buckets, with optional neutral merging. Those coarse buckets are converted to OKLab, ranked for the selected focus, used as deterministic starting centroids, and refined through weighted nearest-centroid assignment. Every sampled bucket contributes its count to a final cluster, so displayed coverage describes the final palette rather than only the initial seed buckets.

Image Palette Extractor does not reject a source merely because it exceeds 20 megapixels. Once the browser decodes a selected file, the tool immediately creates a bounded local working copy whose longest edge matches the highest supported extraction-quality input, reports both original and analysis dimensions, and releases the full-resolution image reference instead of retaining it for later palette renders.

The role shortlist does not generate or repair colors. It assigns four distinct, unmodified final cluster centroids to Surface, Text, Primary, and Secondary; derives higher-contrast black or white action foregrounds; and reports the five pairings shown in the specimen. A final centroid is source-derived because it is a weighted average of assigned source buckets, but it is not guaranteed to equal one literal source pixel. The resulting palette is evidence-backed source material, not a finished brand or design system.

When no unique source-only assignment passes all five shown pair contracts, Hue Codex preserves the colors, labels the role set incomplete, exposes every ratio through the role inspector, and offers recovery through UI-focused extraction, additional swatches, or a handoff to Palette Generator for deliberate fitting. The extractor never silently changes source-derived colors to force a pass.

Custom SVG and PNG palette cards are rendered from the derived swatches and supported settings. Layout, theme, value format, scale, branding, edit link, QR code, role labels, token names, and pair-specific accessibility notes can be changed independently. The local source image is not embedded, and an exact edit link restores supported settings and derived colors rather than source pixels or the local filename.

Palette extraction alpha handling.
Pixel alphaBehavior
alpha < 0.5Skip the pixel; it does not create or update a bucket.
0.5 <= alpha < 1Composite encoded RGB channels over Hue Codex paper #FBFAF5 (RGB 251, 250, 245), then bucket the resulting opaque RGB value.
alpha = 1Use the canvas RGB channels directly.

Palette extraction parameters

ApproximationHue Codex heuristicBrowser-dependent

The selected image is decoded by the browser, drawn to a sample canvas, then scanned at the pixel step for the chosen quality mode. The sample canvas longest side is capped by max dimension before scanning, so browser decoding and image resizing can affect the exact pixel stream.

Bucket keys are deterministic. For non-neutral buckets, each composited RGB channel is rounded to the nearest multiple of the quality bucket size, then clamped to 0..255. When neutral merging is enabled and channel spread is within the neutral threshold, Hue Codex uses one neutral key based on midpoint lightness instead of three separate RGB channel keys.

Weighted OKLab refinement is also deterministic. Quick uses three centroid updates, Balanced uses four, and Detailed uses five. Hue Codex always runs that fixed iteration count; it does not use an early-stop convergence test. Assignment ties keep the first centroid because a later centroid replaces the current choice only when its squared OKLab distance is strictly smaller. An empty cluster retains its prior centroid. Final centroids are converted to clamped, rounded sRGB channels for display. Final coverage is the assigned sample count divided by all accepted samples; average fit is the count-weighted Delta E 76 distance between coarse bucket representatives and their assigned final centroids.

Bucket key, seed ranking, and centroid refinement
# Partial alpha pixels are composited over Hue Codex paper #FBFAF5 first.
r = round(r * alpha + 251 * (1 - alpha))
g = round(g * alpha + 250 * (1 - alpha))
b = round(b * alpha + 245 * (1 - alpha))

spread = max(r,g,b) - min(r,g,b)
midpoint = (max(r,g,b) + min(r,g,b)) / 2

if merge_neutrals and spread <= quality.neutralSpread:
  key = "neutral-" + round(midpoint / quality.neutralStep) * quality.neutralStep
else:
  key = clamp(round(r / quality.bucketSize) * quality.bucketSize, 0, 255),
        clamp(round(g / quality.bucketSize) * quality.bucketSize, 0, 255),
        clamp(round(b / quality.bucketSize) * quality.bucketSize, 0, 255)

coverage = min(100, entry.coveragePercent * 7)
lightness = entry.oklch.l * 100
middleLightness = 100 - abs(lightness - 54)
surfaceBonus = entry.isNeutral and lightness >= 82 ? 30 : 0
inkBonus = entry.isNeutral and lightness <= 24 ? 30 : 0
accentBonus = entry.oklch.c >= 0.09 and 30 <= lightness <= 76 ? 22 : 0

dominant = coverage*1.45 + entry.oklch.c*70
balanced = coverage*0.82 + entry.oklch.c*210 + middleLightness*0.25
vivid = coverage*0.48 + entry.oklch.c*520 + middleLightness*0.15
ui = coverage*0.72 + surfaceBonus + inkBonus + accentBonus + middleLightness*0.14

diversityDelta = focus == dominant ? max(4, quality.diversityDelta - 3)
                                   : quality.diversityDelta

centers = select_ranked_seed_centroids(requested_count, diversityDelta)
repeat quality.iterations times:
  assign each coarse bucket to nearest center in OKLab
  update each center to the sample-count-weighted OKLab mean

finalCoverage(cluster) = assignedSampleCount(cluster) / acceptedSampleCount
Image Palette Extractor quality parameters.
QualityMax dimensionPixel stepRGB bucket sizeNeutral merge thresholdNeutral stepSeed diversityCentroid updates
Quick420 px5 px36 channel unitsspread <= 1828 channel units7 Delta E 76; dominant focus uses 43
Balanced640 px3 px28 channel unitsspread <= 1622 channel units9 Delta E 76; dominant focus uses 64
Detailed860 px2 px20 channel unitsspread <= 1216 channel units11 Delta E 76; dominant focus uses 85

End-to-end image palette pipeline

ApproximationHue Codex heuristicBrowser-dependent
Deterministic extraction and role-assignment sequence.
StepOperation
1. Decode locallyThe browser decodes the selected file; Hue Codex retains the original dimensions for reporting and creates a bounded local analysis canvas.
2. Sample canvas pixelsScan the analysis canvas with the selected quality mode's fixed pixel step. The same browser-decoded pixels and settings produce the same sample stream.
3. Apply alpha policySkip alpha below 0.5; composite partial alpha over #FBFAF5; keep fully opaque RGB channels.
4. Aggregate coarse bucketsQuantize RGB channels by quality bucket size, or use midpoint-based neutral keys when neutral merging applies. Every accepted sample increments one bucket.
5. Decorate candidatesConvert each non-empty bucket representative into Lab, OKLab, and OKLCH and calculate coverage, focus score, neutrality, and contrast metadata.
6. Initialize centroidsSort by focus score, bucket count, and HEX; select separated seeds, then backfill remaining non-duplicate HEX candidates when necessary.
7. Assign in OKLabAssign every coarse bucket to the nearest centroid by squared Euclidean OKLab distance. Equal-distance ties remain with the first centroid.
8. Recalculate centroidsReplace non-empty centroids with sample-count-weighted OKLab means. Empty clusters retain their prior centroid.
9. Run fixed refinementRepeat assignment and recalculation exactly 3, 4, or 5 times for Quick, Balanced, or Detailed. No convergence-based early exit is used.
10. Resolve output colorsRun one final assignment, convert non-empty OKLab centroids to clamped and rounded sRGB, and merge any centroids that resolve to the same HEX.
11. Calculate evidenceCoverage is assigned sample count divided by accepted sample count; average fit is count-weighted Delta E 76 from each bucket representative to its assigned centroid.
12. Select source rolesEvaluate all unique four-centroid assignments for Surface, Text, Primary, and Secondary before presentation sorting.
13. Validate shown pairsCalculate raw WCAG contrast for body text/surface, each action/surface, and derived black-or-white text on each action. Threshold decisions use raw ratios.
14. Present and exportApply the requested visual sort without changing centroid values or role assignment; render the same role values, ratios, status, and source-fidelity metadata in the specimen and exports.

Palette ranking tie-breaks

Hue Codex heuristicApproximation
Image Palette Extractor ranking and tie-breaks.
StageOrder and tie-breaks
Candidate poolAverage every non-empty coarse bucket, convert it to Lab, OKLab, and OKLCH, then calculate the selected focus score. No top-N truncation is applied before final cluster assignment.
Focus rankingSort by focus score descending, bucket count descending, then representative HEX ascending. The HEX tie-break keeps equal numeric cases deterministic.
Seed diversityWalk the ranked list and accept initial centroids whose minimum Delta E 76 from accepted seeds reaches the quality threshold. Backfill with the remaining ranked non-duplicate HEX values when necessary.
Centroid refinementAssign every coarse bucket to its nearest centroid by squared Euclidean distance in OKLab, then update centroids to count-weighted OKLab means for the fixed quality-dependent iteration count. There is no early convergence exit; equal-distance assignment keeps the first centroid; empty clusters retain their prior centroid.
Coverage sortFinal sort by assigned sample count descending. Exact ties keep refined cluster order.
Lightness sortFinal sort by OKLCH lightness descending, then assigned sample count descending.
Hue sortFinal sort by OKLCH hue ascending, then assigned sample count descending. Achromatic missing hue behaves as zero.
Best text contrast sortFinal sort by higher black/white text contrast ratio descending, then assigned sample count descending.
Role shortlistBefore display sorting, evaluate every unique four-color permutation for Surface, Text, Primary, and Secondary. Passing-check count dominates; normalized contract fitness, neutral-surface/lightness/chroma/coverage heuristics, then ascending role HEX tuple break ties. Derive higher-contrast black or white action text. No final centroid is modified.

Source-derived role selection and failure state

StandardHue Codex heuristic

A complete role set requires four distinct final cluster colors. Hue Codex evaluates every unique Surface, Text, Primary, and Secondary assignment independently of the selected presentation sort. It first maximizes how many of the five shown pair contracts pass, then compares normalized contract fitness, a visual-role heuristic, and finally the ascending Surface/Text/Primary/Secondary HEX tuple for deterministic ties.

The accessibility thresholds are scoped to the shown specimen pairings, not the palette as a universal system. Body text on Surface and derived black-or-white text on each action require 4.5:1. Primary and Secondary against Surface require 3:1 as UI/non-text checks. Pass/fail uses the unrounded ratio; displayed ratios are rounded only for reporting.

If fewer than four distinct centroids exist, no four-role set is assigned. If four or more exist but the best assignment does not pass all five checks, Hue Codex displays that best incomplete assignment with its failures and keeps every source-derived color unchanged. Users can request more swatches, use UI-focused extraction, or hand the extracted palette to Palette Generator when intentional color fitting is acceptable.

The five exact role-specimen contracts.
Shown pairingMinimumForeground sourceBackground source
Body text on Surface4.5:1Unmodified Text centroidUnmodified Surface centroid
Primary action on Surface3:1Unmodified Primary centroidUnmodified Surface centroid
Text on Primary action4.5:1Derived higher-contrast #000000 or #FFFFFFUnmodified Primary centroid
Secondary action on Surface3:1Unmodified Secondary centroidUnmodified Surface centroid
Text on Secondary action4.5:1Derived higher-contrast #000000 or #FFFFFFUnmodified Secondary centroid

Duotone processing

ApproximationBrowser-dependent

Duotone Generator estimates source luminance from RGB channels, maps that luminance through contrast and midtone controls, then mixes between the selected shadow and highlight colors. Intensity blends the duotone result back toward the original pixel.

Downloaded PNG output is rendered from source image dimensions when a source image is available. CSS duotone output is an approximation for live treatments and may not match the exact PNG pixels.

Duotone formula

ApproximationHue Codex heuristicBrowser-dependent

Hue Codex duotone rendering uses encoded 8-bit sRGB channel values from canvas pixels. It does not linearize sRGB channels, convert through Lab or OKLab, apply ICC profiles, or run a perceptual gamut-mapping step.

The luminance value is a tone-mapping signal calculated with WCAG-style coefficients on encoded RGB channels. Because the channels are not linearized first, this is not the full WCAG relative-luminance formula used for contrast conformance.

Canvas duotone equation
inputs:
  source pixel = r,g,b,a8 where r,g,b,a8 are canvas channels in 0..255
  shadow = Rs,Gs,Bs from shadow HEX
  highlight = Rh,Gh,Bh from highlight HEX
  intensity = clamp(controlIntensity / 100, 0, 1)
  contrast = controlContrast / 100
  midtone = controlMidtone / 100

# Encoded-sRGB tone signal. This is not WCAG relative luminance because
# the r,g,b channels are not linearized before applying the coefficients.
luminance = (0.2126*r + 0.7152*g + 0.0722*b) / 255

# Contrast expands or compresses tone around the midpoint.
tone0 = clamp(((luminance - 0.5) * contrast) + 0.5, 0, 1)

# Midtone is strongest at tone 0.5 and has no effect at tone 0 or tone 1.
midtoneWeight = 1 - abs((tone0 * 2) - 1)
tone = clamp(tone0 + midtone * midtoneWeight, 0, 1)

# Shadow/highlight ramp in encoded sRGB.
duoR = round(Rs + (Rh - Rs) * tone)
duoG = round(Gs + (Gh - Gs) * tone)
duoB = round(Bs + (Bh - Bs) * tone)

# Intensity blends from the original encoded RGB toward the duotone RGB.
outR = round(r + (duoR - r) * intensity)
outG = round(g + (duoG - g) * intensity)
outB = round(b + (duoB - b) * intensity)
outA = a8
Duotone channel and alpha behavior.
AreaDocumented behavior
Luminance domainEncoded canvas RGB channels in 0..255; no sRGB linearization before the luminance-like tone signal.
Ramp interpolation spaceEncoded sRGB channel interpolation between shadow RGB and highlight RGB endpoints; no linear-light, Lab, OKLab, or polar interpolation.
Control rangesIntensity is 0..100 percent, midtone is -50..50, and contrast is 60..160 percent in the tool UI. Missing values fall back to preset/default values.
Alpha channelThe source alpha channel is preserved. Hue Codex changes RGB channels only and does not premultiply alpha or composite duotone pixels over the paper color.
Transparent pixelsFully transparent pixels may have their RGB channels numerically processed, but alpha remains 0, so they stay transparent in the canvas result and PNG export.
Partial alpha pixelsPartially transparent source pixels keep their original alpha; their RGB channels are tone-mapped and blended by intensity without source-over compositing.

Synthetic-image validation fixtures

Browser-dependentApproximationHue Codex heuristic

These fixtures are small unprofiled sRGB PNGs with known pixel matrices. Expected values assume the browser returns matching encoded sRGB-style RGBA canvas pixels through getImageData(); browser decoding, profile handling, and resampling can still affect arbitrary real images.

The tiny 1x1 and 2x2 fixtures are intended for exact-pixel and coordinate tests. Palette stride tests should use the larger checkerboard and ramp fixtures, or a test harness with pixelStep = 1, so quality-mode sampling does not skip the intended pixels.

Synthetic image fixtures for image-tool validation.
FixturePixel definitionRequired assertions
1x1 red PNG1x1 image; pixel (0,0) = rgba(255,0,0,255).Exact Image Eyedropper source coordinate (0,0) returns #FF0000. Image Palette Extractor creates one red bucket when the sampled canvas contains only this pixel. Duotone intensity 0 keeps #FF0000.
2x2 RGBW grid2x2 image; row 0 = #FF0000, #00FF00; row 1 = #0000FF, #FFFFFF; all alpha 255.Exact Image Eyedropper returns (0,0) #FF0000, (1,0) #00FF00, (0,1) #0000FF, and (1,1) #FFFFFF. Use this fixture for coordinate mapping, edge clamping, and source-origin checks.
Transparent PNG1x1 or 2x2 image with alpha 0 for every pixel; RGB channel values are retained only as source channels.Image Palette Extractor skips every pixel because alpha < 0.5. Image Eyedropper reports the sampled zero alpha, preserves it in the swatch/export, and composites over its declared preview canvas only when an opaque contrast result is needed.
30x30 black/white checkerboardFour 15x15 quadrants: top-left #000000, top-right #FFFFFF, bottom-left #FFFFFF, bottom-right #000000; all alpha 255.Palette extraction includes both #000000 and #FFFFFF buckets. Expected sampled counts before ranking are Quick 18/18, Balanced 50/50, and Detailed 113 black / 112 white because detailed sampling steps by 2 pixels from the top-left origin.
256x1 grayscale ramp256x1 image; pixel (x,0) = rgba(x,x,x,255) for x = 0..255.Exact Image Eyedropper returns x=0 #000000, x=128 #808080, and x=255 #FFFFFF. This fixture validates source-coordinate mapping, channel rounding, neutral handling, and duotone luminance ramp behavior.

Validation checks

Browser-dependentHue Codex heuristic
Image tool QA checks.
CheckExpected behavior
1x1 #FF0000 PNG exact sampleReturns #FF0000
2x2 red/green/blue/white grid at coordinate 0,0Returns #FF0000 from the top-left pixel
Fully transparent pixelsSkipped in palette extraction
30x30 black/white checkerboard fixturePalette extraction includes both black and white; sampled counts are Quick 18/18, Balanced 50/50, Detailed 113/112 before ranking
256x1 grayscale ramp fixtureExact samples at x=0, x=128, and x=255 return #000000, #808080, and #FFFFFF
50% transparent black over paper #FBFAF5Composited to #7E7D7B before bucketing
Accepted-sample assignment and coverageEvery accepted sample is assigned to a final cluster and final-cluster coverage totals approximately 100 percent, allowing for displayed rounding
Sort invarianceCoverage, lightness, hue, and best-text sorts change presentation order only; centroid HEX values and the selected role assignment remain identical
Role provenanceSurface, Text, Primary, and Secondary each reference one distinct final swatch; none is recolored, and the inspector reports its swatch index and coverage
Role threshold decisionsAll five pass/fail values use raw WCAG contrast ratios; rounding is display-only
Incomplete source-only fixtureFour similar gray swatches produce an explicit incomplete role state, visible failed pairings, unchanged HEX values, and recovery actions
Specimen and export consistencyThe visible role values, five ratios, pass count, and incomplete/pass state match structured export data
Share privacyShare state restores derived swatches and supported settings without image bytes, object URLs, local filenames, or the source image
Deterministic replayThe same browser-decoded pixel matrix and extraction settings return the same centroids, coverage, roles, checks, and tie outcomes
Duotone #808080 at intensity 0 percentKeeps the source pixel #808080
Duotone #808080 at intensity 100 percentMaps to #294586 for shadow #10212B, highlight #4169E1, midtone 0, contrast 100%

Image extraction pseudocode

Browser-dependentApproximationHue Codex heuristic

These steps apply after the browser has decoded the image and exposed canvas pixels. Browser image decoding and color management can still change the input pixels.

Image palette extraction, bucketing, diversity, and ranking
quality presets:
  quick:    maxDimension 420, pixelStep 5, bucketSize 36, neutralSpread 18, neutralStep 28, diversityDelta 7
  balanced: maxDimension 640, pixelStep 3, bucketSize 28, neutralSpread 16, neutralStep 22, diversityDelta 9
  detailed: maxDimension 860, pixelStep 2, bucketSize 20, neutralSpread 12, neutralStep 16, diversityDelta 11

extract_palette(canvas, count, quality, focus, sort, merge_neutrals):
  for y from 0 to canvas.height step quality.pixelStep:
    for x from 0 to canvas.width step quality.pixelStep:
      r, g, b, a8 = canvas_pixel(x, y)
      alpha = a8 / 255
      if alpha < 0.5: continue

      # Composite partial alpha over Hue Codex paper #FBFAF5.
      r = round(r * alpha + 251 * (1 - alpha))
      g = round(g * alpha + 250 * (1 - alpha))
      b = round(b * alpha + 245 * (1 - alpha))

      spread = max(r,g,b) - min(r,g,b)
      midpoint = (max(r,g,b) + min(r,g,b)) / 2
      if merge_neutrals and spread <= quality.neutralSpread:
        key = "neutral-" + round(midpoint / quality.neutralStep) * quality.neutralStep
      else:
        key = clamp(round(r / quality.bucketSize) * quality.bucketSize, 0, 255),
              clamp(round(g / quality.bucketSize) * quality.bucketSize, 0, 255),
              clamp(round(b / quality.bucketSize) * quality.bucketSize, 0, 255)

      bucket[key].count += 1
      bucket[key].r += r; bucket[key].g += g; bucket[key].b += b

  candidates = average each bucket and convert to Lab, OKLab, and OKLCH
  ranked = sort candidates by focus_score(entry, focus), then count, then HEX

  diversity = focus == dominant ? max(4, quality.diversityDelta - 3) : quality.diversityDelta
  seeds = []
  for entry in ranked:
    if seeds is empty or min(delta_e_76(entry, seed)) >= diversity:
      seeds.append(entry)
    if seeds length == count: break

  if seeds has fewer than count:
    append highest ranked non-duplicate HEX entries as seeds

  centers = seeds converted to OKLab
  repeat quality.iterations times:
    assign every candidate bucket to its nearest OKLab center
    update each center to the bucket-count-weighted OKLab mean

  final clusters receive the total count of their assigned buckets
  return final clusters sorted by requested mode and labeled by role heuristic
Image palette focus score
focus_score(entry, focus):
  coverage = min(100, entry.coveragePercent * 7)
  lightness = entry.oklch.l * 100
  middleLightness = 100 - abs(lightness - 54)
  surfaceBonus = entry.isNeutral and lightness >= 82 ? 30 : 0
  inkBonus = entry.isNeutral and lightness <= 24 ? 30 : 0
  accentBonus = entry.oklch.c >= 0.09 and 30 <= lightness <= 76 ? 22 : 0

  if focus == vivid:
    return coverage*0.48 + entry.oklch.c*520 + middleLightness*0.15
  if focus == balanced:
    return coverage*0.82 + entry.oklch.c*210 + middleLightness*0.25
  if focus == ui:
    return coverage*0.72 + surfaceBonus + inkBonus + accentBonus + middleLightness*0.14
  # dominant
  return coverage*1.45 + entry.oklch.c*70

Reproducible test vectors

Browser-dependentApproximationHue Codex heuristic

These vectors validate the deterministic pieces of image processing once canvas pixels are available. Browser decoding and color management can still affect real uploaded images.

Image compositing, averaging, and duotone vectors.
InputExpected outputNotes
1x1 PNG with only #FF0000, exact pixel sample#FF0000Image Eyedropper one-pixel sample returns the only canvas pixel
2x2 grid [#FF0000, #00FF00; #0000FF, #FFFFFF], coordinate 0,0#FF0000Coordinates are source-image coordinates from the top-left origin
Fully transparent pixel in palette extractionSkippedAlpha below 0.5 is ignored; no bucket is created and total sampled-pixel count is unchanged
#000000 at 50% alpha over paper #FBFAF5#7E7D7Bround(0 * 0.5 + paper * 0.5) per encoded sRGB channel
#FF0000 at 50% alpha over paper #FBFAF5#FD7D7BPartial alpha is composited over the Hue Codex paper color before bucketing
Average of one #000000 pixel and one #FFFFFF pixel#808080Averaged channels are rounded to displayable sRGB
Duotone source pixel #808080, intensity 0%, shadow #10212B, highlight #4169E1, midtone 0, contrast 100%#808080Intensity 0 keeps the original encoded RGB pixel and preserves alpha
Duotone source pixel #808080, intensity 100%, shadow #10212B, highlight #4169E1, midtone 0, contrast 100%#294586Encoded-sRGB luminance is 128/255; mapped ramp pixel rounds to #294586
Duotone shadow #10212B, highlight #4169E1, luminance 0#10212B; tone = 0Shadow endpoint
Duotone shadow #10212B, highlight #4169E1, luminance 0.5#294586; tone = 0.5Midpoint ramp color
Duotone shadow #10212B, highlight #4169E1, luminance 1#4169E1; tone = 1Highlight endpoint
Same duotone settings, luminance 0.5, midtone +20%#3253AA; tone = 0.7Midtone control pushes the midpoint toward the highlight

Sources and standards

These references anchor the browser APIs, stable accessibility checks, CSS color syntax, and draft/browser-dependent CSS effects behind Hue Codex image tools. Canvas and CSS-rendered output remains browser-dependent, and CSS duotone handoff is an approximation of the canvas PNG render.

Tools using this methodology

These Hue Codex tools link to this methodology because they depend on the formulas, assumptions, limits, or data policy described here.

Report a calculation issue

If a result does not match the documented formula or fixture, send the exact input, tool settings, displayed result, expected result, precision mode, and browser details. Please do not include private images, personal information, or secrets.