Skip to content

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 = $Player
var dusk := PixelPalette.new()
dusk.colors = PixelPaletteMaths.ramp(Color("1b1f3b"), Color("f4e9b0"), 8)
player.palette = dusk

The 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) -> PackedColorArray

steps 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) -> Variant

colors 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) -> Color

color 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) -> Color

color 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) -> PackedColorArray

colors 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) -> PackedColorArray

colors 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) -> PackedInt32Array

The 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 := 2

The fewest colours a ramp has: its two ends.

GREY_SATURATION

const GREY_SATURATION := 0.08

A 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.