Feature - Improve Structure Color Contrast (#2454)

If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #2447

## Description:

This PR updates the logic used to generate structure fill and border
colors. Currently, (v0.26.16 and earlier), some light territory colors
have structures that are difficult to see and identify. This PR ensures
that all territory colors have structures that are easily visible.

Instead of using `Colord.lighten()` and `Colord.darken()` to generate
structure colors, the logic now:
- queries the territory color and border color of the structure owner
- Converts these colors to the [LAB color
space](https://en.wikipedia.org/wiki/CIELAB_color_space) (which is a
human-perception-uniform color space).
- Darkens the border color (by decreasing LAB luminance) and sometimes
lightens the territory color (by increasing LAB luminance) until a
specific `Color Delta` is achieved (currently `delta > 0.5`)
- This ensures contrast between the structure and the territory
background.

Additionally, this PR re-organizes colors in the `Colors.ts` file for
better visibility and removes redundant colors from the `nationColors`
list.

This PR is an implementation of the proposed mock-up posted on imgur in
issue #2447. Screenshots of the original, final, and side-by-side
comparison of structure colors (for all available player colors) are in
the [imgur
album](https://imgur.com/a/openfront-color-playground-4cxSbbj).

I'd recommend inclusion as a feature/fix for v27.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

GlacialDrift
This commit is contained in:
Mike Harris
2025-11-16 22:58:34 -06:00
committed by GitHub
parent 7373a28c99
commit 2b44b68362
5 changed files with 218 additions and 210 deletions
@@ -253,26 +253,13 @@ export class SpriteFactory {
structureCanvas.height = Math.ceil(iconSize);
const context = structureCanvas.getContext("2d")!;
const tc = owner.territoryColor();
const bc = owner.borderColor();
// Potentially change logic here. Some TC/BC combinations do not provide good color contrast.
const darker = bc.luminance() < tc.luminance() ? bc : tc;
const lighter = bc.luminance() < tc.luminance() ? tc : bc;
let borderColor: string;
if (isConstruction) {
context.fillStyle = "rgb(198, 198, 198)";
borderColor = "rgb(128, 127, 127)";
} else {
context.fillStyle = lighter
.lighten(0.13)
.alpha(renderIcon ? 0.65 : 1)
.toRgbString();
const darken = darker.isLight() ? 0.17 : 0.15;
borderColor = darker.darken(darken).toRgbString();
}
context.strokeStyle = borderColor;
// Use structureColors defined from the PlayerView.
context.fillStyle = isConstruction
? "rgb(198,198,198)"
: owner.structureColors().light.toRgbString();
context.strokeStyle = isConstruction
? "rgb(127,127, 127)"
: owner.structureColors().dark.toRgbString();
context.lineWidth = 1;
const halfIconSize = iconSize / 2;
@@ -400,7 +387,10 @@ export class SpriteFactory {
};
const [offsetX, offsetY] = SHAPE_OFFSETS[shape] || [0, 0];
context.drawImage(
this.getImageColored(structureInfo.image, borderColor),
this.getImageColored(
structureInfo.image,
owner.structureColors().dark.toRgbString(),
),
offsetX,
offsetY,
);
+140 -188
View File
@@ -42,222 +42,174 @@ function generateTeamColors(baseColor: Colord): Colord[] {
}
export const nationColors: Colord[] = [
colord("rgb(210,210,100)"), // Lime Yellow
colord("rgb(180,210,120)"), // Light Green
colord("rgb(170,190,100)"), // Yellow Green
colord("rgb(80,200,120)"), // Emerald Green
colord("rgb(130,200,130)"), // Light Sea Green
colord("rgb(140,180,140)"), // Dark Sea Green
colord("rgb(160,190,160)"), // Pale Green
colord("rgb(160,180,140)"), // Dark Olive Green
colord("rgb(100,160,80)"), // Olive Green
colord("rgb(100,140,110)"), // Sea Green
colord("rgb(100,180,160)"), // Aquamarine
colord("rgb(130,180,170)"), // Medium Aquamarine
colord("rgb(170,190,180)"), // Pale Blue Green
colord("rgb(100,130,150)"), // Steel Blue
colord("rgb(120,160,200)"), // Cornflower Blue
colord("rgb(140,150,180)"), // Light Slate Gray
colord("rgb(100,210,210)"), // Turquoise
colord("rgb(140,180,220)"), // Light Blue
colord("rgb(130,170,190)"), // Cadet Blue
colord("rgb(100,180,230)"), // Sky Blue
colord("rgb(80,130,190)"), // Navy Blue
colord("rgb(120,120,190)"), // Periwinkle
colord("rgb(150,110,190)"), // Lavender
colord("rgb(160,120,160)"), // Purple Gray
colord("rgb(170,140,190)"), // Medium Purple
colord("rgb(180,130,180)"), // Plum
colord("rgb(190,140,150)"), // Puce
colord("rgb(180,100,230)"), // Purple
colord("rgb(180,160,180)"), // Mauve
colord("rgb(170,150,170)"), // Dusty Rose
colord("rgb(150,130,150)"), // Thistle
colord("rgb(230,180,180)"), // Light Pink
colord("rgb(210,160,200)"), // Orchid
colord("rgb(230,130,180)"), // Pink
colord("rgb(210,100,160)"), // Hot Pink
colord("rgb(190,100,130)"), // Maroon
colord("rgb(220,120,120)"), // Coral
colord("rgb(200,130,110)"), // Dark Salmon
colord("rgb(230,140,140)"), // Salmon
colord("rgb(230,100,100)"), // Bright Red
colord("rgb(100,180,230)"), // Sky Blue
colord("rgb(230,180,80)"), // Golden Yellow
colord("rgb(180,100,230)"), // Purple
colord("rgb(80,200,120)"), // Emerald Green
colord("rgb(230,130,180)"), // Pink
colord("rgb(100,160,80)"), // Olive Green
colord("rgb(230,150,100)"), // Peach
colord("rgb(80,130,190)"), // Navy Blue
colord("rgb(210,210,100)"), // Lime Yellow
colord("rgb(190,100,130)"), // Maroon
colord("rgb(100,210,210)"), // Turquoise
colord("rgb(210,140,80)"), // Light Orange
colord("rgb(150,110,190)"), // Lavender
colord("rgb(180,210,120)"), // Light Green
colord("rgb(210,100,160)"), // Hot Pink
colord("rgb(100,140,110)"), // Sea Green
colord("rgb(230,180,180)"), // Light Pink
colord("rgb(120,120,190)"), // Periwinkle
colord("rgb(190,170,100)"), // Sand
colord("rgb(100,180,160)"), // Aquamarine
colord("rgb(210,160,200)"), // Orchid
colord("rgb(170,190,100)"), // Yellow Green
colord("rgb(100,130,150)"), // Steel Blue
colord("rgb(230,140,140)"), // Salmon
colord("rgb(140,180,220)"), // Light Blue
colord("rgb(200,160,110)"), // Tan
colord("rgb(180,130,180)"), // Plum
colord("rgb(130,200,130)"), // Light Sea Green
colord("rgb(220,120,120)"), // Coral
colord("rgb(120,160,200)"), // Cornflower Blue
colord("rgb(200,200,140)"), // Khaki
colord("rgb(160,120,160)"), // Purple Gray
colord("rgb(140,180,140)"), // Dark Sea Green
colord("rgb(200,130,110)"), // Dark Salmon
colord("rgb(130,170,190)"), // Cadet Blue
colord("rgb(190,180,160)"), // Tan Gray
colord("rgb(170,140,190)"), // Medium Purple
colord("rgb(160,190,160)"), // Pale Green
colord("rgb(190,150,130)"), // Rosy Brown
colord("rgb(140,150,180)"), // Light Slate Gray
colord("rgb(180,170,140)"), // Dark Khaki
colord("rgb(150,130,150)"), // Thistle
colord("rgb(170,190,180)"), // Pale Blue Green
colord("rgb(190,140,150)"), // Puce
colord("rgb(130,180,170)"), // Medium Aquamarine
colord("rgb(180,160,180)"), // Mauve
colord("rgb(160,180,140)"), // Dark Olive Green
colord("rgb(170,150,170)"), // Dusty Rose
colord("rgb(100,180,230)"), // Sky Blue
colord("rgb(230,180,80)"), // Golden Yellow
colord("rgb(180,100,230)"), // Purple
colord("rgb(80,200,120)"), // Emerald Green
colord("rgb(230,130,180)"), // Pink
colord("rgb(100,160,80)"), // Olive Green
colord("rgb(230,150,100)"), // Peach
colord("rgb(80,130,190)"), // Navy Blue
colord("rgb(210,210,100)"), // Lime Yellow
colord("rgb(190,100,130)"), // Maroon
colord("rgb(100,210,210)"), // Turquoise
colord("rgb(210,140,80)"), // Light Orange
colord("rgb(150,110,190)"), // Lavender
colord("rgb(180,210,120)"), // Light Green
colord("rgb(210,100,160)"), // Hot Pink
colord("rgb(100,140,110)"), // Sea Green
colord("rgb(230,180,180)"), // Light Pink
colord("rgb(120,120,190)"), // Periwinkle
colord("rgb(190,170,100)"), // Sand
colord("rgb(100,180,160)"), // Aquamarine
colord("rgb(210,160,200)"), // Orchid
colord("rgb(170,190,100)"), // Yellow Green
colord("rgb(100,130,150)"), // Steel Blue
colord("rgb(230,140,140)"), // Salmon
colord("rgb(140,180,220)"), // Light Blue
colord("rgb(200,160,110)"), // Tan
colord("rgb(180,130,180)"), // Plum
colord("rgb(130,200,130)"), // Light Sea Green
colord("rgb(220,120,120)"), // Coral
colord("rgb(120,160,200)"), // Cornflower Blue
colord("rgb(200,200,140)"), // Khaki
colord("rgb(160,120,160)"), // Purple Gray
colord("rgb(140,180,140)"), // Dark Sea Green
colord("rgb(200,130,110)"), // Dark Salmon
colord("rgb(130,170,190)"), // Cadet Blue
colord("rgb(190,180,160)"), // Tan Gray
colord("rgb(170,140,190)"), // Medium Purple
colord("rgb(160,190,160)"), // Pale Green
colord("rgb(190,150,130)"), // Rosy Brown
colord("rgb(140,150,180)"), // Light Slate Gray
colord("rgb(190,180,160)"), // Tan Gray
colord("rgb(180,170,140)"), // Dark Khaki
colord("rgb(150,130,150)"), // Thistle
colord("rgb(170,190,180)"), // Pale Blue Green
colord("rgb(190,140,150)"), // Puce
colord("rgb(130,180,170)"), // Medium Aquamarine
colord("rgb(180,160,180)"), // Mauve
colord("rgb(160,180,140)"), // Dark Olive Green
colord("rgb(170,150,170)"), // Dusty Rose
colord("rgb(200,200,140)"), // Khaki
colord("rgb(190,170,100)"), // Sand
];
// Bright pastel theme with 64 colors
export const humanColors: Colord[] = [
colord("rgb(16,185,129)"), // Sea Green
colord("rgb(34,197,94)"), // Emerald
colord("rgb(45,212,191)"), // Turquoise
colord("rgb(48,178,180)"), // Teal
colord("rgb(52,211,153)"), // Spearmint
colord("rgb(56,189,248)"), // Light Blue
colord("rgb(59,130,246)"), // Royal Blue
colord("rgb(67,190,84)"), // Fresh Green
colord("rgb(74,222,128)"), // Mint
colord("rgb(79,70,229)"), // Indigo
colord("rgb(82,183,136)"), // Jade
colord("rgb(96,165,250)"), // Sky Blue
colord("rgb(99,202,253)"), // Azure
colord("rgb(110,231,183)"), // Seafoam
colord("rgb(124,58,237)"), // Royal Purple
colord("rgb(125,211,252)"), // Crystal Blue
colord("rgb(132,204,22)"), // Lime
colord("rgb(133,77,14)"), // Chocolate
colord("rgb(134,239,172)"), // Light Green
colord("rgb(147,51,234)"), // Bright Purple
colord("rgb(147,197,253)"), // Powder Blue
colord("rgb(151,255,187)"), // Fresh Mint
colord("rgb(163,230,53)"), // Yellow Green
colord("rgb(167,139,250)"), // Periwinkle
colord("rgb(168,85,247)"), // Vibrant Purple
colord("rgb(179,136,255)"), // Light Purple
colord("rgb(132,204,22)"), // Lime
colord("rgb(16,185,129)"), // Sea Green
colord("rgb(52,211,153)"), // Spearmint
colord("rgb(45,212,191)"), // Turquoise
colord("rgb(74,222,128)"), // Mint
colord("rgb(110,231,183)"), // Seafoam
colord("rgb(134,239,172)"), // Light Green
colord("rgb(151,255,187)"), // Fresh Mint
colord("rgb(186,255,201)"), // Pale Emerald
colord("rgb(230,250,210)"), // Pastel Lime
colord("rgb(34,197,94)"), // Emerald
colord("rgb(67,190,84)"), // Fresh Green
colord("rgb(82,183,136)"), // Jade
colord("rgb(48,178,180)"), // Teal
colord("rgb(230,255,250)"), // Mint Whisper
colord("rgb(220,240,250)"), // Ice Blue
colord("rgb(233,213,255)"), // Light Lilac
colord("rgb(204,204,255)"), // Soft Lavender Blue
colord("rgb(220,220,255)"), // Meringue Blue
colord("rgb(202,225,255)"), // Baby Blue
colord("rgb(147,197,253)"), // Powder Blue
colord("rgb(125,211,252)"), // Crystal Blue
colord("rgb(99,202,253)"), // Azure
colord("rgb(56,189,248)"), // Light Blue
colord("rgb(96,165,250)"), // Sky Blue
colord("rgb(59,130,246)"), // Royal Blue
colord("rgb(79,70,229)"), // Indigo
colord("rgb(124,58,237)"), // Royal Purple
colord("rgb(147,51,234)"), // Bright Purple
colord("rgb(179,136,255)"), // Light Purple
colord("rgb(167,139,250)"), // Periwinkle
colord("rgb(217,70,239)"), // Fuchsia
colord("rgb(168,85,247)"), // Vibrant Purple
colord("rgb(190,92,251)"), // Amethyst
colord("rgb(192,132,252)"), // Lavender
colord("rgb(202,138,4)"), // Rich Gold
colord("rgb(202,225,255)"), // Baby Blue
colord("rgb(204,204,255)"), // Soft Lavender Blue
colord("rgb(217,70,239)"), // Fuchsia
colord("rgb(220,38,38)"), // Ruby
colord("rgb(220,220,255)"), // Meringue Blue
colord("rgb(220,240,250)"), // Ice Blue
colord("rgb(230,250,210)"), // Pastel Lime
colord("rgb(230,255,250)"), // Mint Whisper
colord("rgb(233,213,255)"), // Light Lilac
colord("rgb(234,88,12)"), // Burnt Orange
colord("rgb(234,179,8)"), // Sunflower
colord("rgb(235,75,75)"), // Bright Red
colord("rgb(236,72,153)"), // Deep Pink
colord("rgb(239,68,68)"), // Crimson
colord("rgb(240,171,252)"), // Orchid
colord("rgb(240,240,200)"), // Light Khaki
colord("rgb(244,114,182)"), // Rose
colord("rgb(236,72,153)"), // Deep Pink
colord("rgb(220,38,38)"), // Ruby
colord("rgb(239,68,68)"), // Crimson
colord("rgb(235,75,75)"), // Bright Red
colord("rgb(245,101,101)"), // Coral
colord("rgb(245,158,11)"), // Amber
colord("rgb(248,113,113)"), // Warm Red
colord("rgb(249,115,22)"), // Tangerine
colord("rgb(250,215,225)"), // Cotton Candy
colord("rgb(250,250,210)"), // Pastel Lemon
colord("rgb(251,113,133)"), // Watermelon
colord("rgb(251,146,60)"), // Light Orange
colord("rgb(251,191,36)"), // Marigold
colord("rgb(251,235,245)"), // Rose Powder
colord("rgb(252,165,165)"), // Peach
colord("rgb(252,211,77)"), // Golden
colord("rgb(253,164,175)"), // Salmon Pink
colord("rgb(252,165,165)"), // Peach
colord("rgb(255,204,229)"), // Blush Pink
colord("rgb(255,223,186)"), // Apricot Cream
colord("rgb(250,215,225)"), // Cotton Candy
colord("rgb(251,235,245)"), // Rose Powder
colord("rgb(240,240,200)"), // Light Khaki
colord("rgb(250,250,210)"), // Pastel Lemon
colord("rgb(255,240,200)"), // Vanilla
colord("rgb(255,223,186)"), // Apricot Cream
colord("rgb(252,211,77)"), // Golden
colord("rgb(251,191,36)"), // Marigold
colord("rgb(234,179,8)"), // Sunflower
colord("rgb(202,138,4)"), // Rich Gold
colord("rgb(245,158,11)"), // Amber
colord("rgb(251,146,60)"), // Light Orange
colord("rgb(249,115,22)"), // Tangerine
colord("rgb(234,88,12)"), // Burnt Orange
colord("rgb(133,77,14)"), // Chocolate
];
export const botColors: Colord[] = [
colord("rgb(190,120,120)"), // Muted Red
colord("rgb(120,160,190)"), // Muted Sky Blue
colord("rgb(190,160,100)"), // Muted Golden Yellow
colord("rgb(160,120,190)"), // Muted Purple
colord("rgb(100,170,130)"), // Muted Emerald Green
colord("rgb(190,130,160)"), // Muted Pink
colord("rgb(120,150,100)"), // Muted Olive Green
colord("rgb(190,140,120)"), // Muted Peach
colord("rgb(100,120,160)"), // Muted Navy Blue
colord("rgb(170,170,120)"), // Muted Lime Yellow
colord("rgb(160,120,130)"), // Muted Maroon
colord("rgb(120,170,170)"), // Muted Turquoise
colord("rgb(170,140,100)"), // Muted Light Orange
colord("rgb(140,120,160)"), // Muted Lavender
colord("rgb(150,170,130)"), // Muted Light Green
colord("rgb(170,120,140)"), // Muted Hot Pink
colord("rgb(120,140,120)"), // Muted Sea Green
colord("rgb(180,160,160)"), // Muted Light Pink
colord("rgb(130,130,160)"), // Muted Periwinkle
colord("rgb(160,150,120)"), // Muted Sand
colord("rgb(120,160,150)"), // Muted Aquamarine
colord("rgb(170,150,170)"), // Muted Orchid
colord("rgb(150,160,120)"), // Muted Yellow Green
colord("rgb(120,130,140)"), // Muted Steel Blue
colord("rgb(180,140,140)"), // Muted Salmon
colord("rgb(140,160,170)"), // Muted Light Blue
colord("rgb(170,150,130)"), // Muted Tan
colord("rgb(160,130,160)"), // Muted Plum
colord("rgb(130,170,130)"), // Muted Light Sea Green
colord("rgb(170,130,130)"), // Muted Coral
colord("rgb(130,150,170)"), // Muted Cornflower Blue
colord("rgb(170,170,140)"), // Muted Khaki
colord("rgb(150,130,150)"), // Muted Purple Gray
colord("rgb(140,160,140)"), // Muted Dark Sea Green
colord("rgb(170,130,120)"), // Muted Dark Salmon
colord("rgb(130,150,160)"), // Muted Cadet Blue
colord("rgb(160,160,150)"), // Muted Tan Gray
colord("rgb(150,140,160)"), // Muted Medium Purple
colord("rgb(150,170,150)"), // Muted Pale Green
colord("rgb(160,140,130)"), // Muted Rosy Brown
colord("rgb(140,150,160)"), // Muted Light Slate Gray
colord("rgb(160,150,140)"), // Muted Dark Khaki
colord("rgb(140,130,140)"), // Muted Thistle
colord("rgb(150,160,160)"), // Muted Pale Blue Green
colord("rgb(160,140,150)"), // Muted Puce
colord("rgb(130,160,150)"), // Muted Medium Aquamarine
colord("rgb(160,150,160)"), // Muted Mauve
colord("rgb(150,160,140)"), // Muted Dark Olive Green
colord("rgb(160,160,150)"), // Muted Tan Gray
colord("rgb(170,170,140)"), // Muted Khaki
colord("rgb(170,170,120)"), // Muted Lime Yellow
colord("rgb(150,160,120)"), // Muted Yellow Green
colord("rgb(150,170,130)"), // Muted Light Green
colord("rgb(150,170,150)"), // Muted Pale Green
colord("rgb(130,170,130)"), // Muted Light Sea Green
colord("rgb(140,160,140)"), // Muted Dark Sea Green
colord("rgb(120,150,100)"), // Muted Olive Green
colord("rgb(120,140,120)"), // Muted Sea Green
colord("rgb(100,170,130)"), // Muted Emerald Green
colord("rgb(120,160,150)"), // Muted Aquamarine
colord("rgb(130,160,150)"), // Muted Medium Aquamarine
colord("rgb(120,170,170)"), // Muted Turquoise
colord("rgb(120,160,190)"), // Muted Sky Blue
colord("rgb(130,150,170)"), // Muted Cornflower Blue
colord("rgb(130,150,160)"), // Muted Cadet Blue
colord("rgb(140,150,160)"), // Muted Light Slate Gray
colord("rgb(140,160,170)"), // Muted Light Blue
colord("rgb(150,160,160)"), // Muted Pale Blue Green
colord("rgb(100,120,160)"), // Muted Navy Blue
colord("rgb(120,130,140)"), // Muted Steel Blue
colord("rgb(130,130,160)"), // Muted Periwinkle
colord("rgb(140,130,140)"), // Muted Thistle
colord("rgb(140,120,160)"), // Muted Lavender
colord("rgb(150,130,150)"), // Muted Purple Gray
colord("rgb(150,140,160)"), // Muted Medium Purple
colord("rgb(160,130,160)"), // Muted Plum
colord("rgb(170,150,170)"), // Muted Orchid
colord("rgb(160,120,190)"), // Muted Purple
colord("rgb(160,120,130)"), // Muted Maroon
colord("rgb(170,120,140)"), // Muted Hot Pink
colord("rgb(170,130,120)"), // Muted Dark Salmon
colord("rgb(170,130,130)"), // Muted Coral
colord("rgb(180,140,140)"), // Muted Salmon
colord("rgb(190,130,160)"), // Muted Pink
colord("rgb(190,120,120)"), // Muted Red
colord("rgb(190,140,120)"), // Muted Peach
colord("rgb(190,160,100)"), // Muted Golden Yellow
colord("rgb(170,140,100)"), // Muted Light Orange
colord("rgb(160,140,130)"), // Muted Rosy Brown
colord("rgb(170,150,130)"), // Muted Tan
colord("rgb(160,150,120)"), // Muted Sand
colord("rgb(160,150,140)"), // Muted Dark Khaki
colord("rgb(160,140,150)"), // Muted Puce
colord("rgb(160,150,160)"), // Muted Mauve
colord("rgb(150,140,150)"), // Muted Dusty Rose
colord("rgb(180,160,160)"), // Muted Light Pink
];
// Fallback colors for when the color palette is exhausted.
+2
View File
@@ -190,6 +190,8 @@ export interface Theme {
// Don't call directly, use PlayerView
territoryColor(playerInfo: PlayerView): Colord;
// Don't call directly, use PlayerView
structureColors(territoryColor: Colord): { light: Colord; dark: Colord };
// Don't call directly, use PlayerView
borderColor(territoryColor: Colord): Colord;
// Don't call directly, use PlayerView
defendedBorderColors(territoryColor: Colord): { light: Colord; dark: Colord };
+54 -1
View File
@@ -1,4 +1,4 @@
import { Colord, colord } from "colord";
import { Colord, colord, LabaColor } from "colord";
import { PseudoRandom } from "../PseudoRandom";
import { PlayerType, Team, TerrainType } from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
@@ -65,6 +65,59 @@ export class PastelTheme implements Theme {
return this.nationColorAllocator.assignColor(player.id());
}
structureColors(territoryColor: Colord): { light: Colord; dark: Colord } {
// Convert territory color to LAB color space. Territory color is rendered in game with alpha = 150/255, use that here.
const lightLAB = territoryColor.alpha(150 / 255).toLab();
// Get "border color" from territory color & convert to LAB color space
const darkLAB = this.borderColor(territoryColor).toLab();
// Calculate the contrast of the two provided colors
let contrast = this.contrast(lightLAB, darkLAB);
// Don't want excessive contrast, so incrementally increase contrast within a loop.
// Define target values, looping limits, and loop counter
const loopLimit = 10; // Switch from darkening border to lightening fill if loopLimit is reached
const maxIterations = 50; // maximum number of loops allowed, throw error above this limit
const contrastTarget = 0.5;
let loopCount = 0;
// Adjust luminance by 5 in each iteration. This is a balance between speed and not overdoing contrast changes.
const luminanceChange = 5;
while (contrast < contrastTarget) {
if (loopCount > maxIterations) {
// Prevent runaway loops
console.warn(`Infinite loop detected during structure color calculation.
Light color: ${colord(lightLAB).toRgbString()},
Dark color: ${colord(darkLAB).toRgbString()},
Contrast: ${contrast}`);
break;
// Increase the light color if the "loop limit" has been reach
// (probably due to the dark color already being as dark as it can be)
} else if (loopCount > loopLimit) {
lightLAB.l = this.clamp(lightLAB.l + luminanceChange);
// Decrease the dark color first to keep the light color as close
// to the territory color as possible
} else {
darkLAB.l = this.clamp(darkLAB.l - luminanceChange);
}
// re-calculate contrast and increment loop counter
contrast = this.contrast(lightLAB, darkLAB);
loopCount++;
}
return { light: colord(lightLAB), dark: colord(darkLAB) };
}
private contrast(first: LabaColor, second: LabaColor): number {
return colord(first).delta(colord(second));
}
private clamp(num: number, low: number = 0, high: number = 100): number {
return Math.min(Math.max(low, num), high);
}
// Don't call directly, use PlayerView
borderColor(territoryColor: Colord): Colord {
return territoryColor.darken(0.125);
+11
View File
@@ -184,6 +184,8 @@ export class PlayerView {
private _territoryColor: Colord;
private _borderColor: Colord;
// Update here to include structure light and dark colors
private _structureColors: { light: Colord; dark: Colord };
private _defendedBorderColors: { light: Colord; dark: Colord };
constructor(
@@ -229,6 +231,11 @@ export class PlayerView {
this._territoryColor = defaultTerritoryColor;
}
this._structureColors = this.game
.config()
.theme()
.structureColors(this._territoryColor);
const maybeFocusedBorderColor =
this.game.myClientID() === this.data.clientID
? this.game.config().theme().focusedBorderColor()
@@ -262,6 +269,10 @@ export class PlayerView {
return isPrimary ? this._territoryColor : this._borderColor;
}
structureColors(): { light: Colord; dark: Colord } {
return this._structureColors;
}
borderColor(tile?: TileRef, isDefended: boolean = false): Colord {
if (tile === undefined || !isDefended) {
return this._borderColor;