Give the image overlay an optional shadow or glow
A blurred copy of the overlay's own alpha is drawn in a colour beneath it: moved by an offset for a shadow, centred for a glow, with a size, a spread and an opacity. The mask is integer-only so a seed still reproduces it, and reaches past the canvas so an image lying off it still casts onto it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -638,6 +638,49 @@ int main() {
|
||||
solid(100, 100, 0, 0, 255)),
|
||||
"zero opacity leaves the input untouched");
|
||||
|
||||
// Halo: none by default, a shadow moved by its offset, a glow all round.
|
||||
check(sameBytes(renderOverlay(blue, 100, 100, at),
|
||||
renderOverlay(blue, 100, 100, [&] {
|
||||
ParamValues none = at;
|
||||
none["overlay_halo"] = std::string("none");
|
||||
return none;
|
||||
}())),
|
||||
"no halo is the default");
|
||||
ParamValues shadowed = at;
|
||||
shadowed["overlay_halo"] = std::string("shadow");
|
||||
shadowed["overlay_halo_size"] = 0L;
|
||||
shadowed["overlay_halo_opacity"] = 1.0;
|
||||
shadowed["overlay_shadow_x"] = 5L;
|
||||
shadowed["overlay_shadow_y"] = 5L;
|
||||
const auto shadow = renderOverlay(blue, 100, 100, shadowed);
|
||||
const auto dark = pixel(shadow, 72, 62); // past the logo's bottom right corner (70, 60)
|
||||
check(dark[0] == 0 && dark[1] == 0 && dark[2] == 0,
|
||||
"a hard shadow is cast down and right by its offset");
|
||||
check(isBlue(pixel(shadow, 28, 50)) && isRed(pixel(shadow, 50, 50)),
|
||||
"it leaves the other side alone and stays beneath the image");
|
||||
shadowed["overlay_halo_size"] = 12L;
|
||||
const auto soft = pixel(renderOverlay(blue, 100, 100, shadowed), 78, 50);
|
||||
check(soft[2] > 0 && soft[2] < 255, "a soft shadow fades out past the edge");
|
||||
|
||||
ParamValues glowing = at;
|
||||
glowing["overlay_halo"] = std::string("glow");
|
||||
const auto glow = renderOverlay(blue, 100, 100, glowing);
|
||||
const auto left = pixel(glow, 27, 50);
|
||||
const auto right = pixel(glow, 72, 50);
|
||||
check(left[0] > 0 && left[0] == right[0] && left[1] == right[1],
|
||||
"a glow lightens both sides alike");
|
||||
check(isBlue(pixel(glow, 2, 2)), "and fades out before the far corners");
|
||||
glowing["overlay_halo_spread"] = 0.5;
|
||||
check(pixel(renderOverlay(blue, 100, 100, glowing), 27, 50)[0] > left[0],
|
||||
"spread strengthens the halo near the edge");
|
||||
|
||||
ParamValues offCanvas = shadowed;
|
||||
offCanvas["overlay_halo_size"] = 0L;
|
||||
offCanvas["overlay_offset_x"] = -70L; // the logo ends just left of the canvas
|
||||
offCanvas["overlay_shadow_x"] = 20L;
|
||||
const auto cast = pixel(renderOverlay(blue, 100, 100, offCanvas), 5, 50);
|
||||
check(cast[2] == 0, "an image off the canvas still casts its shadow onto it");
|
||||
|
||||
// No image chosen is the state the interface starts in: a pass through.
|
||||
check(sameBytes(renderOverlay(blue, 100, 100, {}), solid(100, 100, 0, 0, 255)),
|
||||
"with no image chosen the stage passes its input through");
|
||||
|
||||
Reference in New Issue
Block a user