Checking images against the brand: colour, contrast, sharpness and logo placement

What the platform's eight image measurements read, what pass, warn and not_evaluated mean, which limits can be set, and six worked examples measured on a real site image.

21 min read By Platform Documentation Guide #Testing #Publishing

What the platform's eight image measurements read, what pass, warn and not_evaluated mean, which limits can be set, and six worked examples measured on a real site image.

Outcome

Check an image against the site's rules before it goes on a page

A site's images have to use its colours, keep overlaid text readable, stay sharp, fit the slot they are placed in and carry the logo cleanly. Checking that by eye works for one image and breaks down at fifty. Kugel Platform has eight read-only measurements that read an image's pixels and report numbers against limits the site can set.

Each measurement returns a status of pass, warn or not_evaluated next to the numbers behind it. None of them blocks a save, a publish or a release, and none judges whether an image is good or on message. They tell an editor or an agent what the file contains, so the decision is made on facts.

This article defines the eight measurements, the order to use them in, the limits you can set and where they are set. Six worked examples follow, each measured live on a real image from this site on 15 October 2026, with the numbers in tables. It ends with the ways the checks can mislead and what they do not decide.

Core terms

The eight measurements

Each measurement has its own tool. All of them are synchronous: the answer comes back in the same call, nothing is queued, and nothing is written or stored.

  • Palette (measure_palette). Finds the image's dominant colours and measures how far each one is from a brand palette you supply. The distance is delta E (CIEDE2000): 0 is an exact match, and larger numbers are more visible differences.

  • Contrast (measure_contrast). Computes the WCAG 2 contrast ratio between a text colour and the part of the image behind the text, or against a background colour you name. A ratio of 1 means identical brightness; higher is easier to read.

  • Image quality (measure_image_quality). Reports sharpness, noise, clipped shadows and highlights, whether the frame is blank, JPEG blockiness and, when you give a target size, whether the image has enough pixels.

  • Safe zone (measure_safe_zone). Reads how busy named rectangles of the image are, and reports any placed box, such as a headline or a logo, that overlaps them.

  • Geometry (measure_image_geometry). Reports width, height, aspect ratio, orientation, format and file size, and checks them against the expectations you give.

  • Perceptual similarity (measure_perceptual_similarity). Compares two images the way a person would: structural similarity (SSIM) on a reduced grey copy, plus a 64-bit fingerprint called pHash, whose distance counts the bits that differ.

  • Pixel difference (compare_pixels). Counts the pixels that differ between two images and reports where the differences are.

  • Logo placement (verify_logo_placement). After project_planar_decal places a logo on an image, re-reads the region where the logo should be and compares it with the original logo file.

Two more terms. The subject is the image being measured. The reference is the second image in a comparison.

Core terms

What pass, warn and not_evaluated mean

pass means every number the check computed is inside its limit.

warn means at least one number is outside a limit, or the check found a condition it names, such as two images of different sizes. The report still returns every number. Some checks also name the limit that tripped; others return only the status, and you compare the numbers with the limits yourself.

not_evaluated means the check had nothing to compare against, so it gives no verdict. A palette check with no palette, a geometry check with no expected size and the resolution part of a quality check with no target each return not_evaluated, and still report whatever they measured. A not_evaluated is not a pass: that aspect has not been checked at all.

There is no fail status. A warn is information for the person or agent deciding what to do next. The measurements never stop a save, a publish or a release.

Relationship

The order to use them in

An agent connected to the site, or an editor working through the admin chat, runs the checks in this order. The tool order below is what we verified; how the admin chat phrases a request was not exercised for this article.

  1. Put the image in the right request. A measurement reads only images that belong to the article or page being worked on. An image from another article is adopted first with adopt_artifact, which links it without copying bytes. The comparison tools can adopt their reference for you when you name the request it came from.

  2. Read the site's rules. Brand colours and image proportions are stored on the site object: its imagery rules hold the palette and the aspect ratios, 16:9 for article headers and 4:3 for article bodies. Limits live in the image-analysis policy.

  3. Run the checks the job needs. Palette and quality on any new image. Contrast and safe zone before text goes on it. Geometry before it takes a slot. Similarity and pixel difference when an edit should have left the image alone. Logo placement after a logo is placed.

  4. Read the numbers, not only the status. A pass close to its limit is a different situation from a pass with room to spare.

  5. Decide, repair if needed, and measure again. Resizes, crops and recolours are deterministic edits that cost nothing. Repair is covered in the companion article listed under Related reading.

