UNPKG

@shopify/react-native-skia

Version:

High-performance React Native Graphics using Skia

341 lines (325 loc) 12.3 kB
import type { SkColor, Color as InputColor } from "../types"; const alphaf = (c: number) => ((c >> 24) & 255) / 255; const red = (c: number) => (c >> 16) & 255; const green = (c: number) => (c >> 8) & 255; const blue = (c: number) => c & 255; // From https://raw.githubusercontent.com/deanm/css-color-parser-js/master/csscolorparser.js const CSSColorTable = { transparent: Float32Array.of(0, 0, 0, 0), aliceblue: Float32Array.of(240, 248, 255, 1), antiquewhite: Float32Array.of(250, 235, 215, 1), aqua: Float32Array.of(0, 255, 255, 1), aquamarine: Float32Array.of(127, 255, 212, 1), azure: Float32Array.of(240, 255, 255, 1), beige: Float32Array.of(245, 245, 220, 1), bisque: Float32Array.of(255, 228, 196, 1), black: Float32Array.of(0, 0, 0, 1), blanchedalmond: Float32Array.of(255, 235, 205, 1), blue: Float32Array.of(0, 0, 255, 1), blueviolet: Float32Array.of(138, 43, 226, 1), brown: Float32Array.of(165, 42, 42, 1), burlywood: Float32Array.of(222, 184, 135, 1), cadetblue: Float32Array.of(95, 158, 160, 1), chartreuse: Float32Array.of(127, 255, 0, 1), chocolate: Float32Array.of(210, 105, 30, 1), coral: Float32Array.of(255, 127, 80, 1), cornflowerblue: Float32Array.of(100, 149, 237, 1), cornsilk: Float32Array.of(255, 248, 220, 1), crimson: Float32Array.of(220, 20, 60, 1), cyan: Float32Array.of(0, 255, 255, 1), darkblue: Float32Array.of(0, 0, 139, 1), darkcyan: Float32Array.of(0, 139, 139, 1), darkgoldenrod: Float32Array.of(184, 134, 11, 1), darkgray: Float32Array.of(169, 169, 169, 1), darkgreen: Float32Array.of(0, 100, 0, 1), darkgrey: Float32Array.of(169, 169, 169, 1), darkkhaki: Float32Array.of(189, 183, 107, 1), darkmagenta: Float32Array.of(139, 0, 139, 1), darkolivegreen: Float32Array.of(85, 107, 47, 1), darkorange: Float32Array.of(255, 140, 0, 1), darkorchid: Float32Array.of(153, 50, 204, 1), darkred: Float32Array.of(139, 0, 0, 1), darksalmon: Float32Array.of(233, 150, 122, 1), darkseagreen: Float32Array.of(143, 188, 143, 1), darkslateblue: Float32Array.of(72, 61, 139, 1), darkslategray: Float32Array.of(47, 79, 79, 1), darkslategrey: Float32Array.of(47, 79, 79, 1), darkturquoise: Float32Array.of(0, 206, 209, 1), darkviolet: Float32Array.of(148, 0, 211, 1), deeppink: Float32Array.of(255, 20, 147, 1), deepskyblue: Float32Array.of(0, 191, 255, 1), dimgray: Float32Array.of(105, 105, 105, 1), dimgrey: Float32Array.of(105, 105, 105, 1), dodgerblue: Float32Array.of(30, 144, 255, 1), firebrick: Float32Array.of(178, 34, 34, 1), floralwhite: Float32Array.of(255, 250, 240, 1), forestgreen: Float32Array.of(34, 139, 34, 1), fuchsia: Float32Array.of(255, 0, 255, 1), gainsboro: Float32Array.of(220, 220, 220, 1), ghostwhite: Float32Array.of(248, 248, 255, 1), gold: Float32Array.of(255, 215, 0, 1), goldenrod: Float32Array.of(218, 165, 32, 1), gray: Float32Array.of(128, 128, 128, 1), green: Float32Array.of(0, 128, 0, 1), greenyellow: Float32Array.of(173, 255, 47, 1), grey: Float32Array.of(128, 128, 128, 1), honeydew: Float32Array.of(240, 255, 240, 1), hotpink: Float32Array.of(255, 105, 180, 1), indianred: Float32Array.of(205, 92, 92, 1), indigo: Float32Array.of(75, 0, 130, 1), ivory: Float32Array.of(255, 255, 240, 1), khaki: Float32Array.of(240, 230, 140, 1), lavender: Float32Array.of(230, 230, 250, 1), lavenderblush: Float32Array.of(255, 240, 245, 1), lawngreen: Float32Array.of(124, 252, 0, 1), lemonchiffon: Float32Array.of(255, 250, 205, 1), lightblue: Float32Array.of(173, 216, 230, 1), lightcoral: Float32Array.of(240, 128, 128, 1), lightcyan: Float32Array.of(224, 255, 255, 1), lightgoldenrodyellow: Float32Array.of(250, 250, 210, 1), lightgray: Float32Array.of(211, 211, 211, 1), lightgreen: Float32Array.of(144, 238, 144, 1), lightgrey: Float32Array.of(211, 211, 211, 1), lightpink: Float32Array.of(255, 182, 193, 1), lightsalmon: Float32Array.of(255, 160, 122, 1), lightseagreen: Float32Array.of(32, 178, 170, 1), lightskyblue: Float32Array.of(135, 206, 250, 1), lightslategray: Float32Array.of(119, 136, 153, 1), lightslategrey: Float32Array.of(119, 136, 153, 1), lightsteelblue: Float32Array.of(176, 196, 222, 1), lightyellow: Float32Array.of(255, 255, 224, 1), lime: Float32Array.of(0, 255, 0, 1), limegreen: Float32Array.of(50, 205, 50, 1), linen: Float32Array.of(250, 240, 230, 1), magenta: Float32Array.of(255, 0, 255, 1), maroon: Float32Array.of(128, 0, 0, 1), mediumaquamarine: Float32Array.of(102, 205, 170, 1), mediumblue: Float32Array.of(0, 0, 205, 1), mediumorchid: Float32Array.of(186, 85, 211, 1), mediumpurple: Float32Array.of(147, 112, 219, 1), mediumseagreen: Float32Array.of(60, 179, 113, 1), mediumslateblue: Float32Array.of(123, 104, 238, 1), mediumspringgreen: Float32Array.of(0, 250, 154, 1), mediumturquoise: Float32Array.of(72, 209, 204, 1), mediumvioletred: Float32Array.of(199, 21, 133, 1), midnightblue: Float32Array.of(25, 25, 112, 1), mintcream: Float32Array.of(245, 255, 250, 1), mistyrose: Float32Array.of(255, 228, 225, 1), moccasin: Float32Array.of(255, 228, 181, 1), navajowhite: Float32Array.of(255, 222, 173, 1), navy: Float32Array.of(0, 0, 128, 1), oldlace: Float32Array.of(253, 245, 230, 1), olive: Float32Array.of(128, 128, 0, 1), olivedrab: Float32Array.of(107, 142, 35, 1), orange: Float32Array.of(255, 165, 0, 1), orangered: Float32Array.of(255, 69, 0, 1), orchid: Float32Array.of(218, 112, 214, 1), palegoldenrod: Float32Array.of(238, 232, 170, 1), palegreen: Float32Array.of(152, 251, 152, 1), paleturquoise: Float32Array.of(175, 238, 238, 1), palevioletred: Float32Array.of(219, 112, 147, 1), papayawhip: Float32Array.of(255, 239, 213, 1), peachpuff: Float32Array.of(255, 218, 185, 1), peru: Float32Array.of(205, 133, 63, 1), pink: Float32Array.of(255, 192, 203, 1), plum: Float32Array.of(221, 160, 221, 1), powderblue: Float32Array.of(176, 224, 230, 1), purple: Float32Array.of(128, 0, 128, 1), rebeccapurple: Float32Array.of(102, 51, 153, 1), red: Float32Array.of(255, 0, 0, 1), rosybrown: Float32Array.of(188, 143, 143, 1), royalblue: Float32Array.of(65, 105, 225, 1), saddlebrown: Float32Array.of(139, 69, 19, 1), salmon: Float32Array.of(250, 128, 114, 1), sandybrown: Float32Array.of(244, 164, 96, 1), seagreen: Float32Array.of(46, 139, 87, 1), seashell: Float32Array.of(255, 245, 238, 1), sienna: Float32Array.of(160, 82, 45, 1), silver: Float32Array.of(192, 192, 192, 1), skyblue: Float32Array.of(135, 206, 235, 1), slateblue: Float32Array.of(106, 90, 205, 1), slategray: Float32Array.of(112, 128, 144, 1), slategrey: Float32Array.of(112, 128, 144, 1), snow: Float32Array.of(255, 250, 250, 1), springgreen: Float32Array.of(0, 255, 127, 1), steelblue: Float32Array.of(70, 130, 180, 1), tan: Float32Array.of(210, 180, 140, 1), teal: Float32Array.of(0, 128, 128, 1), thistle: Float32Array.of(216, 191, 216, 1), tomato: Float32Array.of(255, 99, 71, 1), turquoise: Float32Array.of(64, 224, 208, 1), violet: Float32Array.of(238, 130, 238, 1), wheat: Float32Array.of(245, 222, 179, 1), white: Float32Array.of(255, 255, 255, 1), whitesmoke: Float32Array.of(245, 245, 245, 1), yellow: Float32Array.of(255, 255, 0, 1), yellowgreen: Float32Array.of(154, 205, 50, 1), }; const clampCSSByte = (j: number) => { // Clamp to integer 0 .. 255. const i = Math.round(j); // Seems to be what Chrome does (vs truncation). // eslint-disable-next-line no-nested-ternary return i < 0 ? 0 : i > 255 ? 255 : i; }; const clampCSSFloat = (f: number) => { // eslint-disable-next-line no-nested-ternary return f < 0 ? 0 : f > 1 ? 1 : f; }; const parseCSSInt = (str: string) => { // int or percentage. if (str[str.length - 1] === "%") { return clampCSSByte((parseFloat(str) / 100) * 255); } // eslint-disable-next-line radix return clampCSSByte(parseInt(str)); }; const parseCSSFloat = (str: string | undefined) => { if (str === undefined) { return 1; } // float or percentage. if (str[str.length - 1] === "%") { return clampCSSFloat(parseFloat(str) / 100); } return clampCSSFloat(parseFloat(str)); }; const CSSHueToRGB = (m1: number, m2: number, h: number) => { if (h < 0) { h += 1; } else if (h > 1) { h -= 1; } if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; } if (h * 2 < 1) { return m2; } if (h * 3 < 2) { return m1 + (m2 - m1) * (2 / 3 - h) * 6; } return m1; }; const parseCSSColor = (cssStr: string) => { // Remove all whitespace, not compliant, but should just be more accepting. var str = cssStr.replace(/ /g, "").toLowerCase(); // Color keywords (and transparent) lookup. if (str in CSSColorTable) { const cl = CSSColorTable[str as keyof typeof CSSColorTable]; if (cl) { return Float32Array.of(...cl); } return null; } // dup. // #abc and #abc123 syntax. if (str[0] === "#") { if (str.length === 4) { var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. if (!(iv >= 0 && iv <= 0xfff)) { return null; } // Covers NaN. return [ ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8), (iv & 0xf0) | ((iv & 0xf0) >> 4), (iv & 0xf) | ((iv & 0xf) << 4), 1, ]; } else if (str.length === 7) { var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. if (!(iv >= 0 && iv <= 0xffffff)) { return null; } // Covers NaN. return [(iv & 0xff0000) >> 16, (iv & 0xff00) >> 8, iv & 0xff, 1]; } else if (str.length === 9) { var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. if (!(iv >= 0 && iv <= 0xffffffff)) { return null; // Covers NaN. } return [ ((iv & 0xff000000) >> 24) & 0xff, (iv & 0xff0000) >> 16, (iv & 0xff00) >> 8, (iv & 0xff) / 255, ]; } return null; } var op = str.indexOf("("), ep = str.indexOf(")"); if (op !== -1 && ep + 1 === str.length) { var fname = str.substr(0, op); var params = str.substr(op + 1, ep - (op + 1)).split(","); var alpha = 1; // To allow case fallthrough. switch (fname) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-expect-error case "rgba": if (params.length !== 4) { return null; } alpha = parseCSSFloat(params.pop()); // Fall through. case "rgb": if (params.length !== 3) { return null; } return [ parseCSSInt(params[0]), parseCSSInt(params[1]), parseCSSInt(params[2]), alpha, ]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-expect-error case "hsla": if (params.length !== 4) { return null; } alpha = parseCSSFloat(params.pop()); // Fall through. case "hsl": if (params.length !== 3) { return null; } var h = (((parseFloat(params[0]) % 360) + 360) % 360) / 360; // 0 .. 1 // NOTE(deanm): According to the CSS spec s/l should only be // percentages, but we don't bother and let float or percentage. var s = parseCSSFloat(params[1]); var l = parseCSSFloat(params[2]); var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; var m1 = l * 2 - m2; return [ clampCSSByte(CSSHueToRGB(m1, m2, h + 1 / 3) * 255), clampCSSByte(CSSHueToRGB(m1, m2, h) * 255), clampCSSByte(CSSHueToRGB(m1, m2, h - 1 / 3) * 255), alpha, ]; default: return null; } } return null; }; export const Color = (color: InputColor): SkColor => { if (color instanceof Float32Array) { return color; } else if (Array.isArray(color)) { return new Float32Array(color); } else if (typeof color === "string") { const r = parseCSSColor(color); const rgba = r === null ? CSSColorTable.black : r; return Float32Array.of( rgba[0] / 255, rgba[1] / 255, rgba[2] / 255, rgba[3] ); } else { return Float32Array.of( red(color) / 255, green(color) / 255, blue(color) / 255, alphaf(color) ); } };