From ba64b0b4c30d2caef686ccb067e0099c30f065d6 Mon Sep 17 00:00:00 2001 From: PeenScreeker Date: Fri, 1 Dec 2023 04:21:37 -0500 Subject: [PATCH] WIP HSV lerp --- scripts/util/colors.ts | 82 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/scripts/util/colors.ts b/scripts/util/colors.ts index ad325b56..b28f3dc9 100644 --- a/scripts/util/colors.ts +++ b/scripts/util/colors.ts @@ -59,10 +59,88 @@ 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(newHsv); +} +/** Converts an HSV color to a linear space RGB color */ +function HSVToLinearRGB([h, s, v, a]: RgbaTuple): number[] { + // In this color, R = H, G = S, B = V + const Hue = h; + const Saturation = s; + const Value = v; + + const HDiv60 = Hue / 60; + const HDiv60_Floor = Math.floor(HDiv60); + const HDiv60_Fraction = HDiv60 - HDiv60_Floor; + + const RGBValues: RgbaTuple = [ + Value, + Value * (1 - Saturation), + Value * (1 - HDiv60_Fraction * Saturation), + Value * (1 - (1 - HDiv60_Fraction) * Saturation) + ]; + const RGBSwizzle = [ + [0, 3, 1], + [2, 0, 1], + [1, 0, 3], + [1, 2, 0], + [3, 1, 0], + [0, 1, 2] + ]; + const SwizzleIndex = HDiv60_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]; } /**