diff --git a/lib/color-util.test.ts b/lib/color-util.test.ts new file mode 100644 index 0000000..f319921 --- /dev/null +++ b/lib/color-util.test.ts @@ -0,0 +1,25 @@ +import { clampedBytesToRGBColor, clampedByteToHex } from "./color-util"; + +describe("clampedBytesToRGBColor", () => { + it("works", () => { + expect(clampedBytesToRGBColor([999, 5, 171])).toBe("#ff05ab"); + }); +}); + +describe("clampedByteToHex", () => { + it("clamps values over 255 to 255", () => { + expect(clampedByteToHex(500)).toBe("ff"); + }); + + it("clamps values under 0 to 0", () => { + expect(clampedByteToHex(-50)).toBe("00"); + }); + + it("zero-pads values", () => { + expect(clampedByteToHex(10)).toBe("0a"); + }); + + it("works with numbers that don't need zero-padding", () => { + expect(clampedByteToHex(22)).toBe("16"); + }); +}); diff --git a/lib/color-util.ts b/lib/color-util.ts new file mode 100644 index 0000000..8561bc3 --- /dev/null +++ b/lib/color-util.ts @@ -0,0 +1,23 @@ +/** + * Clamp the given number to be between 0 and 255, then + * convert it to hexadecimal. + */ +export function clampedByteToHex(value: number): string { + if (value < 0) { + value = 0; + } else if (value > 255) { + value = 255; + } + let hex = value.toString(16); + if (hex.length === 1) { + hex = "0" + hex; + } + return hex; +} + +/** + * Convert the given array of numbers to an RGB hex value. + */ +export function clampedBytesToRGBColor(values: number[]): string { + return "#" + values.map(clampedByteToHex).join(""); +} diff --git a/lib/pages/mandala-page/serialization.ts b/lib/pages/mandala-page/serialization.ts index 8e7f3be..ff509c4 100644 --- a/lib/pages/mandala-page/serialization.ts +++ b/lib/pages/mandala-page/serialization.ts @@ -7,7 +7,6 @@ import type { AvroSvgCompositionContext, } from "./mandala-design.avsc"; import * as avro from "avro-js"; -import { clampedByteToHex } from "../../random-colors"; import { MANDALA_DESIGN_DEFAULTS, ExtendedMandalaCircleParams, @@ -15,6 +14,7 @@ import { getCirclesFromDesign, } from "./core"; import { fromBase64, toBase64 } from "../../base64"; +import { clampedBytesToRGBColor } from "../../color-util"; const avroMandalaDesign = avro.parse(MandalaAvsc); @@ -70,7 +70,7 @@ export const ColorPacker: Packer = { const red = (number >> 16) & 0xff; const green = (number >> 8) & 0xff; const blue = number & 0xff; - return "#" + [red, green, blue].map(clampedByteToHex).join(""); + return clampedBytesToRGBColor([red, green, blue]); }, }; diff --git a/lib/random-colors.test.ts b/lib/random-colors.test.ts index 43f6644..3187b74 100644 --- a/lib/random-colors.test.ts +++ b/lib/random-colors.test.ts @@ -1,27 +1,8 @@ import { - clampedByteToHex, createRandomColorPalette, RANDOM_PALETTE_ALGORITHMS, } from "./random-colors"; -describe("clampedByteToHex", () => { - it("clamps values over 255 to 255", () => { - expect(clampedByteToHex(500)).toBe("ff"); - }); - - it("clamps values under 0 to 0", () => { - expect(clampedByteToHex(-50)).toBe("00"); - }); - - it("zero-pads values", () => { - expect(clampedByteToHex(10)).toBe("0a"); - }); - - it("works with numbers that don't need zero-padding", () => { - expect(clampedByteToHex(22)).toBe("16"); - }); -}); - describe("createRandomColorPalette()", () => { for (let alg of RANDOM_PALETTE_ALGORITHMS) { it(`works using the '${alg}' algorithm`, () => { diff --git a/lib/random-colors.ts b/lib/random-colors.ts index 90434e5..de5be3a 100644 --- a/lib/random-colors.ts +++ b/lib/random-colors.ts @@ -2,6 +2,7 @@ import { Random } from "./random"; import { range, clamp } from "./util"; import * as colorspaces from "colorspaces"; import { ColorTuple, hsluvToHex } from "hsluv"; +import { clampedBytesToRGBColor } from "./color-util"; type RandomPaletteGenerator = (numEntries: number, rng: Random) => string[]; //type ColorFunction = (rng: Random) => string[]; @@ -18,26 +19,9 @@ export type RandomPaletteAlgorithm = "RGB" | "CIELUV" | "threevals"; export const DEFAULT_RANDOM_PALETTE_ALGORITHM: RandomPaletteAlgorithm = "threevals"; -/** - * Clamp the given number to be between 0 and 255, then - * convert it to hexadecimal. - */ -export function clampedByteToHex(value: number): string { - if (value < 0) { - value = 0; - } else if (value > 255) { - value = 255; - } - let hex = value.toString(16); - if (hex.length === 1) { - hex = "0" + hex; - } - return hex; -} - function createRandomRGBColor(rng: Random): string { const rgb = range(3).map(() => rng.inRange({ min: 0, max: 255, step: 1 })); - return "#" + rgb.map(clampedByteToHex).join(""); + return clampedBytesToRGBColor(rgb); } function createRandomCIELUVColor(rng: Random): string { diff --git a/lib/vocabulary-builder.ts b/lib/vocabulary-builder.ts index 1589a4b..5a3ac6a 100644 --- a/lib/vocabulary-builder.ts +++ b/lib/vocabulary-builder.ts @@ -11,8 +11,8 @@ import { } from "./svg-symbol"; import toml from "toml"; import { validateSvgSymbolMetadata } from "./svg-symbol-metadata"; -import { clampedByteToHex } from "./random-colors"; import { withoutNulls } from "./util"; +import { clampedBytesToRGBColor } from "./color-util"; const SUPPORTED_SVG_TAG_ARRAY: SvgSymbolElement["tagName"][] = ["g", "path"]; const SUPPORTED_SVG_TAGS = new Set(SUPPORTED_SVG_TAG_ARRAY); @@ -108,7 +108,7 @@ function parseRadialGradient(el: cheerio.TagElement): SvgSymbolDef { .map((value) => parseInt(value)); stops.push({ offset: getNonEmptyAttrib(child, "offset"), - color: "#" + rgb.map(clampedByteToHex).join(""), + color: clampedBytesToRGBColor(rgb), }); } }