Color Utils¶
The game’s colour model.
Colours are not stored as RGB but as a 16 bit hue, saturation and luminance index into a palette
built once at startup - see TColorUtils.PackHSL and
TColorUtils.CreateHSVPalette. The global ColorUtils holds those palettes.
There is one palette, and everything draws through it - the map renderer and the sprite
rasterizer both read ColorUtils.Palette rather than carrying one of their own. Its entries are
already in the order the PColorBGRA buffer TImage.FromData reads, with the alpha byte full,
so neither has to convert a pixel on the way out. It is built by TColorUtils.Setup at
whatever brightness you ask for and stays put; TColorUtils.SetBrightness rebuilds it if
you want a different look.
ColorUtils.PackHSL¶
function TColorUtils.PackHSL(hue, saturation, luminance: Integer): Integer; static;
Packs a colour into the 16 bit index the game’s palettes are addressed by: 6 bits of hue, 3 of saturation and 7 of luminance.
Saturation is halved at each of four luminance thresholds on the way up, because a colour that bright cannot carry much saturation without looking wrong - the same taper the client applies.
ColorUtils.CreateHSVPalette¶
function TColorUtils.CreateHSVPalette(brightness: Double; mn, mx: Integer): TIntegerArray; static;
Builds the 65536 entry palette that a TColorUtils.PackHSL index looks up.
Every index is unpacked back into hue, saturation and luminance, converted to RGB, and then bent
by brightness - a gamma exponent, where a lower number means a brighter image. The client
offers a few fixed brightness settings, which are the BRIGHTNESS_ constants.
Entries are alpha, red, green and blue from the top byte down, which is what the PColorBGRA
buffer TImage.FromData reads - not a TColor, which has red and blue the other way round.
mn and mx bound which entries are actually computed; the rest are left at 0.
ColorUtils.SetBrightness¶
procedure TColorUtils.SetBrightness(brightness: Double);
Rebuilds both palettes at a new brightness. Everything that draws - the map renderer and the sprite rasterizer alike - reads them, so this changes the whole library’s colours at once.
Palette is the plain lookup a packed HSL index goes through. MinimapPalette is that same
lookup with the flatter minimap lighting already folded into it, so that
TColorUtils.UnderlayColor and TColorUtils.OverlayColor are a single array read
rather than a rescale and a clamp per tile.
Both are in the order TColorUtils.CreateHSVPalette produces - alpha, red, green, blue -
which is what the PColorBGRA buffer TImage.FromData reads, so whatever is drawn through them
is handed to an image as it is.
Rebuilding costs around 25ms, nearly all of it the brightness curve, so this is meant for setting a look up front rather than for switching back and forth. Setting the brightness it already has does nothing.
Work already derived from the old palette is thrown away rather than left to go stale, but not
from here - ColorUtils is defined long before anything that draws and there may be more than
one map renderer. Instead Revision counts the changes, and
TMapChunkLoader.EnsureColorsCurrent compares it against the revision its caches were
built at and rebuilds them when they no longer match.
ColorUtils.UnderlayColor¶
function TColorUtils.UnderlayColor(hsl: Integer): Integer;
The colour a ground underlay is drawn as on the minimap.
The minimap is lit flatter than the world is, so the luminance in the index is scaled towards a
fixed level before the palette is looked up. That rescale is already baked into MinimapPalette
by TColorUtils.Setup, which leaves this a plain lookup. An hsl of -1 means the tile
has no underlay.
ColorUtils.OverlayColor¶
function TColorUtils.OverlayColor(hsl: Integer): Integer;
TColorUtils.UnderlayColor for an overlay, which additionally distinguishes an overlay
that is deliberately blank, -2, from one that is drawn at the flat minimap luminance, -1.
ColorUtils.Height2Color¶
function TColorUtils.Height2Color(height: Integer): Integer; static;
A terrain height as a shade of grey, black at ground level and white at the highest terrain the game uses. Heights are negative upwards, which is why the sign is flipped.
ColorUtils.SetupHeightColors¶
procedure TColorUtils.SetupHeightColors();
Fills HeightColors with TColorUtils.Height2Color for every height from ground level,
index 0, up to MAX_HEIGHT. A height is negative upwards, so height h is at index -h.
Everything past either end is the same colour as that end, so a lookup only has to clamp the index rather than redo the division and rounding for every tile.
ColorUtils.Collision2Color¶
function TColorUtils.Collision2Color(flag: Integer): Integer; static;
A collision flag as a colour: white for walkable, black for blocked, grey for a tile an object stands on.