shade_region_by_light
Shade a flat region as a lit form using a color ramp and light direction. Scope to one material via base color or selection to keep separate parts distinct.
Instructions
Shade a flat region as a lit form, using a ramp and a light direction.
Turns a flat fill into a shaded one: an asymmetric terminator, a darker core away
from the light, and optional rim light. Every output pixel comes from `ramp`.
Scope this to one material. Shading a whole layer flattens materials together: a
sword shaded in one pass turns steel, brass and leather into the same colours, where
three scoped calls keep all three. Scope it by passing `base_color`, by making a
selection first (`select_by_color` is the usual way), or both.
What it cannot do: a distance field measures depth inside a silhouette, so it cannot
invent form boundaries that are not in the outline. An overlapping head and torso
drawn as one flat shape become one mass. Shade them as separate regions.
Args:
ramp: Colours darkest first, as from `generate_ramp`. Output uses only these.
base_color: Only shade pixels near this colour. Defaults to every opaque pixel
in scope, which is usually what you want once a selection is active.
light_angle: Degrees. 0 is from the right, 90 from above, 135 from the upper
left, which is the conventional pixel-art key light.
light_z: How much the light comes from the viewer, 0 to 1. Higher flattens the
terminator and lights more of the form.
bulge: How rounded the form reads. 1.0 is sphere-like; lower is flatter, which
suits cloth and flat panels; higher exaggerates the curvature.
ambient: Floor brightness in shadow, 0 to 1. Pixel art rarely wants true black
in shadow, so this sits well above zero by default.
rim: Light wrapping the edge away from the key, 0 to 1. A little reads as a
bounce; a lot reads as backlight.
bias: Shift the whole result along the ramp, in steps. Use this when the result
is uniformly a shade too dark or light, rather than re-tuning the lighting.
fill_angle: Degrees, a second light. Omitted by default, which leaves the result
exactly as it was before this argument existed. Set it opposite `light_angle`
for the fill or bounce light that keeps a shadow side readable instead of
letting it go flat dark: the shadow side is where a sprite stops describing
its form, and one light can only ever leave it at `ambient`.
fill_strength: How strong the fill is next to the key, 0 to 1 and capped below 1.
A fill that matches the key cancels the form entirely, because the two
terminators land on opposite sides of the same shape and sum to a flat fill,
so the cap refuses the value that destroys what the tool is for. A third to a
half is the conventional choice.
tolerance: How close a pixel must be to `base_color` to count as part of the
region, as a weighted RGB distance. Ignored when `base_color` is omitted.
**A distance between colours, not between materials.** Two ramps of different
materials are routinely closer than the default 24: every step of a gold ramp
generated by `generate_ramp` is within 24 of a step of a brass one, so a pass
scoped to the brass matched the gold as readily. Art whose fills came from
`generate_ramp` is exact, so scope it with a tolerance near zero and keep the
wide default for hand-drawn art that varies.
layer: Target layer (default: top layer).
frame: Target frame, 1-based.
The two lights are summed and clamped, not averaged: averaging would dim the key side
as the fill came up, so adding a fill light would make the whole sprite darker and
quietly cost the ramp's top step. Clamping leaves the key side as it was and lightens
only what the key did not reach, which is what a fill light is.
`per_step` in the result counts pixels per ramp entry, which is how to check a fill
actually lightened the shadow side rather than trusting that it did.
`region_components` and `region_bounds` say whether the pass went where it was meant
to, which `region_pixels` cannot: it counts matches without saying whether they are in
one place or four. A call scoped to a 27px coin that comes back with a box spanning 200
pixels of canvas has matched something else as well, and that is the mistake
`tolerance` invites.
Refuses a region with no interior to shade: below roughly 6px across, the distance
field never exceeds a pixel and there is no form to describe. The honest answer
there is two hand-placed pixels, which `draw_pixels` already does.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| rim | No | Default: 0.0. | |
| bias | No | Default: 0.0. | |
| ramp | Yes | ||
| bulge | No | Default: 1.0. | |
| frame | No | Default: 1. | |
| layer | No | ||
| ambient | No | Default: 0.35. | |
| light_z | No | Default: 0.45. | |
| filename | Yes | ||
| tolerance | No | Default: 24.0. | |
| base_color | No | ||
| fill_angle | No | ||
| light_angle | No | Default: 135.0. | |
| fill_strength | No | Default: 0.35. |