diff --git a/scripts/util/colors.ts b/scripts/util/colors.ts index ad325b56..15d98da1 100644 --- a/scripts/util/colors.ts +++ b/scripts/util/colors.ts @@ -59,10 +59,78 @@ function rgbaTupleLerp(colorA: RgbaTuple, colorB: RgbaTuple, alpha: number): Rgb * Blends two colors linearly (not HSV lerp). * RGB inputs are converted to RGBA with A value of 1. */ -function rgbaStringLerp(colorA: string, colorB: string, alpha: number): string { +function rgbaStringLerp(colorA: string, colorB: string, alpha: number, useHsv: boolean = false): string { const arrayA = rgbaStringToTuple(colorA); const arrayB = rgbaStringToTuple(colorB); - return tupleToRgbaString(rgbaTupleLerp(arrayA, arrayB, alpha)); + if (!useHsv) return tupleToRgbaString(rgbaTupleLerp(arrayA, arrayB, alpha)); + + const FromHSV: RgbaTuple = LinearRGBToHSV(arrayA) as RgbaTuple; + const ToHSV: RgbaTuple = LinearRGBToHSV(arrayB) as RgbaTuple; + + // Take the shortest path to the new hue + if (Math.abs(FromHSV[0] - ToHSV[0]) > 180) { + if (ToHSV[0] > FromHSV[0]) { + FromHSV[0] += 360; + } else { + ToHSV[0] += 360; + } + } + const newHsv = rgbaTupleLerp(FromHSV, ToHSV, alpha); + + newHsv[0] = newHsv[0] % 360; + if (newHsv[0] < 0) { + newHsv[0] += 360; + } + + const newRgb: RgbaTuple = HSVToLinearRGB(newHsv) as RgbaTuple; + + return tupleToRgbaString(newRgb); +} +/** Converts an HSV color to a linear space RGB color */ +function HSVToLinearRGB([h, s, v, a]: RgbaTuple): number[] { + const hueDir = h / 60; + const hueDir_Floor = Math.floor(hueDir); + const hueDir_Fraction = hueDir - hueDir_Floor; + + const RGBValues: RgbaTuple = [v, v * (1 - s), v * (1 - hueDir_Fraction * s), v * (1 - (1 - hueDir_Fraction) * s)]; + const RGBSwizzle = [ + [0, 3, 1], + [2, 0, 1], + [1, 0, 3], + [1, 2, 0], + [3, 1, 0], + [0, 1, 2] + ]; + const SwizzleIndex = hueDir_Floor % 6; + + return [ + RGBValues[RGBSwizzle[SwizzleIndex][0]] * 255, + RGBValues[RGBSwizzle[SwizzleIndex][1]] * 255, + RGBValues[RGBSwizzle[SwizzleIndex][2]] * 255, + a + ]; +} + +function LinearRGBToHSV([r, g, b, a]: RgbaTuple): number[] { + const RGBMin = Math.min(r, g, b); + const RGBMax = Math.max(r, g, b); + const RGBRange = RGBMax - RGBMin; + + const Hue = + RGBMax === RGBMin + ? 0 + : RGBMax === r + ? (((g - b) / RGBRange) * 60 + 360) % 360 + : RGBMax === g + ? ((b - r) / RGBRange) * 60 + 120 + : RGBMax === b + ? ((r - g) / RGBRange) * 60 + 240 + : 0; + + const Saturation = RGBMax === 0 ? 0 : RGBRange / RGBMax; + const Value = RGBMax / 255; + + return [Hue, Saturation, Value, a]; } /**