Conversation
|
It might be worthwhile looking into calculating this upon upload and stored in the meta vs on the fly in browser. It could be something as simple as: use Intervention\Image\ImageManagerStatic as Image;
$image = Image::make($path)->resize(1, 1, function ($constraint) {
$constraint->aspectRatio();
});
$averageColor = $image->pickColor(0, 0); // [R, G, B, A]
// Normalized to 0-1
$meanR = $averageColor[0] / 255;
$meanG = $averageColor[1] / 255;
$meanB = $averageColor[2] / 255;
$luminance = (0.2126 * $meanR) + (0.7152 * $meanG) + (0.0722 * $meanB);
$isBright = $luminance > 0.5;(not tested AI generated - but looks correct) |
|
The brightness/luminance would indeed be interesting on the frontend as well, now that everything is glass and backdrop filters :) With the additional benefit of front-loading the calculation at the time of upload. |
…r uses the server-provided tone directly, falling back to client-side Canvas detection only for SVGs.
… with a transparent background, so it can skip transparent pixels and only measure the actual content. This works independently of the user's configured image driver -- if the Imagick PHP extension is installed, SVGs get tone detection; if not, they get null gracefully. Raster image detection is unchanged (still uses Intervention Image).
|
OK, I've given it a go! I've updated the PR description to note what's happening.
You could use it like this: {{ if asset:tone == "light" }}
<div class="preview preview-dark">
{{ elseif asset:tone == "dark" }}
<div class="preview preview-light">
{{ /if }}
<img src="{{ asset:url }}" alt="" />
</div>Or with the booleans: {{ if asset:is_light_tone }}
<div class="bg-dark">…</div>
{{ elseif asset:is_dark_tone }}
<div class="bg-light">…</div>
{{ /if }} |
|
This is really nice. Great solution to a tricky situation. |
|
Did you push up the antlers stuff? I don't see anything. |
… button to show transparency
when the Imagick branch finds zero non-transparent pixels (meaning it couldn't meaningfully rasterize the SVG), it now falls through to the XML color-parsing fallback instead of returning null. Same for when Imagick throws an exception.
|
@jasonvarga the Antlers syntax should "just work" because we're referencing saved meta data. Here's some Antlers and the browser rendering it in the background: |
…Vue file when server generation has not taken place yet
…nch and will come back via merge.
# Conflicts: # resources/js/components/assets/Editor/Editor.vue # src/Imaging/Attributes.php
jasonvarga
left a comment
There was a problem hiding this comment.
I've resolved the merge conflicts for you.
This looks almost done but something I noticed was that the tone isn't properly picked up for transparent PNGs.
✅ The black HBO logo SVG gets correctly picked up as "dark".
❌ The black HBO logo PNG gets picked up as "light".
❌ The black dog PNG gets picked up as "light".
🤷♂️ The golden dog PNG gets correctly picked up as "light", but this might just be by fluke.
✅ JPGs work fine. (not in this screenshot)
|
@jasonvarga Are you using |
|
I think you could solve the transparent issue by ignoring transparent pixels below a certain threshold. Then for any semi-transparent pixels doing a double comparison of blending the transparent pixels with white and black and comparing to see which yields a higher contrast: $sum_white = 0.0;
$sum_black = 0.0;
$count = 0;
// Sample ~256 pixels for speed
$step = max(1, (int) ceil(($w * $h) / 256));
$i = 0;
for ($y = 0; $y < $h; $y++) {
for ($x = 0; $x < $w; $x++) {
if ($i++ % $step !== 0) {
continue;
}
$color = $image->pickColor($x, $y);
// Intervention Image v3: alpha is 0.0 (transparent) to 1.0 (opaque)
$alpha = $color->alpha()->toFloat(); // or ->value() / ->float() depending on exact version
if ($alpha < 0.02) { // skip fully/nearly transparent pixels
continue;
}
// Colors are already 0–1 in Intervention v3
$r = $color->red()->toFloat();
$g = $color->green()->toFloat();
$b = $color->blue()->toFloat();
// Blend against WHITE background (1,1,1)
$r_white = $r * $alpha + (1 - $alpha) * 1.0;
$g_white = $g * $alpha + (1 - $alpha) * 1.0;
$b_white = $b * $alpha + (1 - $alpha) * 1.0;
$l_white = 0.299 * $r_white + 0.587 * $g_white + 0.114 * $b_white;
// Blend against BLACK background (0,0,0)
$r_black = $r * $alpha + (1 - $alpha) * 0.0;
$g_black = $g * $alpha + (1 - $alpha) * 0.0;
$b_black = $b * $alpha + (1 - $alpha) * 0.0;
$l_black = 0.299 * $r_black + 0.587 * $g_black + 0.114 * $b_black;
$sum_white += $l_white;
$sum_black += $l_black;
$count++;
}
}
if ($count === 0) {
return null; // fully transparent → no decision possible
}
$avg_white = $sum_white / $count;
$avg_black = $sum_black / $count;
// Option 1: Simple & effective for most logos/icons
// If it looks medium-bright or brighter when on white → recommend dark background
if ($avg_white >= 0.52) { // ← tune this threshold: 0.5–0.6
return 'dark'; // better contrast on black
}
return 'light'; // better/safer on white
// Option 2: More explicit contrast comparison (uncomment if you prefer)
// $contrast_white = max($avg_white, 1 - $avg_white);
// $contrast_black = max($avg_black, 1 - $avg_black);
// return ($contrast_black > $contrast_white) ? 'dark' : 'light';AI generated code - idea is mine :) |
|
Good call guys. 🎉 |












Description of the Problem
As discussed in #13927, transparent assets can be primarily light or dark.
Under certain conditions, it can be difficult to discern an image against a checkerboard background—for example, when the logo is white or black.
What this PR Does
by sampling pixels using built-in browser APIs (Canvas 2D + Image). This is computed server-side on upload using Intervention Image (works with both GD and Imagick drivers) and stored in the asset's.meta/*.yamlfile alongside existing metadata likewidth,height, andduration.(Updated since initial PR):
How it works
lightordark) is written to meta astone.tone: null.generateMeta()pipeline.Exposed to developers in Antlers templates
{{ tone }}-- returnslight,dark, or null{{ is_light_tone }}/{{ is_dark_tone }}-- boolean helpersWorks the same way as
focus_cssand other meta-driven asset values.Before
(and you'd have a similar problem if you had a dark logo with a dark checkerboard background)
After
White and black logos are now much easier to discern:
(ignore the aspect ratio issue here, that's an existing issue that I'll fix separately)
How to Reproduce
/cp/assetsand upload a transparent white or black logo