Settings and limits

Limits you can set, and where

Every check ships with default limits, and there are two ways to change them.

A per-call limit is passed with the measurement and applies to that call only. Every example below that mentions a changed limit used this route. Per-call policy values are merged over the site's policy, and the report returns the policy it used.

The site's image-analysis policy holds the standing values for the whole site: the quality limits, the safe-zone quiet-field settings, the analysis grid and the working size. It is read with get_image_analysis_policy and changed with set_image_analysis_policy. On 15 October 2026 this site had no stored policy of its own, so the defaults in the two tables below were in force. We read the policy and did not change it.

Values outside the allowed range are refused, not clamped. A sharpness floor of -5 came back as: "Invalid image analysis policy: quality.blurMinLaplacianVariance: must be between 0 and 100000 (got -5)". Images larger than 4096 pixels on the long side, or 16,777,216 pixels in total, are scaled down for analysis rather than refused.

Measurements are reads, so they do not count against the write limit. Creating variant images to compare against does count: when we ran these examples this tenant allowed 60 writes per 10 minutes per member, agents working as the same member share that allowance, and we hit it once and waited a few minutes.

Defaults for colour, contrast and quality, as read on 15 October 2026

DefaultPer-call name
Palette: largest average distance (delta E)10max_mean_delta_e
Palette: largest single-colour distance20max_color_delta_e
Contrast: lowest worst-case ratio4.5min_ratio
Quality: lowest sharpness (Laplacian variance)100blurMinLaplacianVariance
Quality: highest noise (sigma)8noiseMaxSigma
Quality: most clipped shadows (% of pixels)5clipMaxShadowPercent
Quality: most clipped highlights (% of pixels)5clipMaxHighlightPercent
Quality: blank-frame floor (brightness spread)4blankMaxLumaStdDev
Quality: highest JPEG blockiness1.35blockinessMax

Defaults for geometry, safe zone, comparison and logo, as read on 15 October 2026

DefaultPer-call name
Geometry: aspect-ratio tolerance (relative)0.01aspect_tolerance, inside expected
Safe zone: highest edge density0.1max_edge_density
Safe zone: largest overlap with a placed box0 (any overlap)max_overlap_ratio
Similarity: lowest SSIM0.9min_ssim
Similarity: largest pHash distance10max_phash_distance
Pixel difference: per-pixel sensitivity0.1pixel_threshold
Pixel difference: largest share of pixels that differ0.01max_diff_ratio
Logo: lowest geometry similarity0.85min_geometry_similarity
Logo: largest colour distance (delta E)8max_color_delta_e
Logo: lowest edge overlap0.6min_edge_iou
Logo: largest pHash distance12max_phash_distance

Example 1

Palette: are this image's colours the brand's colours?

