deno.land / x / skia_canvas@0.5.8 / src / filter.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388export enum FilterType { Blur, Brightness, Contrast, DropShadow, Grayscale, HueRotate, Invert, Opacity, Saturate, Sepia,}
interface FilterBase { type: FilterType;}
interface BlurFilter extends FilterBase { type: FilterType.Blur; value: number;}
interface BrightnessFilter extends FilterBase { type: FilterType.Brightness; value: number;}
interface ContrastFilter extends FilterBase { type: FilterType.Contrast; value: number;}
interface DropShadowFilter extends FilterBase { type: FilterType.DropShadow; dx: number; dy: number; radius: number; color: string;}
interface GrayscaleFilter extends FilterBase { type: FilterType.Grayscale; value: number;}
interface HueRotateFilter extends FilterBase { type: FilterType.HueRotate; value: number;}
interface InvertFilter extends FilterBase { type: FilterType.Invert; value: number;}
interface OpacityFilter extends FilterBase { type: FilterType.Opacity; value: number;}
interface SaturateFilter extends FilterBase { type: FilterType.Saturate; value: number;}
interface SepiaFilter extends FilterBase { type: FilterType.Sepia; value: number;}
export type Filter = | BlurFilter | BrightnessFilter | ContrastFilter | DropShadowFilter | GrayscaleFilter | HueRotateFilter | InvertFilter | OpacityFilter | SaturateFilter | SepiaFilter;
const FILTER_NAMES = [ "blur", "brightness", "contrast", "drop-shadow", "grayscale", "hue-rotate", "invert", "opacity", "saturate", "sepia",];
export function parseFilterString(filter: string): Filter[] { const filters: Filter[] = [];
let state: "fn" | "args" = "fn"; let fn = ""; let argc = 0; // deno-lint-ignore no-explicit-any let argv: any[] = [];
let i = 0; while (i < filter.length) { let ch = filter[i]; if (state === "fn") { if (ch === "(") { if (!FILTER_NAMES.includes(fn.toLowerCase())) { throw new Error(`Invalid filter name: ${fn}`); } fn = fn.toLowerCase(); state = "args"; } else if (ch.match(/[a-zA-Z0-9\-]+/)) { fn += ch; } else if (ch.match(/\s/) && fn.length > 0) { throw new Error(`Unexpected whitespace in filter function`); } else if (!ch.match(/\s/)) { throw new Error(`Unexpected character in filter function: ${ch}`); } } else if (state === "args") { if (argc === 1 && fn !== "drop-shadow") { throw new Error(`Function ${fn} only takes one argument`); } else if (argc === 4 && fn === "drop-shadow") { throw new Error(`Function ${fn} only takes four arguments`); }
let arg = ""; while (ch !== ")" && ch !== "," && ch !== " ") { arg += ch; i++; ch = filter[i]; }
if (arg === "" && ch === " ") { i++; continue; }
// deno-lint-ignore no-inner-declarations function parsePixel() { let numPart = ""; let unitPart = ""; for (let j = 0; j < arg.length; j++) { const ch = arg[j]; if (ch === " ") continue; if ( (ch.match(/[0-9]/) || (ch === "." && !numPart.includes("."))) && !unitPart ) { numPart += ch; } else { unitPart += ch; } } const num = parseFloat(numPart); if (unitPart === "px") { return num; } else if (unitPart === "%") { return num * 16 / 100; } else if ( unitPart === "em" || unitPart === "rem" || unitPart === "pc" ) { return num * 16; } else if (unitPart === "pt") { return num * 4 / 3; } else if (unitPart === "") { if (num === 0) { return 0; } else { throw new Error(`Invalid unitless number: ${num} in ${arg}`); } } else if (unitPart === "in") { return num * 96; } else if (unitPart === "cm") { return num * 96 / 2.54; } else if (unitPart === "mm") { return num * 96 / 25.4; } else if (unitPart === "q") { return num * 96.0 / 25.4 / 4.0; } else { throw new Error(`Invalid unit: ${unitPart} in ${arg}`); } }
// deno-lint-ignore no-inner-declarations function parsePercentage() { if (!arg.match(/^[0-9\.]+%?$/)) { throw new Error(`Invalid percentage: ${arg}`); }
const num = parseFloat( arg.endsWith("%") ? arg.slice(0, arg.length - 1) : arg, ); if (num < 0 || isNaN(num)) { throw new Error(`Invalid percentage: ${arg}`); }
return num / 100; }
// deno-lint-ignore no-inner-declarations function parseAngle() { if (arg.endsWith("deg")) { const num = parseFloat(arg.slice(0, arg.length - 3)); if (isNaN(num)) { throw new Error(`Invalid angle: ${arg}`); } return num; } else if (arg.endsWith("grad")) { const num = parseFloat(arg.slice(0, arg.length - 4)); if (isNaN(num)) { throw new Error(`Invalid angle: ${arg}`); } return num * 360 / 400; } else if (arg.endsWith("rad")) { const num = parseFloat(arg.slice(0, arg.length - 3)); if (isNaN(num)) { throw new Error(`Invalid angle: ${arg}`); } return num * 180 / Math.PI; } else if (arg.endsWith("turn")) { const num = parseFloat(arg.slice(0, arg.length - 4)); if (isNaN(num)) { throw new Error(`Invalid angle: ${arg}`); } return num * 360; } else { throw new Error(`Invalid angle: ${arg}`); } }
// deno-lint-ignore no-explicit-any let v: any = undefined;
switch (fn) { case "blur": v = parsePixel(); break;
case "brightness": case "contrast": case "grayscale": case "invert": case "opacity": case "saturate": case "sepia": v = parsePercentage(); break;
case "drop-shadow": if (argc === 0) { v = parsePixel(); } else if (argc === 1) { v = parsePixel(); } else if (argc === 2) { v = parseFloat(arg); if (isNaN(v)) { throw new Error(`Invalid number: ${arg}`); } } else if (argc === 3) { // Pass color string as is, C++ csscolorparser will parse it v = arg; if (!v) { throw new Error(`Invalid color: ${arg}`); } } break;
case "hue-rotate": v = parseAngle(); break;
default: throw new Error(`Unknown filter function: ${fn}`); }
argc++; if (v !== undefined) argv.push(v); else throw new Error(`Invalid argument: ${arg}`);
if (ch === ")") { if (argc === 0) { throw new Error(`Function ${fn} requires arguments`); }
if (fn === "drop-shadow" && argc !== 4) { throw new Error(`Function ${fn} requires four arguments`); } else if (fn !== "drop-shadow" && argc !== 1) { throw new Error(`Function ${fn} requires one argument`); }
switch (fn) { case "blur": filters.push({ type: FilterType.Blur, value: argv[0], }); break;
case "brightness": filters.push({ type: FilterType.Brightness, value: argv[0], }); break;
case "contrast": filters.push({ type: FilterType.Contrast, value: argv[0], }); break;
case "drop-shadow": filters.push({ type: FilterType.DropShadow, dx: argv[0], dy: argv[1], radius: argv[2], color: argv[3], }); break;
case "grayscale": filters.push({ type: FilterType.Grayscale, value: argv[0], }); break;
case "hue-rotate": filters.push({ type: FilterType.HueRotate, value: argv[0], }); break;
case "invert": filters.push({ type: FilterType.Invert, value: argv[0], }); break;
case "opacity": filters.push({ type: FilterType.Opacity, value: argv[0], }); break;
case "saturate": filters.push({ type: FilterType.Saturate, value: argv[0], }); break;
case "sepia": filters.push({ type: FilterType.Sepia, value: argv[0], }); break;
default: throw new Error(`Unknown filter function: ${fn}`); }
fn = ""; argc = 0; argv = []; state = "fn"; } }
i++; }
if ((state === "fn" && fn !== "") || state === "args") { throw new Error(`Unexpected end of filter string`); }
return filters;}
Version Info