UNPKG

react-native-redash

Version:
384 lines (349 loc) 8.21 kB
/* eslint-disable prefer-destructuring */ export type Vec2 = readonly [number, number]; type Vec3 = readonly [number, number, number]; export type Matrix3 = readonly [ number, number, number, number, number, number, number, number, number, ]; export interface TransformProp { transform: Transforms2d; } type Transformations = { translateX: number; translateY: number; scale: number; skewX: string; skewY: string; scaleX: number; scaleY: number; rotateZ: string; rotate: string; }; export type Transforms2d = ( | Pick<Transformations, "translateX"> | Pick<Transformations, "translateY"> | Pick<Transformations, "scale"> | Pick<Transformations, "scaleX"> | Pick<Transformations, "scaleY"> | Pick<Transformations, "skewX"> | Pick<Transformations, "skewY"> | Pick<Transformations, "rotate"> | Pick<Transformations, "rotateZ"> )[]; /** * @worklet */ export const parseAngle = (angle: string) => { "worklet"; if (angle.endsWith("deg")) { return parseFloat(angle) * (Math.PI / 180); } return parseFloat(angle); }; /** * @worklet */ export const isTranslateX = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "translateX"> => { "worklet"; return Object.keys(transform).indexOf("translateX") !== -1; }; /** * @worklet */ export const isTranslateY = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "translateY"> => { "worklet"; return Object.keys(transform).indexOf("translateY") !== -1; }; /** * @worklet */ export const isScale = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "scale"> => { "worklet"; return Object.keys(transform).indexOf("scale") !== -1; }; /** * @worklet */ export const isScaleX = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "scaleX"> => { "worklet"; return Object.keys(transform).indexOf("scaleX") !== -1; }; /** * @worklet */ export const isScaleY = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "scaleY"> => { "worklet"; return Object.keys(transform).indexOf("scaleY") !== -1; }; /** * @worklet */ export const isSkewX = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "skewX"> => { "worklet"; return Object.keys(transform).indexOf("skewX") !== -1; }; /** * @worklet */ export const isSkewY = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "skewY"> => { "worklet"; return Object.keys(transform).indexOf("skewY") !== -1; }; /** * @worklet */ export const isRotate = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "rotate"> => { "worklet"; return Object.keys(transform).indexOf("rotate") !== -1; }; /** * @worklet */ export const isRotateZ = ( transform: Transforms2d[0] ): transform is Pick<Transformations, "rotateZ"> => { "worklet"; return Object.keys(transform).indexOf("rotateZ") !== -1; }; /** * @worklet */ const exhaustiveCheck = (a: never): never => { "worklet"; throw new Error(`Unexhaustive handling for ${a}`); }; export const identity3: Matrix3 = [1, 0, 0, 0, 1, 0, 0, 0, 1]; /** * @worklet */ const translateXMatrix = (x: number): Matrix3 => { "worklet"; return [1, 0, x, 0, 1, 0, 0, 0, 1]; }; /** * @worklet */ const translateYMatrix = (y: number): Matrix3 => { "worklet"; return [1, 0, 0, 0, 1, y, 0, 0, 1]; }; /** * @worklet */ const scaleMatrix = (s: number): Matrix3 => { "worklet"; return [s, 0, 0, 0, s, 0, 0, 0, 1]; }; /** * @worklet */ const scaleXMatrix = (s: number): Matrix3 => { "worklet"; return [s, 0, 0, 0, 1, 0, 0, 0, 1]; }; /** * @worklet */ const scaleYMatrix = (s: number): Matrix3 => { "worklet"; return [1, 0, 0, 0, s, 0, 0, 0, 1]; }; /** * @worklet */ const skewXMatrix = (s: number): Matrix3 => { "worklet"; return [1, Math.tan(s), 0, 0, 1, 0, 0, 0, 1]; }; /** * @worklet */ const skewYMatrix = (s: number): Matrix3 => { "worklet"; return [1, 0, 0, Math.tan(s), 1, 0, 0, 0, 1]; }; /** * @worklet */ const rotateZMatrix = (r: number): Matrix3 => { "worklet"; return [ Math.cos(r), -1 * Math.sin(r), 0, Math.sin(r), Math.cos(r), 0, 0, 0, 1, ]; }; /** * @worklet */ export const dot3 = (row: Vec3, col: Vec3) => { "worklet"; return row[0] * col[0] + row[1] * col[1] + row[2] * col[2]; }; /** * @worklet */ export const matrixVecMul3 = (m: Matrix3, v: Vec3) => { "worklet"; return [ dot3([m[0], m[1], m[2]], v), dot3([m[3], m[4], m[5]], v), dot3([m[6], m[7], m[8]], v), ] as const; }; /** * @worklet */ export const mapPoint = (m: Matrix3, v: Vec2) => { "worklet"; const r = matrixVecMul3(m, [v[0], v[1], 1]); return [r[0] / r[2], r[1] / r[2]] as const; }; /** * @worklet */ export const multiply3 = (m1: Matrix3, m2: Matrix3) => { "worklet"; const row0 = [m1[0], m1[1], m1[2]] as const; const row1 = [m1[3], m1[4], m1[5]] as const; const row2 = [m1[6], m1[7], m1[8]] as const; const col0 = [m2[0], m2[3 + 0], m2[6 + 0]] as const; const col1 = [m2[1], m2[3 + 1], m2[6 + 1]] as const; const col2 = [m2[2], m2[3 + 2], m2[6 + 2]] as const; return [ dot3(row0, col0), dot3(row0, col1), dot3(row0, col2), dot3(row1, col0), dot3(row1, col1), dot3(row1, col2), dot3(row2, col0), dot3(row2, col1), dot3(row2, col2), ] as const; }; /** * @worklet */ const serializeToSVGMatrix = (m: Matrix3) => { "worklet"; return `matrix(${m[0]}, ${m[3 + 0]}, ${m[1]}, ${m[3 + 1]}, ${m[2]}, ${ m[3 + 2] })`; }; /** * @worklet */ export const svgMatrix = (transforms: Transforms2d) => { "worklet"; return serializeToSVGMatrix(processTransform2d(transforms)); }; /** * @worklet */ export const processTransform2d = (transforms: Transforms2d) => { "worklet"; return transforms.reduce((acc, transform) => { if (isTranslateX(transform)) { return multiply3(acc, translateXMatrix(transform.translateX)); } if (isTranslateY(transform)) { return multiply3(acc, translateYMatrix(transform.translateY)); } if (isScale(transform)) { return multiply3(acc, scaleMatrix(transform.scale)); } if (isScaleX(transform)) { return multiply3(acc, scaleXMatrix(transform.scaleX)); } if (isScaleY(transform)) { return multiply3(acc, scaleYMatrix(transform.scaleY)); } if (isSkewX(transform)) { return multiply3(acc, skewXMatrix(parseAngle(transform.skewX))); } if (isSkewY(transform)) { return multiply3(acc, skewYMatrix(parseAngle(transform.skewY))); } if (isRotate(transform)) { return multiply3(acc, rotateZMatrix(parseAngle(transform.rotate))); } if (isRotateZ(transform)) { return multiply3(acc, rotateZMatrix(parseAngle(transform.rotateZ))); } return exhaustiveCheck(transform); }, identity3); }; /** * @worklet */ const isMatrix3 = (arg: Matrix3 | Transforms2d): arg is Matrix3 => { "worklet"; return arg.length === 9 && arg[0] instanceof Array; }; // https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix // https://math.stackexchange.com/questions/296794/finding-the-transform-matrix-from-4-projected-points-with-javascript // https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ // http://jsfiddle.net/dFrHS/1/ /** * @worklet */ export const decompose2d = (arg: Matrix3 | Transforms2d) => { "worklet"; const m = isMatrix3(arg) ? arg : processTransform2d(arg); const a = m[0]; const b = m[3 + 0]; const c = m[1]; const d = m[3 + 1]; const translateX = m[2]; const translateY = m[3 + 2]; const E = (a + d) / 2; const F = (a - d) / 2; const G = (c + b) / 2; const H = (c - b) / 2; const Q = Math.sqrt(Math.pow(E, 2) + Math.pow(H, 2)); const R = Math.sqrt(Math.pow(F, 2) + Math.pow(G, 2)); const scaleX = Q + R; const scaleY = Q - R; const a1 = Math.atan2(G, F); const a2 = Math.atan2(H, E); const theta = (a2 - a1) / 2; const phi = (a2 + a1) / 2; return [ { translateX }, { translateY }, { rotateZ: -1 * theta }, { scaleX }, { scaleY }, { rotateZ: -1 * phi }, ] as const; };