From 94e966d1477c244fab3890f5cb1ef6e28108be92 Mon Sep 17 00:00:00 2001
From: ysk2014 <1181102772@qq.com>
Date: Mon, 31 Aug 2020 16:37:19 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9css=E6=BB=A4?=
=?UTF-8?q?=E9=95=9C=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
examples/demo4.html | 79 ++
src/css/index.ts | 3 +
src/css/property-descriptors/filter.ts | 68 ++
.../property-descriptors/text-stroke-color.ts | 4 +-
.../property-descriptors/text-stroke-width.ts | 4 +-
src/css/syntax/parser.ts | 1 +
src/css/types/color.ts | 153 +++
src/css/types/functions/stackBlur.ts | 1057 +++++++++++++++++
src/render/canvas/canvas-renderer.ts | 64 +-
9 files changed, 1426 insertions(+), 7 deletions(-)
create mode 100644 examples/demo4.html
create mode 100644 src/css/property-descriptors/filter.ts
create mode 100644 src/css/types/functions/stackBlur.ts
diff --git a/examples/demo4.html b/examples/demo4.html
new file mode 100644
index 0000000..ed242e2
--- /dev/null
+++ b/examples/demo4.html
@@ -0,0 +1,79 @@
+
+
+
+
+ Nested transform tests
+
+
+
+
+
+
+ 
+ 
+
+
+
+
+
\ No newline at end of file
diff --git a/src/css/index.ts b/src/css/index.ts
index 44b9714..7604a1b 100644
--- a/src/css/index.ts
+++ b/src/css/index.ts
@@ -75,6 +75,7 @@ import {counterIncrement} from './property-descriptors/counter-increment';
import {counterReset} from './property-descriptors/counter-reset';
import {quotes} from './property-descriptors/quotes';
import {boxShadow} from './property-descriptors/box-shadow';
+import {filter} from './property-descriptors/filter';
export class CSSParsedDeclaration {
backgroundClip: ReturnType;
@@ -103,6 +104,7 @@ export class CSSParsedDeclaration {
boxShadow: ReturnType;
color: Color;
display: ReturnType;
+ filter: ReturnType;
float: ReturnType;
fontFamily: ReturnType;
fontSize: LengthPercentage;
@@ -168,6 +170,7 @@ export class CSSParsedDeclaration {
this.boxShadow = parse(boxShadow, declaration.boxShadow);
this.color = parse(color, declaration.color);
this.display = parse(display, declaration.display);
+ this.filter = parse(filter, declaration.filter);
this.float = parse(float, declaration.cssFloat);
this.fontFamily = parse(fontFamily, declaration.fontFamily);
this.fontSize = parse(fontSize, declaration.fontSize);
diff --git a/src/css/property-descriptors/filter.ts b/src/css/property-descriptors/filter.ts
new file mode 100644
index 0000000..08eb312
--- /dev/null
+++ b/src/css/property-descriptors/filter.ts
@@ -0,0 +1,68 @@
+import {IPropertyListDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
+import {CSSValue, isIdentWithValue, CSSFunction, isCSSFunction} from '../syntax/parser';
+import {isLength, Length} from '../types/length';
+
+export interface Filter {
+ contrast: Length | null;
+ 'hue-rotate': Length | null;
+ grayscale: Length | null;
+ brightness: Length | null;
+ blur: Length | null;
+ invert: Length | null;
+ saturate: Length | null;
+ sepia: Length | null;
+}
+
+export const filter: IPropertyListDescriptor = {
+ name: 'filter',
+ initialValue: 'none',
+ prefix: true,
+ type: PropertyDescriptorParsingType.LIST,
+ parse: (tokens: CSSValue[]) => {
+ if (tokens.length === 1 && isIdentWithValue(tokens[0], 'none')) {
+ return null;
+ }
+
+ const filter: Filter = {
+ contrast: null,
+ 'hue-rotate': null,
+ grayscale: null,
+ brightness: null,
+ blur: null,
+ invert: null,
+ saturate: null,
+ sepia: null
+ };
+
+ let hasFilter: boolean = false;
+
+ tokens.filter(isCSSFunction).forEach((token: CSSFunction) => {
+ switch (token.name) {
+ case 'contrast':
+ case 'hue-rotate':
+ case 'grayscale':
+ case 'brightness':
+ case 'blur':
+ case 'invert':
+ case 'saturate':
+ case 'sepia':
+ for (let index = 0; index < token.values.length; index++) {
+ const value: CSSValue = token.values[index];
+ if (isLength(value)) {
+ hasFilter = true;
+ filter[token.name] = value;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ });
+
+ if (hasFilter) {
+ return filter;
+ } else {
+ return null;
+ }
+ }
+};
diff --git a/src/css/property-descriptors/text-stroke-color.ts b/src/css/property-descriptors/text-stroke-color.ts
index 2a5d410..ce4e0a9 100644
--- a/src/css/property-descriptors/text-stroke-color.ts
+++ b/src/css/property-descriptors/text-stroke-color.ts
@@ -1,9 +1,9 @@
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
export const textStrokeColor: IPropertyTypeValueDescriptor = {
- name: `-webkit-text-stroke-color`,
+ name: `text-stroke-color`,
initialValue: 'transparent',
- prefix: false,
+ prefix: true,
type: PropertyDescriptorParsingType.TYPE_VALUE,
format: 'color'
};
diff --git a/src/css/property-descriptors/text-stroke-width.ts b/src/css/property-descriptors/text-stroke-width.ts
index 2130296..a4861ec 100644
--- a/src/css/property-descriptors/text-stroke-width.ts
+++ b/src/css/property-descriptors/text-stroke-width.ts
@@ -1,8 +1,8 @@
import {IPropertyTypeValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
export const textStrokeWidth: IPropertyTypeValueDescriptor = {
- name: '-webkit-text-stroke-width',
+ name: 'text-stroke-width',
initialValue: '0',
type: PropertyDescriptorParsingType.TYPE_VALUE,
- prefix: false,
+ prefix: true,
format: 'length-percentage'
};
diff --git a/src/css/syntax/parser.ts b/src/css/syntax/parser.ts
index 83b8a6c..0acc3bc 100644
--- a/src/css/syntax/parser.ts
+++ b/src/css/syntax/parser.ts
@@ -141,6 +141,7 @@ export class Parser {
}
}
+export const isCSSFunction = (token: CSSValue): token is CSSFunction => token.type === TokenType.FUNCTION;
export const isDimensionToken = (token: CSSValue): token is DimensionToken => token.type === TokenType.DIMENSION_TOKEN;
export const isNumberToken = (token: CSSValue): token is NumberValueToken => token.type === TokenType.NUMBER_TOKEN;
export const isIdentToken = (token: CSSValue): token is StringValueToken => token.type === TokenType.IDENT_TOKEN;
diff --git a/src/css/types/color.ts b/src/css/types/color.ts
index ebc17c1..1115973 100644
--- a/src/css/types/color.ts
+++ b/src/css/types/color.ts
@@ -302,3 +302,156 @@ export const COLORS: {[key: string]: Color} = {
YELLOW: 0xffff00ff,
YELLOWGREEN: 0x9acd32ff
};
+
+export interface RGBColor {
+ r: number;
+ g: number;
+ b: number;
+}
+
+export const contrastRGB = (rgb: RGBColor, value: number): RGBColor => {
+ if (value < 0) value = 0;
+ else if (value > 1) value = 1;
+ return {
+ r: Math.max(0, Math.min(255, value * (rgb.r - 128) + 128)),
+ g: Math.max(0, Math.min(255, value * (rgb.g - 128) + 128)),
+ b: Math.max(0, Math.min(255, value * (rgb.b - 128) + 128))
+ };
+};
+
+export const grayscaleRGB = (rgb: RGBColor, value: number, mode?: string | null) => {
+ var gray = 0;
+ //different grayscale algorithms
+ switch (mode) {
+ case 'average':
+ gray = (rgb.r + rgb.g + rgb.b) / 3;
+ break;
+ case 'luma:BT601':
+ gray = rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
+ break;
+ case 'desaturation':
+ gray = (Math.max(rgb.r, rgb.g, rgb.b) + Math.max(rgb.r, rgb.g, rgb.b)) / 2;
+ break;
+ case 'decompsition:max':
+ gray = Math.max(rgb.r, rgb.g, rgb.b);
+ break;
+ case 'decompsition:min':
+ gray = Math.min(rgb.r, rgb.g, rgb.b);
+ break;
+ case 'luma:BT709':
+ default:
+ gray = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722;
+ break;
+ }
+ rgb.r = value * (gray - rgb.r) + rgb.r;
+ rgb.g = value * (gray - rgb.g) + rgb.g;
+ rgb.b = value * (gray - rgb.b) + rgb.b;
+ return rgb;
+};
+
+export const brightnessRGB = (rgb: RGBColor, value: number): RGBColor => {
+ if (value < 0) value = 0;
+ return {
+ r: Math.max(0, Math.min(255, rgb.r * value)),
+ g: Math.max(0, Math.min(255, rgb.g * value)),
+ b: Math.max(0, Math.min(255, rgb.b * value))
+ };
+};
+
+export const invertRGB = (rgb: RGBColor, value: number): RGBColor => {
+ return {
+ r: value * (255 - 2 * rgb.r) + rgb.r,
+ g: value * (255 - 2 * rgb.g) + rgb.g,
+ b: value * (255 - 2 * rgb.b) + rgb.b
+ };
+};
+
+export const sepiaRGB = (rgb: RGBColor, value: number): RGBColor => {
+ if (value < 0) value = 0;
+ else if (value > 1) value = 1;
+ return {
+ r: value * Math.min(255, rgb.r * 0.393 + rgb.g * 0.769 + rgb.b * 0.189 - rgb.r) + rgb.r,
+ g: value * Math.min(255, rgb.r * 0.349 + rgb.g * 0.686 + rgb.b * 0.168 - rgb.g) + rgb.g,
+ b: value * Math.min(255, rgb.r * 0.272 + rgb.g * 0.534 + rgb.b * 0.131 - rgb.b) + rgb.b
+ };
+};
+
+export const hueRotateRGB = (rgb: RGBColor, value: number): RGBColor => {
+ while (value < 0) value += 360;
+ while (value > 360) value -= 360;
+ rgb2hsl(rgb);
+ rgb.r += value;
+ if (rgb.r < 0) rgb.r += 360;
+ if (rgb.r > 359) rgb.r -= 360;
+ hsl2rgb(rgb);
+ return rgb;
+};
+
+export const saturateRGB = (rgb: RGBColor, value: number): RGBColor => {
+ if (value < 0) value = 0;
+ rgb2hsl(rgb);
+ rgb.g *= value;
+ if (rgb.g > 100) rgb.g = 100;
+ hsl2rgb(rgb);
+ return rgb;
+};
+
+function rgb2hsl(rgb: RGBColor) {
+ rgb.r = Math.max(0, Math.min(255, rgb.r)) / 255;
+ rgb.g = Math.max(0, Math.min(255, rgb.g)) / 255;
+ rgb.b = Math.max(0, Math.min(255, rgb.b)) / 255;
+ let h, l;
+ let M = Math.max(rgb.r, rgb.g, rgb.b);
+ let m = Math.min(rgb.r, rgb.g, rgb.b);
+ let d = M - m;
+ if (d == 0) h = 0;
+ else if (M == rgb.r) h = ((rgb.g - rgb.b) / d) % 6;
+ else if (M == rgb.g) h = (rgb.b - rgb.r) / d + 2;
+ else h = (rgb.r - rgb.g) / d + 4;
+ h = Math.round(h * 60);
+ if (h < 0) h += 360;
+ rgb.r = h;
+ l = (M + m) / 2;
+ if (d == 0) rgb.g = 0;
+ else rgb.g = (d / (1 - Math.abs(2 * l - 1))) * 100;
+ rgb.b = l * 100;
+}
+
+function hsl2rgb(rgb: RGBColor) {
+ rgb.r = Math.max(0, Math.min(359, rgb.r));
+ rgb.g = Math.max(0, Math.min(100, rgb.g)) / 100;
+ rgb.b = Math.max(0, Math.min(100, rgb.b)) / 100;
+ let C = (1 - Math.abs(2 * rgb.b - 1)) * rgb.g;
+ let h = rgb.r / 60;
+ let X = C * (1 - Math.abs((h % 2) - 1));
+ let l = rgb.b;
+ rgb.r = 0;
+ rgb.g = 0;
+ rgb.b = 0;
+ if (h >= 0 && h < 1) {
+ rgb.r = C;
+ rgb.g = X;
+ } else if (h >= 1 && h < 2) {
+ rgb.r = X;
+ rgb.g = C;
+ } else if (h >= 2 && h < 3) {
+ rgb.g = C;
+ rgb.b = X;
+ } else if (h >= 3 && h < 4) {
+ rgb.g = X;
+ rgb.b = C;
+ } else if (h >= 4 && h < 5) {
+ rgb.r = X;
+ rgb.b = C;
+ } else {
+ rgb.r = C;
+ rgb.b = X;
+ }
+ let m = l - C / 2;
+ rgb.r += m;
+ rgb.g += m;
+ rgb.b += m;
+ rgb.r = Math.round(rgb.r * 255.0);
+ rgb.g = Math.round(rgb.g * 255.0);
+ rgb.b = Math.round(rgb.b * 255.0);
+}
diff --git a/src/css/types/functions/stackBlur.ts b/src/css/types/functions/stackBlur.ts
new file mode 100644
index 0000000..20724dd
--- /dev/null
+++ b/src/css/types/functions/stackBlur.ts
@@ -0,0 +1,1057 @@
+/* eslint-disable no-bitwise -- used for calculations */
+/* eslint-disable unicorn/prefer-query-selector -- aiming at
+ backward-compatibility */
+/**
+ * StackBlur - a fast almost Gaussian Blur For Canvas
+ *
+ * In case you find this class useful - especially in commercial projects -
+ * I am not totally unhappy for a small donation to my PayPal account
+ * mario@quasimondo.de
+ *
+ * Or support me on flattr:
+ * {@link https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript}.
+ *
+ * @module StackBlur
+ * @author Mario Klingemann
+ * Contact: mario@quasimondo.com
+ * Website: {@link http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html}
+ * Twitter: @quasimondo
+ *
+ * @copyright (c) 2010 Mario Klingemann
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+const mulTable = [
+ 512,
+ 512,
+ 456,
+ 512,
+ 328,
+ 456,
+ 335,
+ 512,
+ 405,
+ 328,
+ 271,
+ 456,
+ 388,
+ 335,
+ 292,
+ 512,
+ 454,
+ 405,
+ 364,
+ 328,
+ 298,
+ 271,
+ 496,
+ 456,
+ 420,
+ 388,
+ 360,
+ 335,
+ 312,
+ 292,
+ 273,
+ 512,
+ 482,
+ 454,
+ 428,
+ 405,
+ 383,
+ 364,
+ 345,
+ 328,
+ 312,
+ 298,
+ 284,
+ 271,
+ 259,
+ 496,
+ 475,
+ 456,
+ 437,
+ 420,
+ 404,
+ 388,
+ 374,
+ 360,
+ 347,
+ 335,
+ 323,
+ 312,
+ 302,
+ 292,
+ 282,
+ 273,
+ 265,
+ 512,
+ 497,
+ 482,
+ 468,
+ 454,
+ 441,
+ 428,
+ 417,
+ 405,
+ 394,
+ 383,
+ 373,
+ 364,
+ 354,
+ 345,
+ 337,
+ 328,
+ 320,
+ 312,
+ 305,
+ 298,
+ 291,
+ 284,
+ 278,
+ 271,
+ 265,
+ 259,
+ 507,
+ 496,
+ 485,
+ 475,
+ 465,
+ 456,
+ 446,
+ 437,
+ 428,
+ 420,
+ 412,
+ 404,
+ 396,
+ 388,
+ 381,
+ 374,
+ 367,
+ 360,
+ 354,
+ 347,
+ 341,
+ 335,
+ 329,
+ 323,
+ 318,
+ 312,
+ 307,
+ 302,
+ 297,
+ 292,
+ 287,
+ 282,
+ 278,
+ 273,
+ 269,
+ 265,
+ 261,
+ 512,
+ 505,
+ 497,
+ 489,
+ 482,
+ 475,
+ 468,
+ 461,
+ 454,
+ 447,
+ 441,
+ 435,
+ 428,
+ 422,
+ 417,
+ 411,
+ 405,
+ 399,
+ 394,
+ 389,
+ 383,
+ 378,
+ 373,
+ 368,
+ 364,
+ 359,
+ 354,
+ 350,
+ 345,
+ 341,
+ 337,
+ 332,
+ 328,
+ 324,
+ 320,
+ 316,
+ 312,
+ 309,
+ 305,
+ 301,
+ 298,
+ 294,
+ 291,
+ 287,
+ 284,
+ 281,
+ 278,
+ 274,
+ 271,
+ 268,
+ 265,
+ 262,
+ 259,
+ 257,
+ 507,
+ 501,
+ 496,
+ 491,
+ 485,
+ 480,
+ 475,
+ 470,
+ 465,
+ 460,
+ 456,
+ 451,
+ 446,
+ 442,
+ 437,
+ 433,
+ 428,
+ 424,
+ 420,
+ 416,
+ 412,
+ 408,
+ 404,
+ 400,
+ 396,
+ 392,
+ 388,
+ 385,
+ 381,
+ 377,
+ 374,
+ 370,
+ 367,
+ 363,
+ 360,
+ 357,
+ 354,
+ 350,
+ 347,
+ 344,
+ 341,
+ 338,
+ 335,
+ 332,
+ 329,
+ 326,
+ 323,
+ 320,
+ 318,
+ 315,
+ 312,
+ 310,
+ 307,
+ 304,
+ 302,
+ 299,
+ 297,
+ 294,
+ 292,
+ 289,
+ 287,
+ 285,
+ 282,
+ 280,
+ 278,
+ 275,
+ 273,
+ 271,
+ 269,
+ 267,
+ 265,
+ 263,
+ 261,
+ 259
+];
+
+const shgTable = [
+ 9,
+ 11,
+ 12,
+ 13,
+ 13,
+ 14,
+ 14,
+ 15,
+ 15,
+ 15,
+ 15,
+ 16,
+ 16,
+ 16,
+ 16,
+ 17,
+ 17,
+ 17,
+ 17,
+ 17,
+ 17,
+ 17,
+ 18,
+ 18,
+ 18,
+ 18,
+ 18,
+ 18,
+ 18,
+ 18,
+ 18,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 19,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 20,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 21,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 22,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 23,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24,
+ 24
+];
+
+/**
+ * @param {ImageData} imageData
+ * @param {Integer} width
+ * @param {Integer} height
+ * @param {Float} radius
+ * @returns {ImageData}
+ */
+function processImageDataRGBA(imageData: ImageData, width: number, height: number, radius: number) {
+ const pixels = imageData.data;
+
+ const div = 2 * radius + 1;
+ // const w4 = width << 2;
+ const widthMinus1 = width - 1;
+ const heightMinus1 = height - 1;
+ const radiusPlus1 = radius + 1;
+ const sumFactor = (radiusPlus1 * (radiusPlus1 + 1)) / 2;
+
+ const stackStart: BlurStack = new BlurStack();
+ let stack = stackStart;
+ let stackEnd;
+ for (let i = 1; i < div; i++) {
+ stack = stack.next = new BlurStack();
+ if (i === radiusPlus1) {
+ stackEnd = stack;
+ }
+ }
+ stack.next = stackStart;
+
+ let stackIn: BlurStack,
+ stackOut = null,
+ yw = 0,
+ yi = 0;
+
+ const mulSum = mulTable[radius];
+ const shgSum = shgTable[radius];
+
+ for (let y = 0; y < height; y++) {
+ stack = stackStart;
+
+ const pr = pixels[yi],
+ pg = pixels[yi + 1],
+ pb = pixels[yi + 2],
+ pa = pixels[yi + 3];
+
+ for (let i = 0; i < radiusPlus1; i++) {
+ stack.r = pr;
+ stack.g = pg;
+ stack.b = pb;
+ stack.a = pa;
+ if (stack.next) stack = stack.next;
+ }
+
+ let rInSum = 0,
+ gInSum = 0,
+ bInSum = 0,
+ aInSum = 0,
+ rOutSum = radiusPlus1 * pr,
+ gOutSum = radiusPlus1 * pg,
+ bOutSum = radiusPlus1 * pb,
+ aOutSum = radiusPlus1 * pa,
+ rSum = sumFactor * pr,
+ gSum = sumFactor * pg,
+ bSum = sumFactor * pb,
+ aSum = sumFactor * pa;
+
+ for (let i = 1; i < radiusPlus1; i++) {
+ const p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
+
+ const r = pixels[p],
+ g = pixels[p + 1],
+ b = pixels[p + 2],
+ a = pixels[p + 3];
+
+ const rbs = radiusPlus1 - i;
+ rSum += (stack.r = r) * rbs;
+ gSum += (stack.g = g) * rbs;
+ bSum += (stack.b = b) * rbs;
+ aSum += (stack.a = a) * rbs;
+
+ rInSum += r;
+ gInSum += g;
+ bInSum += b;
+ aInSum += a;
+
+ if (stack.next) stack = stack.next;
+ }
+
+ stackIn = stackStart;
+ stackOut = stackEnd;
+ for (let x = 0; x < width; x++) {
+ const paInitial = (aSum * mulSum) >> shgSum;
+ pixels[yi + 3] = paInitial;
+ if (paInitial !== 0) {
+ const a = 255 / paInitial;
+ pixels[yi] = ((rSum * mulSum) >> shgSum) * a;
+ pixels[yi + 1] = ((gSum * mulSum) >> shgSum) * a;
+ pixels[yi + 2] = ((bSum * mulSum) >> shgSum) * a;
+ } else {
+ pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;
+ }
+
+ rSum -= rOutSum;
+ gSum -= gOutSum;
+ bSum -= bOutSum;
+ aSum -= aOutSum;
+
+ rOutSum -= stackIn.r;
+ gOutSum -= stackIn.g;
+ bOutSum -= stackIn.b;
+ aOutSum -= stackIn.a;
+
+ let p = x + radius + 1;
+ p = (yw + (p < widthMinus1 ? p : widthMinus1)) << 2;
+
+ rInSum += stackIn.r = pixels[p];
+ gInSum += stackIn.g = pixels[p + 1];
+ bInSum += stackIn.b = pixels[p + 2];
+ aInSum += stackIn.a = pixels[p + 3];
+
+ rSum += rInSum;
+ gSum += gInSum;
+ bSum += bInSum;
+ aSum += aInSum;
+
+ if (stackIn.next) stackIn = stackIn.next;
+
+ if (stackOut) {
+ const {r, g, b, a} = stackOut;
+
+ rOutSum += r;
+ gOutSum += g;
+ bOutSum += b;
+ aOutSum += a;
+
+ rInSum -= r;
+ gInSum -= g;
+ bInSum -= b;
+ aInSum -= a;
+
+ stackOut = stackOut.next;
+ }
+
+ yi += 4;
+ }
+ yw += width;
+ }
+
+ for (let x = 0; x < width; x++) {
+ yi = x << 2;
+
+ let pr = pixels[yi],
+ pg = pixels[yi + 1],
+ pb = pixels[yi + 2],
+ pa = pixels[yi + 3],
+ rOutSum = radiusPlus1 * pr,
+ gOutSum = radiusPlus1 * pg,
+ bOutSum = radiusPlus1 * pb,
+ aOutSum = radiusPlus1 * pa,
+ rSum = sumFactor * pr,
+ gSum = sumFactor * pg,
+ bSum = sumFactor * pb,
+ aSum = sumFactor * pa;
+
+ stack = stackStart;
+
+ for (let i = 0; i < radiusPlus1; i++) {
+ stack.r = pr;
+ stack.g = pg;
+ stack.b = pb;
+ stack.a = pa;
+ if (stack.next) stack = stack.next;
+ }
+
+ let yp = width;
+
+ let gInSum = 0,
+ bInSum = 0,
+ aInSum = 0,
+ rInSum = 0;
+ for (let i = 1; i <= radius; i++) {
+ yi = (yp + x) << 2;
+
+ const rbs = radiusPlus1 - i;
+ rSum += (stack.r = pr = pixels[yi]) * rbs;
+ gSum += (stack.g = pg = pixels[yi + 1]) * rbs;
+ bSum += (stack.b = pb = pixels[yi + 2]) * rbs;
+ aSum += (stack.a = pa = pixels[yi + 3]) * rbs;
+
+ rInSum += pr;
+ gInSum += pg;
+ bInSum += pb;
+ aInSum += pa;
+
+ if (stack.next) stack = stack.next;
+
+ if (i < heightMinus1) {
+ yp += width;
+ }
+ }
+
+ yi = x;
+ stackIn = stackStart;
+ stackOut = stackEnd;
+ for (let y = 0; y < height; y++) {
+ let p = yi << 2;
+ pixels[p + 3] = pa = (aSum * mulSum) >> shgSum;
+ if (pa > 0) {
+ pa = 255 / pa;
+ pixels[p] = ((rSum * mulSum) >> shgSum) * pa;
+ pixels[p + 1] = ((gSum * mulSum) >> shgSum) * pa;
+ pixels[p + 2] = ((bSum * mulSum) >> shgSum) * pa;
+ } else {
+ pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;
+ }
+
+ rSum -= rOutSum;
+ gSum -= gOutSum;
+ bSum -= bOutSum;
+ aSum -= aOutSum;
+
+ rOutSum -= stackIn.r;
+ gOutSum -= stackIn.g;
+ bOutSum -= stackIn.b;
+ aOutSum -= stackIn.a;
+
+ p = (x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width) << 2;
+
+ rSum += rInSum += stackIn.r = pixels[p];
+ gSum += gInSum += stackIn.g = pixels[p + 1];
+ bSum += bInSum += stackIn.b = pixels[p + 2];
+ aSum += aInSum += stackIn.a = pixels[p + 3];
+
+ if (stackIn.next) stackIn = stackIn.next;
+
+ if (stackOut) {
+ rOutSum += pr = stackOut.r;
+ gOutSum += pg = stackOut.g;
+ bOutSum += pb = stackOut.b;
+ aOutSum += pa = stackOut.a;
+
+ rInSum -= pr;
+ gInSum -= pg;
+ bInSum -= pb;
+ aInSum -= pa;
+
+ stackOut = stackOut.next;
+ }
+
+ yi += width;
+ }
+ }
+ return imageData;
+}
+
+/**
+ * @param {ImageData} imageData
+ * @param {Integer} topX
+ * @param {Integer} topY
+ * @param {Integer} width
+ * @param {Integer} height
+ * @param {Float} radius
+ * @returns {ImageData}
+ */
+function processImageDataRGB(imageData: ImageData, width: number, height: number, radius: number) {
+ const pixels = imageData.data;
+
+ const div = 2 * radius + 1;
+ // const w4 = width << 2;
+ const widthMinus1 = width - 1;
+ const heightMinus1 = height - 1;
+ const radiusPlus1 = radius + 1;
+ const sumFactor = (radiusPlus1 * (radiusPlus1 + 1)) / 2;
+
+ const stackStart = new BlurStack();
+ let stack = stackStart;
+ let stackEnd;
+ for (let i = 1; i < div; i++) {
+ stack = stack.next = new BlurStack();
+ if (i === radiusPlus1) {
+ stackEnd = stack;
+ }
+ }
+ stack.next = stackStart;
+ let stackIn = null;
+ let stackOut = null;
+
+ const mulSum = mulTable[radius];
+ const shgSum = shgTable[radius];
+
+ let p, rbs;
+ let yw = 0,
+ yi = 0;
+
+ for (let y = 0; y < height; y++) {
+ let pr = pixels[yi],
+ pg = pixels[yi + 1],
+ pb = pixels[yi + 2],
+ rOutSum = radiusPlus1 * pr,
+ gOutSum = radiusPlus1 * pg,
+ bOutSum = radiusPlus1 * pb,
+ rSum = sumFactor * pr,
+ gSum = sumFactor * pg,
+ bSum = sumFactor * pb;
+
+ stack = stackStart;
+
+ for (let i = 0; i < radiusPlus1; i++) {
+ stack.r = pr;
+ stack.g = pg;
+ stack.b = pb;
+ if (stack.next) stack = stack.next;
+ }
+
+ let rInSum = 0,
+ gInSum = 0,
+ bInSum = 0;
+ for (let i = 1; i < radiusPlus1; i++) {
+ p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
+ rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);
+ gSum += (stack.g = pg = pixels[p + 1]) * rbs;
+ bSum += (stack.b = pb = pixels[p + 2]) * rbs;
+
+ rInSum += pr;
+ gInSum += pg;
+ bInSum += pb;
+
+ if (stack.next) stack = stack.next;
+ }
+
+ stackIn = stackStart;
+ stackOut = stackEnd;
+ for (let x = 0; x < width; x++) {
+ pixels[yi] = (rSum * mulSum) >> shgSum;
+ pixels[yi + 1] = (gSum * mulSum) >> shgSum;
+ pixels[yi + 2] = (bSum * mulSum) >> shgSum;
+
+ rSum -= rOutSum;
+ gSum -= gOutSum;
+ bSum -= bOutSum;
+
+ if (stackIn && stackOut) {
+ rOutSum -= stackIn.r;
+ gOutSum -= stackIn.g;
+ bOutSum -= stackIn.b;
+
+ p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
+
+ rInSum += stackIn.r = pixels[p];
+ gInSum += stackIn.g = pixels[p + 1];
+ bInSum += stackIn.b = pixels[p + 2];
+
+ rSum += rInSum;
+ gSum += gInSum;
+ bSum += bInSum;
+
+ stackIn = stackIn.next;
+
+ rOutSum += pr = stackOut.r;
+ gOutSum += pg = stackOut.g;
+ bOutSum += pb = stackOut.b;
+
+ rInSum -= pr;
+ gInSum -= pg;
+ bInSum -= pb;
+
+ stackOut = stackOut.next;
+ }
+
+ yi += 4;
+ }
+ yw += width;
+ }
+
+ for (let x = 0; x < width; x++) {
+ yi = x << 2;
+ let pr = pixels[yi],
+ pg = pixels[yi + 1],
+ pb = pixels[yi + 2],
+ rOutSum = radiusPlus1 * pr,
+ gOutSum = radiusPlus1 * pg,
+ bOutSum = radiusPlus1 * pb,
+ rSum = sumFactor * pr,
+ gSum = sumFactor * pg,
+ bSum = sumFactor * pb;
+
+ stack = stackStart;
+
+ for (let i = 0; i < radiusPlus1; i++) {
+ stack.r = pr;
+ stack.g = pg;
+ stack.b = pb;
+ if (stack.next) stack = stack.next;
+ }
+
+ let rInSum = 0,
+ gInSum = 0,
+ bInSum = 0;
+ for (let i = 1, yp = width; i <= radius; i++) {
+ yi = (yp + x) << 2;
+
+ rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);
+ gSum += (stack.g = pg = pixels[yi + 1]) * rbs;
+ bSum += (stack.b = pb = pixels[yi + 2]) * rbs;
+
+ rInSum += pr;
+ gInSum += pg;
+ bInSum += pb;
+
+ if (stack.next) stack = stack.next;
+
+ if (i < heightMinus1) {
+ yp += width;
+ }
+ }
+
+ yi = x;
+ stackIn = stackStart;
+ stackOut = stackEnd;
+ for (let y = 0; y < height; y++) {
+ p = yi << 2;
+ pixels[p] = (rSum * mulSum) >> shgSum;
+ pixels[p + 1] = (gSum * mulSum) >> shgSum;
+ pixels[p + 2] = (bSum * mulSum) >> shgSum;
+
+ rSum -= rOutSum;
+ gSum -= gOutSum;
+ bSum -= bOutSum;
+
+ if (stackIn && stackOut) {
+ rOutSum -= stackIn.r;
+ gOutSum -= stackIn.g;
+ bOutSum -= stackIn.b;
+
+ p = (x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width) << 2;
+
+ rSum += rInSum += stackIn.r = pixels[p];
+ gSum += gInSum += stackIn.g = pixels[p + 1];
+ bSum += bInSum += stackIn.b = pixels[p + 2];
+
+ stackIn = stackIn.next;
+
+ rOutSum += pr = stackOut.r;
+ gOutSum += pg = stackOut.g;
+ bOutSum += pb = stackOut.b;
+
+ rInSum -= pr;
+ gInSum -= pg;
+ bInSum -= pb;
+
+ stackOut = stackOut.next;
+ }
+
+ yi += width;
+ }
+ }
+
+ return imageData;
+}
+
+/**
+ *
+ */
+export class BlurStack {
+ /**
+ * Set properties.
+ */
+ r: number;
+ g: number;
+ b: number;
+ a: number;
+ next?: BlurStack;
+
+ constructor() {
+ this.r = 0;
+ this.g = 0;
+ this.b = 0;
+ this.a = 0;
+ }
+}
+
+export const stackBlurImage = (
+ imageData: ImageData,
+ width: number,
+ height: number,
+ radius: number,
+ blurAlphaChannel: number
+): ImageData => {
+ if (isNaN(radius) || radius < 1) {
+ return imageData;
+ }
+ radius |= 0;
+
+ if (blurAlphaChannel) {
+ return processImageDataRGBA(imageData, width, height, radius);
+ } else {
+ return processImageDataRGB(imageData, width, height, radius);
+ }
+};
diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts
index 3430e3a..2ae6851 100644
--- a/src/render/canvas/canvas-renderer.ts
+++ b/src/render/canvas/canvas-renderer.ts
@@ -1,5 +1,17 @@
import {ElementPaint, parseStackingContexts, StackingContext} from '../stacking-context';
-import {asString, Color, isTransparent} from '../../css/types/color';
+import {
+ asString,
+ Color,
+ isTransparent,
+ RGBColor,
+ contrastRGB,
+ hueRotateRGB,
+ grayscaleRGB,
+ brightnessRGB,
+ invertRGB,
+ saturateRGB,
+ sepiaRGB
+} from '../../css/types/color';
import {Logger} from '../../core/logger';
import {ElementContainer} from '../../dom/element-container';
import {BORDER_STYLE} from '../../css/property-descriptors/border-style';
@@ -38,6 +50,8 @@ import {TextareaElementContainer} from '../../dom/elements/textarea-element-cont
import {SelectElementContainer} from '../../dom/elements/select-element-container';
import {IFrameElementContainer} from '../../dom/replaced-elements/iframe-element-container';
import {TextShadow} from '../../css/property-descriptors/text-shadow';
+import {Filter} from '../../css/property-descriptors/filter';
+import {stackBlurImage} from '../../css/types/functions/stackBlur';
export type RenderConfigurations = RenderOptions & {
backgroundColor: Color | null;
@@ -248,7 +262,8 @@ export class CanvasRenderer {
renderReplacedElement(
container: ReplacedElementContainer,
curves: BoundCurves,
- image: HTMLImageElement | HTMLCanvasElement
+ image: HTMLImageElement | HTMLCanvasElement,
+ filter?: Filter | null | undefined
) {
if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
const box = contentBox(container);
@@ -267,6 +282,49 @@ export class CanvasRenderer {
box.width,
box.height
);
+ if (filter) {
+ try {
+ let imageData = this.ctx.getImageData(box.left, box.top, box.width, box.height);
+ for (let _j = 0; _j < imageData.height; _j++) {
+ for (let _i = 0; _i < imageData.width; _i++) {
+ let index = _j * 4 * imageData.width + _i * 4;
+ let rgb: RGBColor = {
+ r: imageData.data[index],
+ g: imageData.data[index + 1],
+ b: imageData.data[index + 2]
+ };
+ if (filter.contrast) {
+ rgb = contrastRGB(rgb, filter.contrast.number);
+ }
+ if (filter['hue-rotate']) {
+ rgb = hueRotateRGB(rgb, filter['hue-rotate'].number);
+ }
+ if (filter.grayscale) {
+ rgb = grayscaleRGB(rgb, filter['grayscale'].number, 'luma:BT709');
+ }
+ if (filter.brightness) {
+ rgb = brightnessRGB(rgb, filter['brightness'].number);
+ }
+ if (filter.invert) {
+ rgb = invertRGB(rgb, filter['invert'].number);
+ }
+ if (filter.saturate) {
+ rgb = saturateRGB(rgb, filter.saturate.number);
+ }
+ if (filter.sepia) {
+ rgb = sepiaRGB(rgb, filter.sepia.number);
+ }
+ imageData.data[index] = rgb.r;
+ imageData.data[index + 1] = rgb.g;
+ imageData.data[index + 2] = rgb.b;
+ }
+ }
+ if (filter.blur) {
+ imageData = stackBlurImage(imageData, box.width, box.height, filter.blur.number * 2.2, 1);
+ }
+ this.ctx.putImageData(imageData, box.left, box.top);
+ } catch (error) {}
+ }
this.ctx.restore();
}
}
@@ -283,7 +341,7 @@ export class CanvasRenderer {
if (container instanceof ImageElementContainer) {
try {
const image = await this.options.cache.match(container.src);
- this.renderReplacedElement(container, curves, image);
+ this.renderReplacedElement(container, curves, image, styles.filter);
} catch (e) {
Logger.getInstance(this.options.id).error(`Error loading image ${container.src}`);
}