UNPKG

7.81 kBPlain TextView Raw
1'use strict';
2import {
3 hsvToColor,
4 RGBtoHSV,
5 rgbaColor,
6 processColor,
7 red,
8 green,
9 blue,
10 opacity,
11} from './Colors';
12import { makeMutable } from './core';
13import { Extrapolation, interpolate } from './interpolation';
14import type { SharedValue } from './commonTypes';
15import { useSharedValue } from './hook/useSharedValue';
16
17/**
18 * @deprecated Please use Extrapolation instead
19 */
20export const Extrapolate = Extrapolation;
21
22/**
23 * Options for color interpolation.
24 *
25 * @param gamma - Gamma value used in gamma correction. Defaults to `2.2`.
26 * @param useCorrectedHSVInterpolation - Whether to reduce the number of colors the interpolation has to go through. Defaults to `true`.
27 */
28export type InterpolationOptions = {
29 gamma?: number;
30 useCorrectedHSVInterpolation?: boolean;
31};
32
33const interpolateColorsHSV = (
34 value: number,
35 inputRange: readonly number[],
36 colors: InterpolateHSV,
37 options: InterpolationOptions
38) => {
39 'worklet';
40 let h = 0;
41 const { useCorrectedHSVInterpolation = true } = options;
42 if (useCorrectedHSVInterpolation) {
43 // if the difference between hues in a range is > 180 deg
44 // then move the hue at the right end of the range +/- 360 deg
45 // and add the next point in the original place + 0.00001 with original hue
46 // to not break the next range
47 const correctedInputRange = [inputRange[0]];
48 const originalH = colors.h;
49 const correctedH = [originalH[0]];
50
51 for (let i = 1; i < originalH.length; ++i) {
52 const d = originalH[i] - originalH[i - 1];
53 if (originalH[i] > originalH[i - 1] && d > 0.5) {
54 correctedInputRange.push(inputRange[i]);
55 correctedInputRange.push(inputRange[i] + 0.00001);
56 correctedH.push(originalH[i] - 1);
57 correctedH.push(originalH[i]);
58 } else if (originalH[i] < originalH[i - 1] && d < -0.5) {
59 correctedInputRange.push(inputRange[i]);
60 correctedInputRange.push(inputRange[i] + 0.00001);
61 correctedH.push(originalH[i] + 1);
62 correctedH.push(originalH[i]);
63 } else {
64 correctedInputRange.push(inputRange[i]);
65 correctedH.push(originalH[i]);
66 }
67 }
68 h =
69 (interpolate(
70 value,
71 correctedInputRange,
72 correctedH,
73 Extrapolation.CLAMP
74 ) +
75 1) %
76 1;
77 } else {
78 h = interpolate(value, inputRange, colors.h, Extrapolation.CLAMP);
79 }
80 const s = interpolate(value, inputRange, colors.s, Extrapolation.CLAMP);
81 const v = interpolate(value, inputRange, colors.v, Extrapolation.CLAMP);
82 const a = interpolate(value, inputRange, colors.a, Extrapolation.CLAMP);
83 return hsvToColor(h, s, v, a);
84};
85
86const toLinearSpace = (x: number[], gamma: number): number[] => {
87 'worklet';
88 return x.map((v) => Math.pow(v / 255, gamma));
89};
90
91const toGammaSpace = (x: number, gamma: number): number => {
92 'worklet';
93 return Math.round(Math.pow(x, 1 / gamma) * 255);
94};
95
96const interpolateColorsRGB = (
97 value: number,
98 inputRange: readonly number[],
99 colors: InterpolateRGB,
100 options: InterpolationOptions
101) => {
102 'worklet';
103 const { gamma = 2.2 } = options;
104 let { r: outputR, g: outputG, b: outputB } = colors;
105 if (gamma !== 1) {
106 outputR = toLinearSpace(outputR, gamma);
107 outputG = toLinearSpace(outputG, gamma);
108 outputB = toLinearSpace(outputB, gamma);
109 }
110 const r = interpolate(value, inputRange, outputR, Extrapolation.CLAMP);
111 const g = interpolate(value, inputRange, outputG, Extrapolation.CLAMP);
112 const b = interpolate(value, inputRange, outputB, Extrapolation.CLAMP);
113 const a = interpolate(value, inputRange, colors.a, Extrapolation.CLAMP);
114 if (gamma === 1) {
115 return rgbaColor(r, g, b, a);
116 }
117 return rgbaColor(
118 toGammaSpace(r, gamma),
119 toGammaSpace(g, gamma),
120 toGammaSpace(b, gamma),
121 a
122 );
123};
124
125export interface InterpolateRGB {
126 r: number[];
127 g: number[];
128 b: number[];
129 a: number[];
130}
131
132const getInterpolateRGB = (
133 colors: readonly (string | number)[]
134): InterpolateRGB => {
135 'worklet';
136
137 const r = [];
138 const g = [];
139 const b = [];
140 const a = [];
141 for (let i = 0; i < colors.length; ++i) {
142 const color = colors[i];
143 const processedColor = processColor(color);
144 // explicit check in case if processedColor is 0
145 if (processedColor !== null && processedColor !== undefined) {
146 r.push(red(processedColor));
147 g.push(green(processedColor));
148 b.push(blue(processedColor));
149 a.push(opacity(processedColor));
150 }
151 }
152 return { r, g, b, a };
153};
154
155export interface InterpolateHSV {
156 h: number[];
157 s: number[];
158 v: number[];
159 a: number[];
160}
161
162const getInterpolateHSV = (
163 colors: readonly (string | number)[]
164): InterpolateHSV => {
165 'worklet';
166 const h = [];
167 const s = [];
168 const v = [];
169 const a = [];
170 for (let i = 0; i < colors.length; ++i) {
171 const color = colors[i];
172 const processedColor = processColor(color) as any;
173 if (typeof processedColor === 'number') {
174 const processedHSVColor = RGBtoHSV(
175 red(processedColor),
176 green(processedColor),
177 blue(processedColor)
178 );
179
180 h.push(processedHSVColor.h);
181 s.push(processedHSVColor.s);
182 v.push(processedHSVColor.v);
183 a.push(opacity(processedColor));
184 }
185 }
186 return { h, s, v, a };
187};
188
189/**
190 * Lets you map a value from a range of numbers to a range of colors using linear interpolation.
191 *
192 * @param value - A number from the `input` range that is going to be mapped to the color in the `output` range.
193 * @param inputRange - An array of numbers specifying the input range of the interpolation.
194 * @param outputRange - An array of output colors values (eg. "red", "#00FFCC", "rgba(255, 0, 0, 0.5)").
195 * @param colorSpace - The color space to use for interpolation. Defaults to 'RGB'.
196 * @param options - Additional options for interpolation - {@link InterpolationOptions}.
197 * @returns The color after interpolation from within the output range in rgba(r, g, b, a) format.
198 * @see https://docs.swmansion.com/react-native-reanimated/docs/utilities/interpolateColor
199 */
200export function interpolateColor(
201 value: number,
202 inputRange: readonly number[],
203 outputRange: readonly string[],
204 colorSpace?: 'RGB' | 'HSV',
205 options?: InterpolationOptions
206): string;
207
208export function interpolateColor(
209 value: number,
210 inputRange: readonly number[],
211 outputRange: readonly number[],
212 colorSpace?: 'RGB' | 'HSV',
213 options?: InterpolationOptions
214): number;
215
216export function interpolateColor(
217 value: number,
218 inputRange: readonly number[],
219 outputRange: readonly (string | number)[],
220 colorSpace: 'RGB' | 'HSV' = 'RGB',
221 options: InterpolationOptions = {}
222): string | number {
223 'worklet';
224 if (colorSpace === 'HSV') {
225 return interpolateColorsHSV(
226 value,
227 inputRange,
228 getInterpolateHSV(outputRange),
229 options
230 );
231 } else if (colorSpace === 'RGB') {
232 return interpolateColorsRGB(
233 value,
234 inputRange,
235 getInterpolateRGB(outputRange),
236 options
237 );
238 }
239 throw new Error(
240 `[Reanimated] Invalid color space provided: ${
241 colorSpace as string
242 }. Supported values are: ['RGB', 'HSV'].`
243 );
244}
245
246export enum ColorSpace {
247 RGB = 0,
248 HSV = 1,
249}
250
251export interface InterpolateConfig {
252 inputRange: readonly number[];
253 outputRange: readonly (string | number)[];
254 colorSpace: ColorSpace;
255 cache: SharedValue<InterpolateRGB | InterpolateHSV | null>;
256 options: InterpolationOptions;
257}
258
259export function useInterpolateConfig(
260 inputRange: readonly number[],
261 outputRange: readonly (string | number)[],
262 colorSpace = ColorSpace.RGB,
263 options: InterpolationOptions = {}
264): SharedValue<InterpolateConfig> {
265 return useSharedValue<InterpolateConfig>({
266 inputRange,
267 outputRange,
268 colorSpace,
269 cache: makeMutable<InterpolateRGB | InterpolateHSV | null>(null),
270 options,
271 });
272}