@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
189 lines (171 loc) • 5.13 kB
text/typescript
export enum Extrapolate {
IDENTITY = "identity",
CLAMP = "clamp",
EXTEND = "extend",
}
interface InterpolationNarrowedInput {
leftEdgeInput: number;
rightEdgeInput: number;
leftEdgeOutput: number;
rightEdgeOutput: number;
}
export interface ExtrapolationConfig {
extrapolateLeft?: Extrapolate | string;
extrapolateRight?: Extrapolate | string;
}
interface RequiredExtrapolationConfig {
extrapolateLeft: Extrapolate;
extrapolateRight: Extrapolate;
}
export type ExtrapolationType =
| ExtrapolationConfig
| Extrapolate
| string
| undefined;
function getVal(
type: Extrapolate,
coef: number,
val: number,
leftEdgeOutput: number,
rightEdgeOutput: number,
x: number
): number {
"worklet";
switch (type) {
case Extrapolate.IDENTITY:
return x;
case Extrapolate.CLAMP:
if (coef * val < coef * leftEdgeOutput) {
return leftEdgeOutput;
}
return rightEdgeOutput;
case Extrapolate.EXTEND:
default:
return val;
}
}
function isExtrapolate(value: string): value is Extrapolate {
"worklet";
return (
value === Extrapolate.EXTEND ||
value === Extrapolate.CLAMP ||
value === Extrapolate.IDENTITY
);
}
// validates extrapolations type
// if type is correct, converts it to ExtrapolationConfig
export function validateInterpolationOptions(
type: ExtrapolationType
): RequiredExtrapolationConfig {
"worklet";
// initialize extrapolationConfig with default extrapolation
const extrapolationConfig: RequiredExtrapolationConfig = {
extrapolateLeft: Extrapolate.EXTEND,
extrapolateRight: Extrapolate.EXTEND,
};
if (!type) {
return extrapolationConfig;
}
if (typeof type === "string") {
if (!isExtrapolate(type)) {
throw new Error(
`No supported value for "interpolate" \nSupported values: ["extend", "clamp", "identity", Extrapolatation.CLAMP, Extrapolatation.EXTEND, Extrapolatation.IDENTITY]\n Valid example:
interpolate(value, [inputRange], [outputRange], "clamp")`
);
}
extrapolationConfig.extrapolateLeft = type;
extrapolationConfig.extrapolateRight = type;
return extrapolationConfig;
}
// otherwise type is extrapolation config object
if (
(type.extrapolateLeft && !isExtrapolate(type.extrapolateLeft)) ||
(type.extrapolateRight && !isExtrapolate(type.extrapolateRight))
) {
throw new Error(
`No supported value for "interpolate" \nSupported values: ["extend", "clamp", "identity", Extrapolatation.CLAMP, Extrapolatation.EXTEND, Extrapolatation.IDENTITY]\n Valid example:
interpolate(value, [inputRange], [outputRange], {
extrapolateLeft: Extrapolation.CLAMP,
extrapolateRight: Extrapolation.IDENTITY
}})`
);
}
Object.assign(extrapolationConfig, type);
return extrapolationConfig;
}
function internalInterpolate(
x: number,
narrowedInput: InterpolationNarrowedInput,
extrapolationConfig: RequiredExtrapolationConfig
) {
"worklet";
const { leftEdgeInput, rightEdgeInput, leftEdgeOutput, rightEdgeOutput } =
narrowedInput;
if (rightEdgeInput - leftEdgeInput === 0) {
return leftEdgeOutput;
}
const progress = (x - leftEdgeInput) / (rightEdgeInput - leftEdgeInput);
const val = leftEdgeOutput + progress * (rightEdgeOutput - leftEdgeOutput);
const coef = rightEdgeOutput >= leftEdgeOutput ? 1 : -1;
if (coef * val < coef * leftEdgeOutput) {
return getVal(
extrapolationConfig.extrapolateLeft,
coef,
val,
leftEdgeOutput,
rightEdgeOutput,
x
);
} else if (coef * val > coef * rightEdgeOutput) {
return getVal(
extrapolationConfig.extrapolateRight,
coef,
val,
leftEdgeOutput,
rightEdgeOutput,
x
);
}
return val;
}
// e.g. function interpolate(x, input, output, type = Extrapolatation.CLAMP)
export function interpolate(
x: number,
input: readonly number[],
output: readonly number[],
type?: ExtrapolationType
): number {
"worklet";
if (input.length < 2 || output.length < 2) {
throw Error(
"Interpolation input and output should contain at least two values."
);
}
const extrapolationConfig = validateInterpolationOptions(type);
const { length } = input;
const narrowedInput: InterpolationNarrowedInput = {
leftEdgeInput: input[0],
rightEdgeInput: input[1],
leftEdgeOutput: output[0],
rightEdgeOutput: output[1],
};
if (length > 2) {
if (x > input[length - 1]) {
narrowedInput.leftEdgeInput = input[length - 2];
narrowedInput.rightEdgeInput = input[length - 1];
narrowedInput.leftEdgeOutput = output[length - 2];
narrowedInput.rightEdgeOutput = output[length - 1];
} else {
for (let i = 1; i < length; ++i) {
if (x <= input[i]) {
narrowedInput.leftEdgeInput = input[i - 1];
narrowedInput.rightEdgeInput = input[i];
narrowedInput.leftEdgeOutput = output[i - 1];
narrowedInput.rightEdgeOutput = output[i];
break;
}
}
}
}
return internalInterpolate(x, narrowedInput, extrapolationConfig);
}