The subject is this site's home image: a handwritten notebook on the left and a typeset article spread on the right, joined by a thin line, on a near-black ground. The target is the site's brand palette, eight colours stored with its imagery rules: three near-black browns, two off-whites, terracotta (#cc785c), gold (#bf9b30) and a muted grey-brown.

Called without a palette, the tool lists the image's five dominant colours and returns not_evaluated. It does not look up the site's colours itself, so whoever calls it supplies them. Called with the palette, each dominant colour is matched to its nearest brand colour and the distance is reported.

The site's home image: a handwritten notebook on the left and a typeset magazine spread on the right, joined by a thin line with three small markers, on a near-black background.
The measured subject: a 1536 by 1024 image whose largest colour groups are near-black and off-white, close to the brand's own.

Dominant colours against the brand palette (limits 10 and 20)

Share of imageNearest brand colourDelta E
#2e2c2b31.5%#2d2a262.10
#e9e3d525.7%#f4f2ec4.76
#25242418.1%#2d2a263.38
#ccc3b515.3%#f4f2ec11.35
#9d68449.5%#cc785c12.45

The weighted mean distance was 5.41 and the worst single colour 12.45, both inside the defaults of 10 and 20, so the status is pass. The three largest groups are within 4.8 of the brand's near-blacks and off-whites. The two largest gaps are the pale grey #ccc3b5 (11.35) and the terracotta of the magazine spread (12.45 from #cc785c).

The same image under other conditions:

Palette results for the original and four variations

Mean delta EWorst delta EResult
Original, default limits (10 and 20)5.4112.45pass
Original, against the theme's terracotta #a8563a5.0211.35pass
Original, limits tightened to 4 and 8, eight colours5.4515.27warn
First generated candidate for the same brief8.1214.27pass
Original recoloured to a blue duotone24.4627.94warn

The site stores two terracotta values. Its imagery palette holds #cc785c, and its theme's primary colour is #a8563a (rgb 168 86 58). Against the theme's terracotta the brown group sits 8.35 away instead of 12.45, and the worst case moves to the pale grey at 11.35. Neither palette is wrong. Choose the one that matches what you want to enforce, and say which one you used when you report the result.

The tightened run shows how limits change the verdict on the same image. The numbers barely moved (mean 5.45, worst 15.27), but both are now over the new limits of 4 and 8, and 84.2% of the image's colour lay within the 8-unit limit. The blue duotone is the clear failure: its five dominant colours were each between 23 and 28 away from the nearest brand colour.

Example 2

Contrast: can text be read over this part of the image?

Before text goes on an image, measure the part of the image behind it. The check samples the pixels inside the rectangle you name, works out the contrast ratio against the 10th, 50th and 90th percentile of the background's brightness, and compares the worst of the three with the limit. The default limit is 4.5, the usual WCAG 2 figure for normal-size text.

We tested the page's cream (#faf9f5) as the text colour in two regions of the home image. Region 1 is the dark strip at the top left. Region 2 is a pale patch of the magazine page. The figure marks both regions and states the results. Its labels were checked with check_image_text, which found all five strings we expected.

The home image with two outlined regions. Region 1 is an outline on the dark strip at the top left, containing the words Light headline text. Region 2 is an outline over a pale patch of the magazine page. Two lines at the bottom read: 1, light text on the dark strip, 13.75 to 1 at worst, pass; 2, light text on the pale page, 1.07 to 1 at worst, warn.
Same text colour, opposite verdicts: cream text is easy to read on the dark strip and close to invisible on the pale page.

Text over two regions of the image (limit 4.5 unless noted)

Typical (mean)Worst caseResult
Cream #faf9f5 on the dark strip (region 1)14.1513.75pass
Cream #faf9f5 on the pale page (region 2)1.291.07warn
Near-black #1e1b18 on the pale page (region 2)12.636.77pass
Gold #bf9b30 on the dark strip, limit raised to 75.645.48warn

Brand colour pairs with a named background, no image sampled (limit 4.5)

RatioResult
Terracotta #a8563a text on the page colour #faf9f54.91pass
Gold #bf9b30 text on the page colour #faf9f52.51warn
Muted #5a544c text on the surface colour #f4f2ec6.68pass
Cream #faf9f5 text on terracotta #a8563a4.91pass

The worst case, not the average, is compared with the limit, because text crosses both the brightest and the darkest parts of its background. On the pale page the darker pixels gave 2.40, the median 1.12 and the brightest 1.07, and 1.07 is the number that counts. The dark strip was uniform: 14.60, 14.09 and 13.75.

With a named background colour no pixels are sampled, which makes the check a quick test of brand colour pairs. Terracotta text on the page colour passes with little room (4.91 against 4.5), and gold text on the page colour measures 2.51, so at default limits it warns. At a limit of 7, gold on the dark strip (5.48 at worst) is a warn; at the default of 4.5 it would pass. A higher bar is a deliberate choice, for small or important text.

The check reads colour and brightness only. It cannot tell whether the region behind the text is a face, and it cannot tell whether lettering is legible. That is the job of check_image_text.

Example 3

Similarity: is this a near-duplicate, or did the edit change too much?

Similarity answers two questions: is this image a near-duplicate of that one, and did an edit preserve the original? The reference was the home image. We made variants with deterministic edits that cost nothing (resize, blur, re-encode, brighten, recolour) and added two images that are different pictures: the first generated candidate for the same brief, and an unrelated square comic panel from another article, which the comparison adopted into this request by naming its source.

SSIM compares structure on a 256 by 256 grey copy, where 1.0 is identical. The pHash distance counts the bits that differ in a 64-bit fingerprint, where 0 is identical. The defaults are SSIM at least 0.9 and distance at most 10.

A contact sheet of six versions of the home image in two rows of three, each labelled with its result: Original 1536 x 1024; Resized, SSIM 0.9939; Blurred, SSIM 0.9226, BLUR; JPEG q12, SSIM 0.9759, BLOCKY; Brighter, highlights 40.7%; Blue duotone, palette warn.
Six versions of one image with their measured results: a resize, a blur and a heavy JPEG stay close to the original, while a brighter copy and a blue recolour do not.

Similarity to the original home image (limits 0.9 and 10)

SSIMpHash distanceResult
Resized to 1080 wide0.99390pass
Re-encoded as JPEG quality 120.97590pass
Blurred (sigma 3)0.92260pass
Recoloured to a blue duotone0.88772warn
Brightened 2.4 times0.686714warn
First generated candidate, same brief0.347818warn
Unrelated square comic panel0.179632warn

Three results are worth reading closely. The blurred copy passed, with SSIM 0.9226: similarity tolerates softening, so blur is caught by the quality check in Example 4, not here. The blue recolour warned on SSIM alone (0.8877) while its fingerprint was 2 bits from the original: the structure is the same and the colours are not, which is the palette check's job. And a different picture generated from the same brief scored 0.3478 and 18, well outside both limits, so images made from one brief are not duplicates of each other.

Pixel difference is stricter. It counts the pixels whose colour differs by more than a sensitivity of 0.1 (in the YIQ colour space) and warns when more than 1% of the image differs. Use it to prove that a deterministic re-render or an edit changed only what it should.

Pixel difference against the original home image (limits 0.1 and 1%)

Pixels that differShareResult
The original compared with itself0 of 1,572,8640%pass
Resized to 1080 wide2,128 of 777,6000.27%warn
Blurred (sigma 3)189,615 of 1,572,86412.06%warn
Cropped to 16:9300,526 of 1,327,10422.65%warn
Darkened to 30% brightness, limits widened to 0.3 and 50%664,216 of 1,572,86442.23%pass

Two cautions. The resized copy warned although only 0.27% of its pixels differed, well under 1%. The one other thing the report flagged was that the sizes did not match (dimensions_match: false), and the tool resampled the reference to the subject's size before comparing. Treat a size mismatch as a warn in its own right.

The 16:9 crop is a worse case. Cropping moves every pixel, and the tool compared the crop with a rescaled copy of the whole image, so 22.65% says nothing about the crop's quality. Use pixel difference on images of the same size, such as a regression check after a re-render, and use similarity for everything else.

Example 4

Quality: is the image sharp, clean and large enough?

The quality check reports facts about the file itself: sharpness as the variance of the Laplacian (the higher, the more fine detail), noise as an estimated sigma, the share of pixels clipped to pure black or white, whether the frame is blank, whether the image is large enough for a target size and, for JPEG files, how pronounced the 8 by 8 compression blocks are. Unlike the palette check, it names the limit that tripped, with the measured value and the threshold.

Quality of the home image and five variations (default limits)

Sharpness (min 100)Noise (max 8)Highlights clipped (max 5%)Result
Original4,0272.770.50%pass
Blurred (sigma 3)5.60.090%warn (BLUR)
Brightened 2.4 times3,2872.5140.70%warn (CLIPPED_HIGHLIGHTS)
JPEG quality 122,8131.772.05%warn (BLOCKY)
Shrunk to 480 pixels wide, target 1,200 on the long edge2,4302.250.53%warn (LOW_RESOLUTION)
Darkened to 30% brightness2790.800%pass

Each warning carries a code, the measured value and the limit. The blur warning read "Laplacian variance is below the sharpness threshold", 5.571 against 100. The highlight warning read "40.7% of pixels are at luma >= 253", against 5. The JPEG warning read "JPEG 8x8 block boundaries are more pronounced than the interior (compression blockiness)", 1.408 against 1.35. The low-resolution warning read "Source is 40% of the required pixels on its tightest axis; 2.5x upscale needed." Blockiness is evaluated only for JPEG files, so on the WebP originals it comes back not_evaluated, and resolution stays not_evaluated until you give a target.

Limits are yours to tighten. Setting the sharpness floor to 5,000 and the noise ceiling to 2 turned the original's pass into two warnings: BLUR at 4,027 against 5,000, and NOISY at 2.77 against 2. Whether that is the right bar depends on the image's job.

One gap to know about: the darkened copy passed every check. The exposure checks look for clipped pixels, not for an image that is simply dim, so a dim image needs a person's eye or a look at the brightness in the report (an average of 36 on a 0 to 255 scale here, against 119 for the original).

Example 5

Geometry and safe zone: does it fit the slot, and is there room for text?

Geometry compares an image's size and proportions with what a slot expects. This site's imagery rules set 16:9 for article headers and 4:3 for article bodies. With no expectations the check has no verdict and returns the facts: the home image is 1536 by 1024 pixels (3:2, landscape, 1.57 megapixels, WebP, 114,460 bytes). Given the header slot's expectations, 16:9 and at least 1,200 pixels wide, it warns on the ratio and passes the width. Cropped to 16:9 it passes. That crop is the header image of this article.

Geometry against a 16:9 slot at least 1,200 pixels wide

Size (pixels)Aspect ratioResult
Home image, no expectations given1536 x 10241.5 (3:2)not_evaluated
Home image1536 x 10241.5 (3:2)warn
Home image cropped to 16:91536 x 8641.7778 (16:9)pass

The report lists each check with its expected value, the actual value and whether it passed. The warn above reads: aspect ratio expected 1.7778, actual 1.5, not passed; minimum width expected 1200, actual 1536, passed. The default tolerance on the ratio is 1%, relative.

The safe-zone check reads how busy named rectangles are. A zone is a rectangle given as fractions of the image, or a block of cells on a grid, 6 by 6 by default, with columns A to F and rows 1 to 6; A2:B5 is the left third of the image, rows 2 to 5. For each zone it reports the edge density, the share of the zone's small cells that are quiet, and any placed box (a headline, a logo, a button) whose rectangle overlaps it. At default limits a zone passes when its edge density is at most 0.1, at least 90% of its cells are quiet and nothing overlaps it.

Zones of the home image

Edge density (max 0.1)Quiet cells (min 90%)OverlapResult
Title strip, no placed boxes (edge limit 0.02)0100%nonepass
Title strip, with a headline box placed on it0100%65.6% of the zonewarn
Notebook page (cells A2:B5)0.158557.8%nonewarn
Article spread (cells D3:F5)0.278940.3%nonewarn

The strip is the quiet area of the image: a flat dark ground and nothing else. The two page zones are busy, with handwriting and set type. Both exceed the edge limit and fall short of the quiet-cell floor, and the report names the screen that failed (quiet cells, 0.5781 against 0.9, for the notebook page).

The result depends on what you ask. The same title strip passed when measured alone and warned when a headline box was placed over it, because at the default limit of 0 any overlap is a collision. The zone's status is a single warn either way, so read the collisions list and the failed screen to see which it was. Because any overlap counts, declare placed boxes only for things that must stay out of a zone. To check that a zone is quiet enough for text, declare the zone alone and read its quiet cells.

Example 6

Logo placement: is the logo still intact where it was placed?

When project_planar_decal places a logo on an image, it returns a transform: the exact position and distortion it applied. verify_logo_placement takes that transform, cuts the same region out of the finished image and compares it with the original logo file on four measures: geometry similarity, colour distance, edge overlap and pHash distance. It is read-only and can be run again at any time, for example after another edit.

A note on the logo itself. This site's logo is its name as text, and no logo image file is stored. The logo in these examples is a stand-in: the words Kugel Platform rendered as word art in the site's cream on a dark tile cut from the home image. The measurement works the same way with a real logo file, which can be named by the request that owns it.

The home image with the words Kugel Platform in cream serif lettering at the top left, on the dark ground above the notebook.
The stand-in logo placed flat on the quiet dark strip: it measured as intact.

Logo integrity at four checks (default limits unless noted)

Geometry similarity (min 0.85)Colour distance (max 8)Edge overlap (min 0.6)Result
Placed flat on the dark strip, checked where it was placed0.98071.661.00pass
The image without a logo, checked at the same place0.67669.310warn
Sheared over the magazine page, geometry limit 0.80.84673.181.00pass
Flat version, checked at the sheared position0.197062.970warn

The sheared placement tilts the logo slightly over the magazine page. Its geometry similarity was 0.8467, narrowly under the default of 0.85, so we ran the check with the limit lowered to 0.8 to see the rest of the picture. At the default the placement step's own check had already recorded it as a warn. The tool's guidance is to treat a geometry similarity below 0.9 as a reason to place the logo again.

A warn here has two causes: the logo is distorted, or the check looked in the wrong place. The first and fourth rows show the difference, because the same flat image passes at its own position and fails at another. All four runs reported text_verified as not_evaluated. The tool takes no expected text, so confirm lettering with check_image_text.

The home image with a dark tile carrying the words Kugel Platform in cream, tilted slightly, laid over the pale magazine page.
A tilted placement over busy content: legible, and barely inside a relaxed limit.

Boundary

Warnings and failure modes

What the checks reported, and refused, during the runs behind this article:

  • Wrong request. A measurement reads only images indexed under the request it is called for. Naming an image that is not indexed there came back as: "No artifact with sha256 ... is indexed for request ...". Adopt the image first. adopt_artifact needs the full 64-character hash: a shortened hash with a wildcard was refused with "Name the source artifact with blobKey, or source_request_id + sha256."

  • Values are validated. A colour name instead of a hex colour came back as "foreground must be a #rgb/#rrggbb colour". A grid cell that does not exist came back as "cell "H9" is outside the 6x6 grid (columns A..F, rows 1..6)". Limits outside their allowed range are refused, as shown under Settings and limits.

  • A named background still needs an image. A contrast check with a background colour and no image came back as "Name the image artifact with public_path ... or with sha256 ... At least one is required." Name any image from the request; the background colour is then used instead of sampling the image.

  • Some reports give only a status. Palette, similarity and pixel difference return the numbers but not which limit was missed, so compare them with the limits in the tables above. Quality names the code, value and threshold. Safe zone names the screen that failed.

  • Size mismatches warn. Pixel difference warns when the two images differ in size, whatever the share of differing pixels, and it is not meaningful for crops.

  • Dim images pass. The exposure checks look for clipped pixels, so a uniformly dark image passes them.

  • Pixels, not meaning. The home image's page text is generated lettering that is not real words, and every check treated it as ordinary detail. Whether lettering is legible is a question for check_image_text, which matched all five labels we expected on the annotated figure.

Boundary

What the measurements do not decide

They do not decide whether an image persuades, suits the article or deserves publishing. A pass is evidence about the file, not about the page. They repair nothing and block nothing: the status is a report.

Not verified for this article: how the admin chat phrases these requests to an editor; the effect of changing the site's stored image-analysis policy, which we read and did not change; the refusal for an image from another article that was never adopted, beyond the missing-index message quoted above; and behaviour on PDF pages, which the tools accept as a subject but we did not exercise.

The next safe operation is to run one of these checks on an image already attached to an article. All eight are read-only, so nothing changes if the result is not what you expected.

Related reading

Related reading

Other articles in this series on images and PDFs:

Back to Library