1 | 'use strict';
|
2 | import {
|
3 | hsvToColor,
|
4 | RGBtoHSV,
|
5 | rgbaColor,
|
6 | processColor,
|
7 | red,
|
8 | green,
|
9 | blue,
|
10 | opacity,
|
11 | } from './Colors';
|
12 | import { makeMutable } from './core';
|
13 | import { Extrapolation, interpolate } from './interpolation';
|
14 | import type { SharedValue } from './commonTypes';
|
15 | import { useSharedValue } from './hook/useSharedValue';
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | export const Extrapolate = Extrapolation;
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | export type InterpolationOptions = {
|
29 | gamma?: number;
|
30 | useCorrectedHSVInterpolation?: boolean;
|
31 | };
|
32 |
|
33 | const 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 |
|
44 |
|
45 |
|
46 |
|
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 |
|
86 | const toLinearSpace = (x: number[], gamma: number): number[] => {
|
87 | 'worklet';
|
88 | return x.map((v) => Math.pow(v / 255, gamma));
|
89 | };
|
90 |
|
91 | const toGammaSpace = (x: number, gamma: number): number => {
|
92 | 'worklet';
|
93 | return Math.round(Math.pow(x, 1 / gamma) * 255);
|
94 | };
|
95 |
|
96 | const 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 |
|
125 | export interface InterpolateRGB {
|
126 | r: number[];
|
127 | g: number[];
|
128 | b: number[];
|
129 | a: number[];
|
130 | }
|
131 |
|
132 | const 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 |
|
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 |
|
155 | export interface InterpolateHSV {
|
156 | h: number[];
|
157 | s: number[];
|
158 | v: number[];
|
159 | a: number[];
|
160 | }
|
161 |
|
162 | const 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 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 | export function interpolateColor(
|
201 | value: number,
|
202 | inputRange: readonly number[],
|
203 | outputRange: readonly string[],
|
204 | colorSpace?: 'RGB' | 'HSV',
|
205 | options?: InterpolationOptions
|
206 | ): string;
|
207 |
|
208 | export function interpolateColor(
|
209 | value: number,
|
210 | inputRange: readonly number[],
|
211 | outputRange: readonly number[],
|
212 | colorSpace?: 'RGB' | 'HSV',
|
213 | options?: InterpolationOptions
|
214 | ): number;
|
215 |
|
216 | export 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 |
|
246 | export enum ColorSpace {
|
247 | RGB = 0,
|
248 | HSV = 1,
|
249 | }
|
250 |
|
251 | export 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 |
|
259 | export 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 | }
|