PixelPaletteMaths
Extends RefCounted. Read from the plugin's source on every build.
The maths over a palette’s colours, in OKLCH: a ramp from one colour to
another, a nudge, a tint, new hues for the slots not locked, and the
swatches sorted by hue or by brightness. The
command line’s palette verbs run it, and the palette tab’s Ramp, Lock and
Regenerate and New swap are the same picks, docs/tasks/TASK-palette-tab.md.
In model/, so it ships in a game with the kit’s oklab.gd: game code
ramps or regenerates a palette through the calls the verbs make,
docs/decisions/2026-10/21-scriptable-documents.md, and
PixelPalette’s own PixelPalette.ramp_between and its siblings
edit one in place.
Example: eight steps from a dark blue to a pale yellow, worn by a sprite at once:
var player: PixelSprite2D = $Playervar dusk := PixelPalette.new()dusk.colors = PixelPaletteMaths.ramp(Color("1b1f3b"), Color("f4e9b0"), 8)player.palette = duskThe thing a reader would get wrong: a ramp’s two ends come back as the colours given, bit for bit, never round-tripped through OKLCH, so a ramp filled between two slots leaves both slots as they were.
Methods
ramp
static func ramp(from: Color, to: Color, steps: int, hue_shift := 0.0) -> PackedColorArraysteps colours from from to to, both included,
stepped evenly in lightness, chroma and hue, the hue the short way round.
hue_shift degrees are added to the middle, most at the halfway
step and none at either end, so a ramp of greens can lean yellow in its
lights and blue in its darks with a negative shift between them. A grey
end takes the other end’s hue, so a ramp from black to a red is all red.
fill_between
static func fill_between(colors: PackedColorArray, first: int, last: int, hue_shift := 0.0) -> Variantcolors with every slot strictly between first and
last, counted from zero, filled by a ramp from one to the
other. The two picked stay; nothing outside them moves. Null for a pair
out of range or with nothing between.
nudge
static func nudge(color: Color, lightness := 0.0, chroma := 0.0, hue := 0.0) -> Colorcolor moved in OKLCH: lightness and chroma added,
hue degrees turned. A grey only lightens or darkens: its hue is
noise, so more chroma would tint it toward whatever the noise points at.
Alpha is kept.
tint
static func tint(color: Color, toward: Color, amount: float) -> Colorcolor mixed toward toward by amount, 0 to 1, in
Oklab, the way the theme mixes, JamChipOklab.mix. Alpha is kept.
regenerate
static func regenerate(colors: PackedColorArray, locked: PackedInt32Array, seed: int, spread := 360.0) -> PackedColorArraycolors with each slot not in locked, counted from zero,
given a new hue and keeping its lightness and chroma: the palette tab’s
Lock and Regenerate. The new hue is anywhere when spread is 360
degrees, or within half of spread either side of the old one, and
is the same for the same seed. A grey stays grey.
sorted
static func sorted(colors: PackedColorArray, by_hue: bool) -> PackedColorArraycolors sorted by hue, greys first dark to light, then each hue
dark to light; or with by_hue false by brightness alone, dark to
light.
Stable, so equal keys keep their order. The rack’s Sort by hue and Sort
by brightness, and palette edit --sort.
sort_order
static func sort_order(colors: PackedColorArray, by_hue: bool) -> PackedInt32ArrayThe order sorted puts colors in, as the place each came
from: what the palette tab applies to every row, so a sort by the
selected row is a column order for all.
Constants
MIN_STEPS
const MIN_STEPS := 2The fewest colours a ramp has: its two ends.
GREY_SATURATION
const GREY_SATURATION := 0.08A grey has no hue to sort by, so it sorts ahead of every colour.
JamChip, by GlitterSnack. MIT licence. Built from the source on every merge. Its artwork and screenshots are made with JamChip.