未验证 提交 5c8a6260 编写于 作者: Y Yegor 提交者: GitHub

Delete CSS paint code (#20426)

* Delete CSS paint code
上级 2ac5b346
......@@ -419,7 +419,6 @@ FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc
FILE: ../../../flutter/lib/ui/window/viewport_metrics.h
FILE: ../../../flutter/lib/ui/window/window.cc
FILE: ../../../flutter/lib/ui/window/window.h
FILE: ../../../flutter/lib/web_ui/lib/assets/houdini_painter.js
FILE: ../../../flutter/lib/web_ui/lib/src/engine.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart
......@@ -463,7 +462,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_renderer.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/houdini_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(yjbanov): Consider the following optimizations:
// - Switch from JSON to typed arrays. See:
// https://github.com/w3c/css-houdini-drafts/issues/136
// - When there is no DOM-rendered content, then clipping in the canvas is more
// efficient than DOM-rendered clipping.
// - When DOM-rendered clip is the only option, then clipping _again_ in the
// canvas is superfluous.
// - When transform is a 2D transform and there is no DOM-rendered content, then
// canvas transform is more efficient than DOM-rendered transform.
// - If a transform must be DOM-rendered, then clipping in the canvas _again_ is
// superfluous.
/**
* Applies paint commands to CSS Paint API (a.k.a. Houdini).
*
* This painter is driven by houdini_canvas.dart. This painter and the
* HoudiniCanvas class must be kept in sync with each other.
*/
class FlutterPainter {
/**
* Properties used by this painter.
*
* @return {string[]} list of CSS properties this painter depends on.
*/
static get inputProperties() {
return ['--flt'];
}
/**
* Implements the painter interface.
*/
paint(ctx, geom, properties) {
let fltProp = properties.get('--flt').toString();
if (!fltProp) {
// Nothing to paint.
return;
}
const commands = JSON.parse(fltProp);
for (let i = 0; i < commands.length; i++) {
let command = commands[i];
// TODO(yjbanov): we should probably move command identifiers into an enum
switch (command[0]) {
case 1:
this._save(ctx, geom, command);
break;
case 2:
this._restore(ctx, geom, command);
break;
case 3:
this._translate(ctx, geom, command);
break;
case 4:
this._scale(ctx, geom, command);
break;
case 5:
this._rotate(ctx, geom, command);
break;
// Skip case 6: implemented in the DOM for now.
case 7:
this._skew(ctx, geom, command);
break;
case 8:
this._clipRect(ctx, geom, command);
break;
case 9:
this._clipRRect(ctx, geom, command);
break;
case 10:
this._clipPath(ctx, geom, command);
break;
case 11:
this._drawColor(ctx, geom, command);
break;
case 12:
this._drawLine(ctx, geom, command);
break;
case 13:
this._drawPaint(ctx, geom, command);
break;
case 14:
this._drawRect(ctx, geom, command);
break;
case 15:
this._drawRRect(ctx, geom, command);
break;
case 16:
this._drawDRRect(ctx, geom, command);
break;
case 17:
this._drawOval(ctx, geom, command);
break;
case 18:
this._drawCircle(ctx, geom, command);
break;
case 19:
this._drawPath(ctx, geom, command);
break;
case 20:
this._drawShadow(ctx, geom, command);
break;
default:
throw new Error(`Unsupported command ID: ${command[0]}`);
}
}
}
_applyPaint(ctx, paint) {
let blendMode = _stringForBlendMode(paint.blendMode);
ctx.globalCompositeOperation = blendMode ? blendMode : 'source-over';
ctx.lineWidth = paint.strokeWidth ? paint.strokeWidth : 1.0;
let strokeCap = _stringForStrokeCap(paint.strokeCap);
ctx.lineCap = strokeCap ? strokeCap : 'butt';
if (paint.shader != null) {
let paintStyle = paint.shader.createPaintStyle(ctx);
ctx.fillStyle = paintStyle;
ctx.strokeStyle = paintStyle;
} else if (paint.color != null) {
let colorString = paint.color;
ctx.fillStyle = colorString;
ctx.strokeStyle = colorString;
}
if (paint.maskFilter != null) {
ctx.filter = `blur(${paint.maskFilter[1]}px)`;
}
}
_strokeOrFill(ctx, paint, resetPaint) {
switch (paint.style) {
case PaintingStyle.stroke:
ctx.stroke();
break;
case PaintingStyle.fill:
default:
ctx.fill();
break;
}
if (resetPaint) {
this._resetPaint(ctx);
}
}
_resetPaint(ctx) {
ctx.globalCompositeOperation = 'source-over';
ctx.lineWidth = 1.0;
ctx.lineCap = 'butt';
ctx.filter = 'none';
ctx.fillStyle = null;
ctx.strokeStyle = null;
}
_save(ctx, geom, command) {
ctx.save();
}
_restore(ctx, geom, command) {
ctx.restore();
}
_translate(ctx, geom, command) {
ctx.translate(command[1], command[2]);
}
_scale(ctx, geom, command) {
ctx.translate(command[1], command[2]);
}
_rotate(ctx, geom, command) {
ctx.rotate(command[1]);
}
_skew(ctx, geom, command) {
ctx.translate(command[1], command[2]);
}
_drawRect(ctx, geom, command) {
let scanner = _scanCommand(command);
let rect = scanner.scanRect();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
ctx.beginPath();
ctx.rect(rect.left, rect.top, rect.width(), rect.height());
this._strokeOrFill(ctx, paint, true);
}
_drawRRect(ctx, geom, command) {
let scanner = _scanCommand(command);
let rrect = scanner.scanRRect();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
this._drawRRectPath(ctx, rrect, true);
this._strokeOrFill(ctx, paint, true);
}
_drawDRRect(ctx, geom, command) {
let scanner = _scanCommand(command);
let outer = scanner.scanRRect();
let inner = scanner.scanRRect();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
this._drawRRectPath(ctx, outer, true);
this._drawRRectPathReverse(ctx, inner, false);
this._strokeOrFill(ctx, paint, true);
}
_drawRRectPath(ctx, rrect, startNewPath) {
// TODO(mdebbar): there's a bug in this code, it doesn't correctly handle
// the case when the radius is greater than the width of the
// rect. When we fix that in BitmapCanvas, we need to fix it
// here too.
// To draw the rounded rectangle, perform the following 8 steps:
// 1. draw the line for the top
// 2. draw the arc for the top-right corner
// 3. draw the line for the right side
// 4. draw the arc for the bottom-right corner
// 5. draw the line for the bottom of the rectangle
// 6. draw the arc for the bottom-left corner
// 7. draw the line for the left side
// 8. draw the arc for the top-left corner
//
// After drawing, the current point will be the left side of the top of the
// rounded rectangle (after the corner).
// TODO(het): Confirm that this is the end point in Flutter for RRect
if (startNewPath) {
ctx.beginPath();
}
ctx.moveTo(rrect.left + rrect.trRadiusX, rrect.top);
// Top side and top-right corner
ctx.lineTo(rrect.right - rrect.trRadiusX, rrect.top);
ctx.ellipse(
rrect.right - rrect.trRadiusX,
rrect.top + rrect.trRadiusY,
rrect.trRadiusX,
rrect.trRadiusY,
0,
1.5 * Math.PI,
2.0 * Math.PI,
false,
);
// Right side and bottom-right corner
ctx.lineTo(rrect.right, rrect.bottom - rrect.brRadiusY);
ctx.ellipse(
rrect.right - rrect.brRadiusX,
rrect.bottom - rrect.brRadiusY,
rrect.brRadiusX,
rrect.brRadiusY,
0,
0,
0.5 * Math.PI,
false,
);
// Bottom side and bottom-left corner
ctx.lineTo(rrect.left + rrect.blRadiusX, rrect.bottom);
ctx.ellipse(
rrect.left + rrect.blRadiusX,
rrect.bottom - rrect.blRadiusY,
rrect.blRadiusX,
rrect.blRadiusY,
0,
0.5 * Math.PI,
Math.PI,
false,
);
// Left side and top-left corner
ctx.lineTo(rrect.left, rrect.top + rrect.tlRadiusY);
ctx.ellipse(
rrect.left + rrect.tlRadiusX,
rrect.top + rrect.tlRadiusY,
rrect.tlRadiusX,
rrect.tlRadiusY,
0,
Math.PI,
1.5 * Math.PI,
false,
);
}
_drawRRectPathReverse(ctx, rrect, startNewPath) {
// Draw the rounded rectangle, counterclockwise.
ctx.moveTo(rrect.right - rrect.trRadiusX, rrect.top);
if (startNewPath) {
ctx.beginPath();
}
// Top side and top-left corner
ctx.lineTo(rrect.left + rrect.tlRadiusX, rrect.top);
ctx.ellipse(
rrect.left + rrect.tlRadiusX,
rrect.top + rrect.tlRadiusY,
rrect.tlRadiusX,
rrect.tlRadiusY,
0,
1.5 * Math.PI,
Math.PI,
true,
);
// Left side and bottom-left corner
ctx.lineTo(rrect.left, rrect.bottom - rrect.blRadiusY);
ctx.ellipse(
rrect.left + rrect.blRadiusX,
rrect.bottom - rrect.blRadiusY,
rrect.blRadiusX,
rrect.blRadiusY,
0,
Math.PI,
0.5 * Math.PI,
true,
);
// Bottom side and bottom-right corner
ctx.lineTo(rrect.right - rrect.brRadiusX, rrect.bottom);
ctx.ellipse(
rrect.right - rrect.brRadiusX,
rrect.bottom - rrect.brRadiusY,
rrect.brRadiusX,
rrect.brRadiusY,
0,
0.5 * Math.PI,
0,
true,
);
// Right side and top-right corner
ctx.lineTo(rrect.right, rrect.top + rrect.trRadiusY);
ctx.ellipse(
rrect.right - rrect.trRadiusX,
rrect.top + rrect.trRadiusY,
rrect.trRadiusX,
rrect.trRadiusY,
0,
0,
1.5 * Math.PI,
true,
);
}
_clipRect(ctx, geom, command) {
let scanner = _scanCommand(command);
let rect = scanner.scanRect();
ctx.beginPath();
ctx.rect(rect.left, rect.top, rect.width(), rect.height());
ctx.clip();
}
_clipRRect(ctx, geom, command) {
let path = new Path([]);
let commands = [new RRectCommand(command[1])];
path.subpaths.push(new Subpath(commands));
this._runPath(ctx, path);
ctx.clip();
}
_clipPath(ctx, geom, command) {
let scanner = _scanCommand(command);
let path = scanner.scanPath();
this._runPath(ctx, path);
ctx.clip();
}
_drawCircle(ctx, geom, command) {
let scanner = _scanCommand(command);
let dx = scanner.scanNumber();
let dy = scanner.scanNumber();
let radius = scanner.scanNumber();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
ctx.beginPath();
ctx.ellipse(dx, dy, radius, radius, 0, 0, 2.0 * Math.PI, false);
this._strokeOrFill(ctx, paint, true);
}
_drawOval(ctx, geom, command) {
let scanner = _scanCommand(command);
let rect = scanner.scanRect();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
ctx.beginPath();
ctx.ellipse(
(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2,
rect.width / 2, rect.height / 2, 0, 0, 2.0 * Math.PI, false);
this._strokeOrFill(ctx, paint, true);
}
_drawPath(ctx, geom, command) {
let scanner = _scanCommand(command);
let path = scanner.scanPath();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
this._runPath(ctx, path);
this._strokeOrFill(ctx, paint, true);
}
_drawShadow(ctx, geom, command) {
// TODO: this is mostly a stub; implement properly.
let scanner = _scanCommand(command);
let path = scanner.scanPath();
let color = scanner.scanArray();
let elevation = scanner.scanNumber();
let transparentOccluder = scanner.scanBool();
let shadows = _computeShadowsForElevation(elevation, color);
for (let i = 0; i < shadows.length; i++) {
let shadow = shadows[i];
let paint = new Paint(
null, // blendMode
PaintingStyle.fill, // style
1.0, // strokeWidth
null, // strokeCap
true, // isAntialias
shadow.color, // color
null, // shader
[BlurStyle.normal, shadow.blur], // maskFilter
null, // filterQuality
null // colorFilter
);
ctx.save();
ctx.translate(shadow.offsetX, shadow.offsetY);
this._applyPaint(ctx, paint);
this._runPath(ctx, path, true);
this._strokeOrFill(ctx, paint, false);
ctx.restore();
}
this._resetPaint(ctx);
}
_runPath(ctx, path) {
ctx.beginPath();
for (let i = 0; i < path.subpaths.length; i++) {
let subpath = path.subpaths[i];
for (let j = 0; j < subpath.commands.length; j++) {
let command = subpath.commands[j];
switch (command.type()) {
case PathCommandType.bezierCurveTo:
ctx.bezierCurveTo(
command.x1, command.y1, command.x2, command.y2, command.x3,
command.y3);
break;
case PathCommandType.close:
ctx.closePath();
break;
case PathCommandType.ellipse:
ctx.ellipse(
command.x, command.y, command.radiusX, command.radiusY,
command.rotation, command.startAngle, command.endAngle,
command.anticlockwise);
break;
case PathCommandType.lineTo:
ctx.lineTo(command.x, command.y);
break;
case PathCommandType.moveTo:
ctx.moveTo(command.x, command.y);
break;
case PathCommandType.rrect:
this._drawRRectPath(ctx, command.rrect, false);
break;
case PathCommandType.rect:
ctx.rect(command.x, command.y, command.width, command.height);
break;
case PathCommandType.quadraticCurveTo:
ctx.quadraticCurveTo(
command.x1, command.y1, command.x2, command.y2);
break;
default:
throw new Error(`Unknown path command ${command.type()}`);
}
}
}
}
_drawColor(ctx, geom, command) {
ctx.globalCompositeOperation = _stringForBlendMode(command[2]);
ctx.fillStyle = command[1];
// Fill a virtually infinite rect with the color.
//
// We can't use (0, 0, width, height) because the current transform can
// cause it to not fill the entire clip.
ctx.fillRect(-10000, -10000, 20000, 20000);
this._resetPaint(ctx);
}
_drawLine(ctx, geom, command) {
let scanner = _scanCommand(command);
let p1dx = scanner.scanNumber();
let p1dy = scanner.scanNumber();
let p2dx = scanner.scanNumber();
let p2dy = scanner.scanNumber();
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
ctx.beginPath();
ctx.moveTo(p1dx, p1dy);
ctx.lineTo(p2dx, p2dy);
ctx.stroke();
this._resetPaint(ctx);
}
_drawPaint(ctx, geom, command) {
let scanner = _scanCommand(command);
let paint = scanner.scanPaint();
this._applyPaint(ctx, paint);
ctx.beginPath();
// Fill a virtually infinite rect with the color.
//
// We can't use (0, 0, width, height) because the current transform can
// cause it to not fill the entire clip.
ctx.fillRect(-10000, -10000, 20000, 20000);
this._resetPaint(ctx);
}
}
function _scanCommand(command) {
return new CommandScanner(command);
}
const PaintingStyle = {
fill: 0,
stroke: 1,
};
/// A singleton used to parse serialized commands.
class CommandScanner {
constructor(command) {
// Skip the first element, which is always the command ID.
this.index = 1;
this.command = command;
}
scanRect() {
let rect = this.command[this.index++];
return new Rect(rect[0], rect[1], rect[2], rect[3]);
}
scanRRect() {
let rrect = this.command[this.index++];
return new RRect(
rrect[0], rrect[1], rrect[2], rrect[3], rrect[4], rrect[5], rrect[6],
rrect[7], rrect[8], rrect[9], rrect[10], rrect[11]);
}
scanPaint() {
let paint = this.command[this.index++];
return new Paint(
paint[0], paint[1], paint[2], paint[3], paint[4], paint[5], paint[6],
paint[7], paint[8], paint[9]);
}
scanNumber() {
return this.command[this.index++];
}
scanString() {
return this.command[this.index++];
}
scanBool() {
return this.command[this.index++];
}
scanPath() {
let subpaths = this.command[this.index++];
return new Path(subpaths);
}
scanArray() {
return this.command[this.index++];
}
}
class Rect {
constructor(left, top, right, bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
width() {
return this.right - this.left;
}
height() {
return this.bottom - this.top;
}
}
class RRect {
constructor(
left, top, right, bottom, tlRadiusX, tlRadiusY, trRadiusX, trRadiusY,
brRadiusX, brRadiusY, blRadiusX, blRadiusY) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
this.tlRadiusX = tlRadiusX;
this.tlRadiusY = tlRadiusY;
this.trRadiusX = trRadiusX;
this.trRadiusY = trRadiusY;
this.brRadiusX = brRadiusX;
this.brRadiusY = brRadiusY;
this.blRadiusX = blRadiusX;
this.blRadiusY = blRadiusY;
}
tallMiddleRect() {
let leftRadius = Math.max(this.blRadiusX, this.tlRadiusX);
let rightRadius = Math.max(this.trRadiusX, this.brRadiusX);
return new Rect(
this.left + leftRadius, this.top, this.right - rightRadius,
this.bottom);
}
middleRect() {
let leftRadius = Math.max(this.blRadiusX, this.tlRadiusX);
let topRadius = Math.max(this.tlRadiusY, this.trRadiusY);
let rightRadius = Math.max(this.trRadiusX, this.brRadiusX);
let bottomRadius = Math.max(this.brRadiusY, this.blRadiusY);
return new Rect(
this.left + leftRadius, this.top + topRadius, this.right - rightRadius,
this.bottom - bottomRadius);
}
wideMiddleRect() {
let topRadius = Math.max(this.tlRadiusY, this.trRadiusY);
let bottomRadius = Math.max(this.brRadiusY, this.blRadiusY);
return new Rect(
this.left, this.top + topRadius, this.right,
this.bottom - bottomRadius);
}
}
class Paint {
constructor(
blendMode, style, strokeWidth, strokeCap, isAntialias, color, shader,
maskFilter, filterQuality, colorFilter) {
this.blendMode = blendMode;
this.style = style;
this.strokeWidth = strokeWidth;
this.strokeCap = strokeCap;
this.isAntialias = isAntialias;
this.color = color;
this.shader = _deserializeShader(shader); // TODO: deserialize
this.maskFilter = maskFilter;
this.filterQuality = filterQuality;
this.colorFilter = colorFilter; // TODO: deserialize
}
}
function _deserializeShader(data) {
if (!data) {
return null;
}
switch (data[0]) {
case 1:
return new GradientLinear(data);
default:
throw new Error(`Shader type not supported: ${data}`);
}
}
class GradientLinear {
constructor(data) {
this.fromX = data[1];
this.fromY = data[2];
this.toX = data[3];
this.toY = data[4];
this.colors = data[5];
this.colorStops = data[6];
this.tileMode = data[7];
}
createPaintStyle(ctx) {
let gradient =
ctx.createLinearGradient(this.fromX, this.fromY, this.toX, this.toY);
if (this.colorStops == null) {
gradient.addColorStop(0, this.colors[0]);
gradient.addColorStop(1, this.colors[1]);
return gradient;
}
for (let i = 0; i < this.colors.length; i++) {
gradient.addColorStop(this.colorStops[i], this.colors[i]);
}
return gradient;
}
}
const BlendMode = {
clear: 0,
src: 1,
dst: 2,
srcOver: 3,
dstOver: 4,
srcIn: 5,
dstIn: 6,
srcOut: 7,
dstOut: 8,
srcATop: 9,
dstATop: 10,
xor: 11,
plus: 12,
modulate: 13,
screen: 14,
overlay: 15,
darken: 16,
lighten: 17,
colorDodge: 18,
colorBurn: 19,
hardLight: 20,
softLight: 21,
difference: 22,
exclusion: 23,
multiply: 24,
hue: 25,
saturation: 26,
color: 27,
luminosity: 28,
};
function _stringForBlendMode(blendMode) {
if (blendMode == null) return null;
switch (blendMode) {
case BlendMode.srcOver:
return 'source-over';
case BlendMode.srcIn:
return 'source-in';
case BlendMode.srcOut:
return 'source-out';
case BlendMode.srcATop:
return 'source-atop';
case BlendMode.dstOver:
return 'destination-over';
case BlendMode.dstIn:
return 'destination-in';
case BlendMode.dstOut:
return 'destination-out';
case BlendMode.dstATop:
return 'destination-atop';
case BlendMode.plus:
return 'lighten';
case BlendMode.src:
return 'copy';
case BlendMode.xor:
return 'xor';
case BlendMode.multiply:
// Falling back to multiply, ignoring alpha channel.
// TODO(flutter_web): only used for debug, find better fallback for web.
case BlendMode.modulate:
return 'multiply';
case BlendMode.screen:
return 'screen';
case BlendMode.overlay:
return 'overlay';
case BlendMode.darken:
return 'darken';
case BlendMode.lighten:
return 'lighten';
case BlendMode.colorDodge:
return 'color-dodge';
case BlendMode.colorBurn:
return 'color-burn';
case BlendMode.hardLight:
return 'hard-light';
case BlendMode.softLight:
return 'soft-light';
case BlendMode.difference:
return 'difference';
case BlendMode.exclusion:
return 'exclusion';
case BlendMode.hue:
return 'hue';
case BlendMode.saturation:
return 'saturation';
case BlendMode.color:
return 'color';
case BlendMode.luminosity:
return 'luminosity';
default:
throw new Error(
'Flutter web does not support the blend mode: $blendMode');
}
}
const StrokeCap = {
butt: 0,
round: 1,
square: 2,
};
function _stringForStrokeCap(strokeCap) {
if (strokeCap == null) return null;
switch (strokeCap) {
case StrokeCap.butt:
return 'butt';
case StrokeCap.round:
return 'round';
case StrokeCap.square:
default:
return 'square';
}
}
class Path {
constructor(serializedSubpaths) {
this.subpaths = [];
for (let i = 0; i < serializedSubpaths.length; i++) {
let subpath = serializedSubpaths[i];
let pathCommands = [];
for (let j = 0; j < subpath.length; j++) {
let pathCommand = subpath[j];
switch (pathCommand[0]) {
case 1:
pathCommands.push(new MoveTo(pathCommand));
break;
case 2:
pathCommands.push(new LineTo(pathCommand));
break;
case 3:
pathCommands.push(new Ellipse(pathCommand));
break;
case 4:
pathCommands.push(new QuadraticCurveTo(pathCommand));
break;
case 5:
pathCommands.push(new BezierCurveTo(pathCommand));
break;
case 6:
pathCommands.push(new RectCommand(pathCommand));
break;
case 7:
pathCommands.push(new RRectCommand(pathCommand));
break;
case 8:
pathCommands.push(new CloseCommand());
break;
default:
throw new Error(`Unsupported path command: ${pathCommand}`);
}
}
this.subpaths.push(new Subpath(pathCommands));
}
}
}
class Subpath {
constructor(commands) {
this.commands = commands;
}
}
class MoveTo {
constructor(data) {
this.x = data[1];
this.y = data[2];
}
type() {
return PathCommandType.moveTo;
}
}
class LineTo {
constructor(data) {
this.x = data[1];
this.y = data[2];
}
type() {
return PathCommandType.lineTo;
}
}
class Ellipse {
constructor(data) {
this.x = data[1];
this.y = data[2];
this.radiusX = data[3];
this.radiusY = data[4];
this.rotation = data[5];
this.startAngle = data[6];
this.endAngle = data[7];
this.anticlockwise = data[8];
}
type() {
return PathCommandType.ellipse;
}
}
class QuadraticCurveTo {
constructor(data) {
this.x1 = data[1];
this.y1 = data[2];
this.x2 = data[3];
this.y2 = data[4];
}
type() {
return PathCommandType.quadraticCurveTo;
}
}
class BezierCurveTo {
constructor(data) {
this.x1 = data[1];
this.y1 = data[2];
this.x2 = data[3];
this.y2 = data[4];
this.x3 = data[5];
this.y3 = data[6];
}
type() {
return PathCommandType.bezierCurveTo;
}
}
class RectCommand {
constructor(data) {
this.x = data[1];
this.y = data[2];
this.width = data[3];
this.height = data[4];
}
type() {
return PathCommandType.rect;
}
}
class RRectCommand {
constructor(data) {
let scanner = _scanCommand(data);
this.rrect = scanner.scanRRect();
}
type() {
return PathCommandType.rrect;
}
}
class CloseCommand {
type() {
return PathCommandType.close;
}
}
class CanvasShadow {
constructor(offsetX, offsetY, blur, spread, color) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.blur = blur;
this.spread = spread;
this.color = color;
}
}
const _noShadows = [];
function _computeShadowsForElevation(elevation, color) {
if (elevation <= 0.0) {
return _noShadows;
} else if (elevation <= 1.0) {
return _computeShadowElevation(2, color);
} else if (elevation <= 2.0) {
return _computeShadowElevation(4, color);
} else if (elevation <= 3.0) {
return _computeShadowElevation(6, color);
} else if (elevation <= 4.0) {
return _computeShadowElevation(8, color);
} else if (elevation <= 5.0) {
return _computeShadowElevation(16, color);
} else {
return _computeShadowElevation(24, color);
}
}
function _computeShadowElevation(dp, color) {
// TODO(yjbanov): multiple shadows are very expensive. Find a more efficient
// method to render them.
let red = color[1];
let green = color[2];
let blue = color[3];
// let penumbraColor = `rgba(${red}, ${green}, ${blue}, 0.14)`;
// let ambientShadowColor = `rgba(${red}, ${green}, ${blue}, 0.12)`;
let umbraColor = `rgba(${red}, ${green}, ${blue}, 0.2)`;
let result = [];
if (dp === 2) {
// result.push(new CanvasShadow(0.0, 2.0, 1.0, 0.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 3.0, 0.5, -2.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 1.0, 2.5, 0.0, umbraColor));
} else if (dp === 3) {
// result.push(new CanvasShadow(0.0, 1.5, 4.0, 0.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 3.0, 1.5, -2.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 1.0, 4.0, 0.0, umbraColor));
} else if (dp === 4) {
// result.push(new CanvasShadow(0.0, 4.0, 2.5, 0.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 1.0, 5.0, 0.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 2.0, 2.0, -1.0, umbraColor));
} else if (dp === 6) {
// result.push(new CanvasShadow(0.0, 6.0, 5.0, 0.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 1.0, 9.0, 0.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 3.0, 2.5, -1.0, umbraColor));
} else if (dp === 8) {
// result.push(new CanvasShadow(0.0, 4.0, 10.0, 1.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 3.0, 7.0, 2.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 5.0, 2.5, -3.0, umbraColor));
} else if (dp === 12) {
// result.push(new CanvasShadow(0.0, 12.0, 8.5, 2.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 5.0, 11.0, 4.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 7.0, 4.0, -4.0, umbraColor));
} else if (dp === 16) {
// result.push(new CanvasShadow(0.0, 16.0, 12.0, 2.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 6.0, 15.0, 5.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 0.0, 5.0, -5.0, umbraColor));
} else {
// result.push(new CanvasShadow(0.0, 24.0, 18.0, 3.0, penumbraColor));
// result.push(new CanvasShadow(0.0, 9.0, 23.0, 8.0, ambientShadowColor));
result.push(new CanvasShadow(0.0, 11.0, 7.5, -7.0, umbraColor));
}
return result;
}
const PathCommandType = {
moveTo: 0,
lineTo: 1,
ellipse: 2,
close: 3,
quadraticCurveTo: 4,
bezierCurveTo: 5,
rect: 6,
rrect: 7,
};
const TileMode = {
clamp: 0,
repeated: 1,
};
const BlurStyle = {
normal: 0,
solid: 1,
outer: 2,
inner: 3,
};
/// This makes the painter available as "background-image: paint(flt)".
registerPaint('flt', FlutterPainter);
......@@ -64,7 +64,6 @@ part 'engine/dom_renderer.dart';
part 'engine/engine_canvas.dart';
part 'engine/frame_reference.dart';
part 'engine/history.dart';
part 'engine/houdini_canvas.dart';
part 'engine/html_image_codec.dart';
part 'engine/keyboard.dart';
part 'engine/mouse_cursor.dart';
......
......@@ -193,9 +193,9 @@ class BitmapCanvas extends EngineCanvas {
///
/// See also:
///
/// * [PersistedStandardPicture._applyBitmapPaint] which uses this method to
/// * [PersistedPicture._applyBitmapPaint] which uses this method to
/// decide whether to reuse this canvas or not.
/// * [PersistedStandardPicture._recycleCanvas] which also uses this method
/// * [PersistedPicture._recycleCanvas] which also uses this method
/// for the same reason.
bool isReusable() {
return _devicePixelRatio == EngineWindow.browserDevicePixelRatio;
......
......@@ -169,8 +169,4 @@ class EngineColorFilter implements ui.ColorFilter {
return 'Unknown ColorFilter type. This is an error. If you\'re seeing this, please file an issue at https://github.com/flutter/flutter/issues/new.';
}
}
List<dynamic> webOnlySerializeToCssPaint() {
throw UnsupportedError('ColorFilter for CSS paint not yet supported');
}
}
......@@ -282,3 +282,127 @@ html.Element _drawParagraphElement(
}
return paragraphElement;
}
class _SaveElementStackEntry {
_SaveElementStackEntry({
required this.savedElement,
required this.transform,
});
final html.Element savedElement;
final Matrix4 transform;
}
/// Provides save stack tracking functionality to implementations of
/// [EngineCanvas].
mixin SaveElementStackTracking on EngineCanvas {
static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0);
final List<_SaveElementStackEntry> _saveStack = <_SaveElementStackEntry>[];
/// The element at the top of the element stack, or [rootElement] if the stack
/// is empty.
html.Element get currentElement =>
_elementStack.isEmpty ? rootElement : _elementStack.last;
/// The stack that maintains the DOM elements used to express certain paint
/// operations, such as clips.
final List<html.Element> _elementStack = <html.Element>[];
/// Pushes the [element] onto the element stack for the purposes of applying
/// a paint effect using a DOM element, e.g. for clipping.
///
/// The [restore] method automatically pops the element off the stack.
void pushElement(html.Element element) {
_elementStack.add(element);
}
/// Empties the save stack and the element stack, and resets the transform
/// and clip parameters.
///
/// Classes that override this method must call `super.clear()`.
@override
void clear() {
_saveStack.clear();
_elementStack.clear();
_currentTransform = Matrix4.identity();
}
/// The current transformation matrix.
Matrix4 get currentTransform => _currentTransform;
Matrix4 _currentTransform = Matrix4.identity();
/// Saves current clip and transform on the save stack.
///
/// Classes that override this method must call `super.save()`.
@override
void save() {
_saveStack.add(_SaveElementStackEntry(
savedElement: currentElement,
transform: _currentTransform.clone(),
));
}
/// Restores current clip and transform from the save stack.
///
/// Classes that override this method must call `super.restore()`.
@override
void restore() {
if (_saveStack.isEmpty) {
return;
}
final _SaveElementStackEntry entry = _saveStack.removeLast();
_currentTransform = entry.transform;
// Pop out of any clips.
while (currentElement != entry.savedElement) {
_elementStack.removeLast();
}
}
/// Multiplies the [currentTransform] matrix by a translation.
///
/// Classes that override this method must call `super.translate()`.
@override
void translate(double dx, double dy) {
_currentTransform.translate(dx, dy);
}
/// Scales the [currentTransform] matrix.
///
/// Classes that override this method must call `super.scale()`.
@override
void scale(double sx, double sy) {
_currentTransform.scale(sx, sy);
}
/// Rotates the [currentTransform] matrix.
///
/// Classes that override this method must call `super.rotate()`.
@override
void rotate(double radians) {
_currentTransform.rotate(_unitZ, radians);
}
/// Skews the [currentTransform] matrix.
///
/// Classes that override this method must call `super.skew()`.
@override
void skew(double sx, double sy) {
// DO NOT USE Matrix4.skew(sx, sy)! It treats sx and sy values as radians,
// but in our case they are transform matrix values.
final Matrix4 skewMatrix = Matrix4.identity();
final Float32List storage = skewMatrix.storage;
storage[1] = sy;
storage[4] = sx;
_currentTransform.multiply(skewMatrix);
}
/// Multiplies the [currentTransform] matrix by another matrix.
///
/// Classes that override this method must call `super.transform()`.
@override
void transform(Float32List matrix4) {
_currentTransform.multiply(Matrix4.fromFloat32List(matrix4));
}
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(yjbanov): optimization opportunities (see also houdini_painter.js)
// - collapse non-drawing paint operations
// - avoid producing DOM-based clips if there is no text
// - evaluate using stylesheets for static CSS properties
// - evaluate reusing houdini canvases
// @dart = 2.10
part of engine;
/// A canvas that renders to a combination of HTML DOM and CSS Custom Paint API.
///
/// This canvas produces paint commands for houdini_painter.js to apply. This
/// class must be kept in sync with houdini_painter.js.
class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking {
@override
final html.Element rootElement = html.Element.tag('flt-houdini');
/// The rectangle positioned relative to the parent layer's coordinate system
/// where this canvas paints.
///
/// Painting outside the bounds of this rectangle is cropped.
final ui.Rect? bounds;
HoudiniCanvas(this.bounds) {
// TODO(yjbanov): would it be faster to specify static values in a
// stylesheet and let the browser apply them?
rootElement.style
..position = 'absolute'
..top = '0'
..left = '0'
..width = '${bounds!.size.width}px'
..height = '${bounds!.size.height}px'
..backgroundImage = 'paint(flt)';
}
/// Prepare to reuse this canvas by clearing it's current contents.
@override
void clear() {
super.clear();
_serializedCommands = <List<dynamic>>[];
// TODO(yjbanov): we should measure if reusing old elements is beneficial.
domRenderer.clearDom(rootElement);
}
/// Paint commands serialized for sending to the CSS custom painter.
List<List<dynamic>> _serializedCommands = <List<dynamic>>[];
void apply(PaintCommand command) {
// Some commands are applied purely in HTML DOM and do not need to be
// serialized.
if (command is! PaintDrawParagraph &&
command is! PaintDrawImageRect &&
command is! PaintTransform) {
command.serializeToCssPaint(_serializedCommands);
}
command.apply(this);
}
/// Sends the paint commands to the CSS custom painter for painting.
void commit() {
if (_serializedCommands.isNotEmpty) {
rootElement.style.setProperty('--flt', json.encode(_serializedCommands));
} else {
rootElement.style.removeProperty('--flt');
}
}
@override
void clipRect(ui.Rect rect) {
final html.Element clip = html.Element.tag('flt-clip-rect');
final String cssTransform = matrix4ToCssTransform(
transformWithOffset(currentTransform, ui.Offset(rect.left, rect.top)));
clip.style
..overflow = 'hidden'
..position = 'absolute'
..transform = cssTransform
..width = '${rect.width}px'
..height = '${rect.height}px';
// The clipping element will translate the coordinate system as well, which
// is not what a clip should do. To offset that we translate in the opposite
// direction.
super.translate(-rect.left, -rect.top);
currentElement.append(clip);
pushElement(clip);
}
@override
void clipRRect(ui.RRect rrect) {
final ui.Rect outer = rrect.outerRect;
if (rrect.isRect) {
clipRect(outer);
return;
}
final html.Element clip = html.Element.tag('flt-clip-rrect');
final html.CssStyleDeclaration style = clip.style;
style
..overflow = 'hidden'
..position = 'absolute'
..transform = 'translate(${outer.left}px, ${outer.right}px)'
..width = '${outer.width}px'
..height = '${outer.height}px';
if (rrect.tlRadiusY == rrect.tlRadiusX) {
style.borderTopLeftRadius = '${rrect.tlRadiusX}px';
} else {
style.borderTopLeftRadius = '${rrect.tlRadiusX}px ${rrect.tlRadiusY}px';
}
if (rrect.trRadiusY == rrect.trRadiusX) {
style.borderTopRightRadius = '${rrect.trRadiusX}px';
} else {
style.borderTopRightRadius = '${rrect.trRadiusX}px ${rrect.trRadiusY}px';
}
if (rrect.brRadiusY == rrect.brRadiusX) {
style.borderBottomRightRadius = '${rrect.brRadiusX}px';
} else {
style.borderBottomRightRadius =
'${rrect.brRadiusX}px ${rrect.brRadiusY}px';
}
if (rrect.blRadiusY == rrect.blRadiusX) {
style.borderBottomLeftRadius = '${rrect.blRadiusX}px';
} else {
style.borderBottomLeftRadius =
'${rrect.blRadiusX}px ${rrect.blRadiusY}px';
}
// The clipping element will translate the coordinate system as well, which
// is not what a clip should do. To offset that we translate in the opposite
// direction.
super.translate(-rrect.left, -rrect.top);
currentElement.append(clip);
pushElement(clip);
}
@override
void clipPath(ui.Path path) {
// TODO(yjbanov): implement.
}
@override
void drawColor(ui.Color color, ui.BlendMode blendMode) {
// Drawn using CSS Paint.
}
@override
void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawPaint(SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawRect(ui.Rect rect, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawRRect(ui.RRect rrect, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawOval(ui.Rect rect, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawPath(ui.Path path, SurfacePaintData paint) {
// Drawn using CSS Paint.
}
@override
void drawShadow(ui.Path path, ui.Color color, double elevation,
bool transparentOccluder) {
// Drawn using CSS Paint.
}
@override
void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) {
// TODO(yjbanov): implement.
}
@override
void drawImageRect(
ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint) {
// TODO(yjbanov): implement src rectangle
final HtmlImage htmlImage = image as HtmlImage;
final html.Element imageBox = html.Element.tag('flt-img');
final String cssTransform = matrix4ToCssTransform(
transformWithOffset(currentTransform, ui.Offset(dst.left, dst.top)));
imageBox.style
..position = 'absolute'
..transformOrigin = '0 0 0'
..width = '${dst.width.toInt()}px'
..height = '${dst.height.toInt()}px'
..transform = cssTransform
..backgroundImage = 'url(${htmlImage.imgElement.src})'
..backgroundRepeat = 'norepeat'
..backgroundSize = '${dst.width}px ${dst.height}px';
currentElement.append(imageBox);
}
@override
void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {
final html.Element paragraphElement =
_drawParagraphElement(paragraph as EngineParagraph, offset, transform: currentTransform);
currentElement.append(paragraphElement);
}
@override
void drawVertices(
ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint) {
// TODO(flutter_web): implement.
}
@override
void drawPoints(ui.PointMode pointMode, Float32List points, SurfacePaintData paint) {
// TODO(flutter_web): implement.
}
@override
void endOfPaint() {}
}
class _SaveElementStackEntry {
_SaveElementStackEntry({
required this.savedElement,
required this.transform,
});
final html.Element savedElement;
final Matrix4 transform;
}
/// Provides save stack tracking functionality to implementations of
/// [EngineCanvas].
mixin SaveElementStackTracking on EngineCanvas {
static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0);
final List<_SaveElementStackEntry> _saveStack = <_SaveElementStackEntry>[];
/// The element at the top of the element stack, or [rootElement] if the stack
/// is empty.
html.Element get currentElement =>
_elementStack.isEmpty ? rootElement : _elementStack.last;
/// The stack that maintains the DOM elements used to express certain paint
/// operations, such as clips.
final List<html.Element> _elementStack = <html.Element>[];
/// Pushes the [element] onto the element stack for the purposes of applying
/// a paint effect using a DOM element, e.g. for clipping.
///
/// The [restore] method automatically pops the element off the stack.
void pushElement(html.Element element) {
_elementStack.add(element);
}
/// Empties the save stack and the element stack, and resets the transform
/// and clip parameters.
///
/// Classes that override this method must call `super.clear()`.
@override
void clear() {
_saveStack.clear();
_elementStack.clear();
_currentTransform = Matrix4.identity();
}
/// The current transformation matrix.
Matrix4 get currentTransform => _currentTransform;
Matrix4 _currentTransform = Matrix4.identity();
/// Saves current clip and transform on the save stack.
///
/// Classes that override this method must call `super.save()`.
@override
void save() {
_saveStack.add(_SaveElementStackEntry(
savedElement: currentElement,
transform: _currentTransform.clone(),
));
}
/// Restores current clip and transform from the save stack.
///
/// Classes that override this method must call `super.restore()`.
@override
void restore() {
if (_saveStack.isEmpty) {
return;
}
final _SaveElementStackEntry entry = _saveStack.removeLast();
_currentTransform = entry.transform;
// Pop out of any clips.
while (currentElement != entry.savedElement) {
_elementStack.removeLast();
}
}
/// Multiplies the [currentTransform] matrix by a translation.
///
/// Classes that override this method must call `super.translate()`.
@override
void translate(double dx, double dy) {
_currentTransform.translate(dx, dy);
}
/// Scales the [currentTransform] matrix.
///
/// Classes that override this method must call `super.scale()`.
@override
void scale(double sx, double sy) {
_currentTransform.scale(sx, sy);
}
/// Rotates the [currentTransform] matrix.
///
/// Classes that override this method must call `super.rotate()`.
@override
void rotate(double radians) {
_currentTransform.rotate(_unitZ, radians);
}
/// Skews the [currentTransform] matrix.
///
/// Classes that override this method must call `super.skew()`.
@override
void skew(double sx, double sy) {
// DO NOT USE Matrix4.skew(sx, sy)! It treats sx and sy values as radians,
// but in our case they are transform matrix values.
final Matrix4 skewMatrix = Matrix4.identity();
final Float32List storage = skewMatrix.storage;
storage[1] = sy;
storage[4] = sx;
_currentTransform.multiply(skewMatrix);
}
/// Multiplies the [currentTransform] matrix by another matrix.
///
/// Classes that override this method must call `super.transform()`.
@override
void transform(Float32List matrix4) {
_currentTransform.multiply(Matrix4.fromFloat32List(matrix4));
}
}
......@@ -7,7 +7,6 @@ part of engine;
/// Renders an RRect using path primitives.
abstract class _RRectRenderer {
// TODO(mdebbar): Backport the overlapping corners fix to houdini_painter.js
// To draw the rounded rectangle, perform the following steps:
// 0. Ensure border radius don't overlap
// 1. Flip left,right top,bottom since web doesn't support flipped
......
......@@ -1542,12 +1542,6 @@ class SurfacePath implements ui.Path {
ui.Rect? get webOnlyPathAsCircle =>
pathRef.isOval == -1 ? null : pathRef.getBounds();
/// Serializes this path to a value that's sent to a CSS custom painter for
/// painting.
List<dynamic> webOnlySerializeToCssPaint() {
throw UnimplementedError();
}
/// Returns if Path is empty.
/// Empty Path may have FillType but has no points, verbs or weights.
/// Constructor, reset and rewind makes SkPath empty.
......
......@@ -71,292 +71,9 @@ void _recycleCanvas(EngineCanvas? canvas) {
}
}
/// Signature of a function that instantiates a [PersistedPicture].
typedef PersistedPictureFactory = PersistedPicture Function(
double dx,
double dy,
ui.Picture picture,
int hints,
);
/// Function used by the [SceneBuilder] to instantiate a picture layer.
PersistedPictureFactory persistedPictureFactory = standardPictureFactory;
/// Instantiates an implementation of a picture layer that uses DOM, CSS, and
/// 2D canvas for painting.
PersistedStandardPicture standardPictureFactory(
double dx, double dy, ui.Picture picture, int hints) {
return PersistedStandardPicture(dx, dy, picture, hints);
}
/// Instantiates an implementation of a picture layer that uses CSS Paint API
/// (part of Houdini) for painting.
PersistedHoudiniPicture houdiniPictureFactory(
double dx, double dy, ui.Picture picture, int hints) {
return PersistedHoudiniPicture(dx, dy, picture, hints);
}
class PersistedHoudiniPicture extends PersistedPicture {
PersistedHoudiniPicture(double dx, double dy, ui.Picture picture, int hints)
: super(dx, dy, picture as EnginePicture, hints) {
if (!_cssPainterRegistered) {
_registerCssPainter();
}
}
static bool _cssPainterRegistered = false;
@override
double matchForUpdate(PersistedPicture existingSurface) {
// Houdini is display list-based so all pictures are cheap to repaint.
// However, if the picture hasn't changed at all then it's completely
// free.
return existingSurface.picture == picture ? 0.0 : 1.0;
}
static void _registerCssPainter() {
_cssPainterRegistered = true;
final dynamic css = js_util.getProperty(html.window, 'CSS');
final dynamic paintWorklet = js_util.getProperty(css, 'paintWorklet');
if (paintWorklet == null) {
html.window.console.warn(
'WARNING: CSS.paintWorklet not available. Paint worklets are only '
'supported on sites served from https:// or http://localhost.');
return;
}
js_util.callMethod(
paintWorklet,
'addModule',
<dynamic>[
'/packages/flutter_web_ui/assets/houdini_painter.js',
],
);
}
/// Houdini does not paint to bitmap.
@override
int get bitmapPixelCount => 0;
@override
void applyPaint(EngineCanvas? oldCanvas) {
_recycleCanvas(oldCanvas);
final HoudiniCanvas canvas = HoudiniCanvas(_optimalLocalCullRect);
_canvas = canvas;
domRenderer.clearDom(rootElement!);
rootElement!.append(_canvas!.rootElement);
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
canvas.commit();
}
}
class PersistedStandardPicture extends PersistedPicture {
PersistedStandardPicture(double dx, double dy, ui.Picture picture, int hints)
: super(dx, dy, picture as EnginePicture, hints);
@override
double matchForUpdate(PersistedStandardPicture existingSurface) {
if (existingSurface.picture == picture) {
// Picture is the same, return perfect score.
return 0.0;
}
if (!existingSurface.picture.recordingCanvas!.didDraw) {
// The previous surface didn't draw anything and therefore has no
// resources to reuse.
return 1.0;
}
final bool didRequireBitmap =
existingSurface.picture.recordingCanvas!.hasArbitraryPaint;
final bool requiresBitmap = picture.recordingCanvas!.hasArbitraryPaint;
if (didRequireBitmap != requiresBitmap) {
// Switching canvas types is always expensive.
return 1.0;
} else if (!requiresBitmap) {
// Currently DomCanvas is always expensive to repaint, as we always throw
// out all the DOM we rendered before. This may change in the future, at
// which point we may return other values here.
return 1.0;
} else {
final BitmapCanvas? oldCanvas = existingSurface._canvas as BitmapCanvas?;
if (oldCanvas == null) {
// We did not allocate a canvas last time. This can happen when the
// picture is completely clipped out of the view.
return 1.0;
} else if (!oldCanvas.doesFitBounds(_exactLocalCullRect!)) {
// The canvas needs to be resized before painting.
return 1.0;
} else {
final int newPixelCount = BitmapCanvas._widthToPhysical(_exactLocalCullRect!.width)
* BitmapCanvas._heightToPhysical(_exactLocalCullRect!.height);
final int oldPixelCount =
oldCanvas._widthInBitmapPixels * oldCanvas._heightInBitmapPixels;
if (oldPixelCount == 0) {
return 1.0;
}
final double pixelCountRatio = newPixelCount / oldPixelCount;
assert(0 <= pixelCountRatio && pixelCountRatio <= 1.0,
'Invalid pixel count ratio $pixelCountRatio');
return 1.0 - pixelCountRatio;
}
}
}
@override
Matrix4? get localTransformInverse => null;
@override
int get bitmapPixelCount {
if (_canvas is! BitmapCanvas) {
return 0;
}
final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas;
return bitmapCanvas.bitmapPixelCount;
}
@override
void applyPaint(EngineCanvas? oldCanvas) {
if (picture.recordingCanvas!.hasArbitraryPaint) {
_applyBitmapPaint(oldCanvas);
} else {
_applyDomPaint(oldCanvas);
}
}
void _applyDomPaint(EngineCanvas? oldCanvas) {
_recycleCanvas(oldCanvas);
_canvas = DomCanvas();
domRenderer.clearDom(rootElement!);
rootElement!.append(_canvas!.rootElement);
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
}
void _applyBitmapPaint(EngineCanvas? oldCanvas) {
if (oldCanvas is BitmapCanvas &&
oldCanvas.doesFitBounds(_optimalLocalCullRect!) &&
oldCanvas.isReusable()) {
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.keptCount++;
}
oldCanvas.bounds = _optimalLocalCullRect!;
_canvas = oldCanvas;
oldCanvas.setElementCache(_elementCache);
_canvas!.clear();
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
} else {
// We can't use the old canvas because the size has changed, so we put
// it in a cache for later reuse.
_recycleCanvas(oldCanvas);
// We cannot paint immediately because not all canvases that we may be
// able to reuse have been released yet. So instead we enqueue this
// picture to be painted after the update cycle is done syncing the layer
// tree then reuse canvases that were freed up.
_paintQueue.add(_PaintRequest(
canvasSize: _optimalLocalCullRect!.size,
paintCallback: () {
_canvas = _findOrCreateCanvas(_optimalLocalCullRect!);
assert(_canvas is BitmapCanvas
&& (_canvas as BitmapCanvas?)!._elementCache == _elementCache);
if (_debugExplainSurfaceStats) {
final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas;
_surfaceStatsFor(this).paintPixelCount +=
bitmapCanvas.bitmapPixelCount;
}
domRenderer.clearDom(rootElement!);
rootElement!.append(_canvas!.rootElement);
_canvas!.clear();
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
},
));
}
}
/// Attempts to reuse a canvas from the [_recycledCanvases]. Allocates a new
/// one if unable to reuse.
///
/// The best recycled canvas is one that:
///
/// - Fits the requested [canvasSize]. This is a hard requirement. Otherwise
/// we risk clipping the picture.
/// - Is the smallest among all possible reusable canvases. This makes canvas
/// reuse more efficient.
/// - Contains no more than twice the number of requested pixels. This makes
/// sure we do not use too much memory for small canvases.
BitmapCanvas _findOrCreateCanvas(ui.Rect bounds) {
final ui.Size canvasSize = bounds.size;
BitmapCanvas? bestRecycledCanvas;
double lastPixelCount = double.infinity;
for (int i = 0; i < _recycledCanvases.length; i++) {
final BitmapCanvas candidate = _recycledCanvases[i];
if (!candidate.isReusable()) {
continue;
}
final ui.Size candidateSize = candidate.size;
final double candidatePixelCount =
candidateSize.width * candidateSize.height;
final bool fits = candidate.doesFitBounds(bounds);
final bool isSmaller = candidatePixelCount < lastPixelCount;
if (fits && isSmaller) {
// [isTooSmall] is used to make sure that a small picture doesn't
// reuse and hold onto memory of a large canvas.
final double requestedPixelCount = bounds.width * bounds.height;
final bool isTooSmall = isSmaller &&
requestedPixelCount > 1 &&
(candidatePixelCount / requestedPixelCount) > 4;
if (!isTooSmall) {
bestRecycledCanvas = candidate;
lastPixelCount = candidatePixelCount;
final bool fitsExactly = candidateSize.width == canvasSize.width &&
candidateSize.height == canvasSize.height;
if (fitsExactly) {
// No need to keep looking any more.
break;
}
}
}
}
if (bestRecycledCanvas != null) {
if (_debugExplainSurfaceStats) {
_surfaceStatsFor(this).reuseCanvasCount++;
}
_recycledCanvases.remove(bestRecycledCanvas);
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.inRecycleCount =
_recycledCanvases.length;
}
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.reusedCount++;
}
bestRecycledCanvas.bounds = bounds;
bestRecycledCanvas.setElementCache(_elementCache);
return bestRecycledCanvas;
}
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.createdCount++;
}
final BitmapCanvas canvas = BitmapCanvas(bounds);
canvas.setElementCache(_elementCache);
if (_debugExplainSurfaceStats) {
_surfaceStatsFor(this)
..allocateBitmapCanvasCount += 1
..allocatedBitmapSizeInPixels =
canvas._widthInBitmapPixels * canvas._heightInBitmapPixels;
}
return canvas;
}
}
/// A surface that uses a combination of `<canvas>`, `<div>` and `<p>` elements
/// to draw shapes and text.
abstract class PersistedPicture extends PersistedLeafSurface {
class PersistedPicture extends PersistedLeafSurface {
PersistedPicture(this.dx, this.dy, this.picture, this.hints)
: localPaintBounds = picture.recordingCanvas!.pictureBounds;
......@@ -553,7 +270,14 @@ abstract class PersistedPicture extends PersistedLeafSurface {
///
/// If the implementation does not paint onto a bitmap canvas, it should
/// return zero.
int get bitmapPixelCount;
int get bitmapPixelCount {
if (_canvas is! BitmapCanvas) {
return 0;
}
final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas;
return bitmapCanvas.bitmapPixelCount;
}
void _applyPaint(PersistedPicture? oldSurface) {
final EngineCanvas? oldCanvas = oldSurface?._canvas;
......@@ -575,8 +299,193 @@ abstract class PersistedPicture extends PersistedLeafSurface {
applyPaint(oldCanvas);
}
/// Concrete implementations implement this method to do actual painting.
void applyPaint(EngineCanvas? oldCanvas);
@override
double matchForUpdate(PersistedPicture existingSurface) {
if (existingSurface.picture == picture) {
// Picture is the same, return perfect score.
return 0.0;
}
if (!existingSurface.picture.recordingCanvas!.didDraw) {
// The previous surface didn't draw anything and therefore has no
// resources to reuse.
return 1.0;
}
final bool didRequireBitmap =
existingSurface.picture.recordingCanvas!.hasArbitraryPaint;
final bool requiresBitmap = picture.recordingCanvas!.hasArbitraryPaint;
if (didRequireBitmap != requiresBitmap) {
// Switching canvas types is always expensive.
return 1.0;
} else if (!requiresBitmap) {
// Currently DomCanvas is always expensive to repaint, as we always throw
// out all the DOM we rendered before. This may change in the future, at
// which point we may return other values here.
return 1.0;
} else {
final BitmapCanvas? oldCanvas = existingSurface._canvas as BitmapCanvas?;
if (oldCanvas == null) {
// We did not allocate a canvas last time. This can happen when the
// picture is completely clipped out of the view.
return 1.0;
} else if (!oldCanvas.doesFitBounds(_exactLocalCullRect!)) {
// The canvas needs to be resized before painting.
return 1.0;
} else {
final int newPixelCount = BitmapCanvas._widthToPhysical(_exactLocalCullRect!.width)
* BitmapCanvas._heightToPhysical(_exactLocalCullRect!.height);
final int oldPixelCount =
oldCanvas._widthInBitmapPixels * oldCanvas._heightInBitmapPixels;
if (oldPixelCount == 0) {
return 1.0;
}
final double pixelCountRatio = newPixelCount / oldPixelCount;
assert(0 <= pixelCountRatio && pixelCountRatio <= 1.0,
'Invalid pixel count ratio $pixelCountRatio');
return 1.0 - pixelCountRatio;
}
}
}
@override
Matrix4? get localTransformInverse => null;
void applyPaint(EngineCanvas? oldCanvas) {
if (picture.recordingCanvas!.hasArbitraryPaint) {
_applyBitmapPaint(oldCanvas);
} else {
_applyDomPaint(oldCanvas);
}
}
void _applyDomPaint(EngineCanvas? oldCanvas) {
_recycleCanvas(oldCanvas);
_canvas = DomCanvas();
domRenderer.clearDom(rootElement!);
rootElement!.append(_canvas!.rootElement);
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
}
void _applyBitmapPaint(EngineCanvas? oldCanvas) {
if (oldCanvas is BitmapCanvas &&
oldCanvas.doesFitBounds(_optimalLocalCullRect!) &&
oldCanvas.isReusable()) {
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.keptCount++;
}
oldCanvas.bounds = _optimalLocalCullRect!;
_canvas = oldCanvas;
oldCanvas.setElementCache(_elementCache);
_canvas!.clear();
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
} else {
// We can't use the old canvas because the size has changed, so we put
// it in a cache for later reuse.
_recycleCanvas(oldCanvas);
// We cannot paint immediately because not all canvases that we may be
// able to reuse have been released yet. So instead we enqueue this
// picture to be painted after the update cycle is done syncing the layer
// tree then reuse canvases that were freed up.
_paintQueue.add(_PaintRequest(
canvasSize: _optimalLocalCullRect!.size,
paintCallback: () {
_canvas = _findOrCreateCanvas(_optimalLocalCullRect!);
assert(_canvas is BitmapCanvas
&& (_canvas as BitmapCanvas?)!._elementCache == _elementCache);
if (_debugExplainSurfaceStats) {
final BitmapCanvas bitmapCanvas = _canvas as BitmapCanvas;
_surfaceStatsFor(this).paintPixelCount +=
bitmapCanvas.bitmapPixelCount;
}
domRenderer.clearDom(rootElement!);
rootElement!.append(_canvas!.rootElement);
_canvas!.clear();
picture.recordingCanvas!.apply(_canvas, _optimalLocalCullRect);
},
));
}
}
/// Attempts to reuse a canvas from the [_recycledCanvases]. Allocates a new
/// one if unable to reuse.
///
/// The best recycled canvas is one that:
///
/// - Fits the requested [canvasSize]. This is a hard requirement. Otherwise
/// we risk clipping the picture.
/// - Is the smallest among all possible reusable canvases. This makes canvas
/// reuse more efficient.
/// - Contains no more than twice the number of requested pixels. This makes
/// sure we do not use too much memory for small canvases.
BitmapCanvas _findOrCreateCanvas(ui.Rect bounds) {
final ui.Size canvasSize = bounds.size;
BitmapCanvas? bestRecycledCanvas;
double lastPixelCount = double.infinity;
for (int i = 0; i < _recycledCanvases.length; i++) {
final BitmapCanvas candidate = _recycledCanvases[i];
if (!candidate.isReusable()) {
continue;
}
final ui.Size candidateSize = candidate.size;
final double candidatePixelCount =
candidateSize.width * candidateSize.height;
final bool fits = candidate.doesFitBounds(bounds);
final bool isSmaller = candidatePixelCount < lastPixelCount;
if (fits && isSmaller) {
// [isTooSmall] is used to make sure that a small picture doesn't
// reuse and hold onto memory of a large canvas.
final double requestedPixelCount = bounds.width * bounds.height;
final bool isTooSmall = isSmaller &&
requestedPixelCount > 1 &&
(candidatePixelCount / requestedPixelCount) > 4;
if (!isTooSmall) {
bestRecycledCanvas = candidate;
lastPixelCount = candidatePixelCount;
final bool fitsExactly = candidateSize.width == canvasSize.width &&
candidateSize.height == canvasSize.height;
if (fitsExactly) {
// No need to keep looking any more.
break;
}
}
}
}
if (bestRecycledCanvas != null) {
if (_debugExplainSurfaceStats) {
_surfaceStatsFor(this).reuseCanvasCount++;
}
_recycledCanvases.remove(bestRecycledCanvas);
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.inRecycleCount =
_recycledCanvases.length;
}
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.reusedCount++;
}
bestRecycledCanvas.bounds = bounds;
bestRecycledCanvas.setElementCache(_elementCache);
return bestRecycledCanvas;
}
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.createdCount++;
}
final BitmapCanvas canvas = BitmapCanvas(bounds);
canvas.setElementCache(_elementCache);
if (_debugExplainSurfaceStats) {
_surfaceStatsFor(this)
..allocateBitmapCanvasCount += 1
..allocatedBitmapSizeInPixels =
canvas._widthInBitmapPixels * canvas._heightInBitmapPixels;
}
return canvas;
}
void _applyTranslate() {
rootElement!.style.transform = 'translate(${dx}px, ${dy}px)';
......
......@@ -608,8 +608,6 @@ abstract class PaintCommand {
const PaintCommand();
void apply(EngineCanvas? canvas);
void serializeToCssPaint(List<List<dynamic>> serializedCommands);
}
/// A [PaintCommand] that affect pixels on the screen (unlike, for example, the
......@@ -665,11 +663,6 @@ class PaintSave extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(const <int>[1]);
}
}
class PaintRestore extends PaintCommand {
......@@ -688,11 +681,6 @@ class PaintRestore extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(const <int>[2]);
}
}
class PaintTranslate extends PaintCommand {
......@@ -714,11 +702,6 @@ class PaintTranslate extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<num>[3, dx, dy]);
}
}
class PaintScale extends PaintCommand {
......@@ -740,11 +723,6 @@ class PaintScale extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<num>[4, sx, sy]);
}
}
class PaintRotate extends PaintCommand {
......@@ -765,11 +743,6 @@ class PaintRotate extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<num>[5, radians]);
}
}
class PaintTransform extends PaintCommand {
......@@ -790,11 +763,6 @@ class PaintTransform extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[6]..addAll(matrix4));
}
}
class PaintSkew extends PaintCommand {
......@@ -816,11 +784,6 @@ class PaintSkew extends PaintCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<num>[7, sx, sy]);
}
}
class PaintClipRect extends DrawCommand {
......@@ -841,11 +804,6 @@ class PaintClipRect extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[8, _serializeRectToCssPaint(rect)]);
}
}
class PaintClipRRect extends DrawCommand {
......@@ -866,14 +824,6 @@ class PaintClipRRect extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
9,
_serializeRRectToCssPaint(rrect),
]);
}
}
class PaintClipPath extends DrawCommand {
......@@ -894,11 +844,6 @@ class PaintClipPath extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[10, path.webOnlySerializeToCssPaint()]);
}
}
class PaintDrawColor extends DrawCommand {
......@@ -920,12 +865,6 @@ class PaintDrawColor extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands
.add(<dynamic>[11, colorToCssString(color), blendMode.index]);
}
}
class PaintDrawLine extends DrawCommand {
......@@ -948,18 +887,6 @@ class PaintDrawLine extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
12,
p1.dx,
p1.dy,
p2.dx,
p2.dy,
_serializePaintToCssPaint(paint)
]);
}
}
class PaintDrawPaint extends DrawCommand {
......@@ -980,11 +907,6 @@ class PaintDrawPaint extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[13, _serializePaintToCssPaint(paint)]);
}
}
class PaintDrawVertices extends DrawCommand {
......@@ -1006,11 +928,6 @@ class PaintDrawVertices extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
throw UnimplementedError();
}
}
class PaintDrawPoints extends DrawCommand {
......@@ -1032,11 +949,6 @@ class PaintDrawPoints extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
throw UnimplementedError();
}
}
class PaintDrawRect extends DrawCommand {
......@@ -1058,15 +970,6 @@ class PaintDrawRect extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
14,
_serializeRectToCssPaint(rect),
_serializePaintToCssPaint(paint)
]);
}
}
class PaintDrawRRect extends DrawCommand {
......@@ -1088,15 +991,6 @@ class PaintDrawRRect extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
15,
_serializeRRectToCssPaint(rrect),
_serializePaintToCssPaint(paint),
]);
}
}
class PaintDrawDRRect extends DrawCommand {
......@@ -1125,16 +1019,6 @@ class PaintDrawDRRect extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
16,
_serializeRRectToCssPaint(outer),
_serializeRRectToCssPaint(inner),
_serializePaintToCssPaint(paint),
]);
}
}
class PaintDrawOval extends DrawCommand {
......@@ -1156,15 +1040,6 @@ class PaintDrawOval extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
17,
_serializeRectToCssPaint(rect),
_serializePaintToCssPaint(paint),
]);
}
}
class PaintDrawCircle extends DrawCommand {
......@@ -1187,17 +1062,6 @@ class PaintDrawCircle extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
18,
c.dx,
c.dy,
radius,
_serializePaintToCssPaint(paint),
]);
}
}
class PaintDrawPath extends DrawCommand {
......@@ -1219,15 +1083,6 @@ class PaintDrawPath extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
19,
path.webOnlySerializeToCssPaint(),
_serializePaintToCssPaint(paint),
]);
}
}
class PaintDrawShadow extends DrawCommand {
......@@ -1252,22 +1107,6 @@ class PaintDrawShadow extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[
20,
path.webOnlySerializeToCssPaint(),
<dynamic>[
color.alpha,
color.red,
color.green,
color.blue,
],
elevation,
transparentOccluder,
]);
}
}
class PaintDrawImage extends DrawCommand {
......@@ -1290,13 +1129,6 @@ class PaintDrawImage extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
if (assertionsEnabled) {
throw UnsupportedError('drawImage not serializable');
}
}
}
class PaintDrawImageRect extends DrawCommand {
......@@ -1320,13 +1152,6 @@ class PaintDrawImageRect extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
if (assertionsEnabled) {
throw UnsupportedError('drawImageRect not serializable');
}
}
}
class PaintDrawParagraph extends DrawCommand {
......@@ -1348,55 +1173,6 @@ class PaintDrawParagraph extends DrawCommand {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
if (assertionsEnabled) {
throw UnsupportedError('drawParagraph not serializable');
}
}
}
List<dynamic> _serializePaintToCssPaint(SurfacePaintData paint) {
final EngineGradient? engineShader = paint.shader as EngineGradient?;
return <dynamic>[
paint.blendMode?.index,
paint.style?.index,
paint.strokeWidth,
paint.strokeCap?.index,
paint.isAntiAlias,
colorToCssString(paint.color),
engineShader?.webOnlySerializeToCssPaint(),
paint.maskFilter?.webOnlySerializeToCssPaint(),
paint.filterQuality?.index,
paint.colorFilter?.webOnlySerializeToCssPaint(),
];
}
List<dynamic> _serializeRectToCssPaint(ui.Rect rect) {
return <dynamic>[
rect.left,
rect.top,
rect.right,
rect.bottom,
];
}
List<dynamic> _serializeRRectToCssPaint(ui.RRect rrect) {
return <dynamic>[
rrect.left,
rrect.top,
rrect.right,
rrect.bottom,
rrect.tlRadiusX,
rrect.tlRadiusY,
rrect.trRadiusX,
rrect.trRadiusY,
rrect.brRadiusX,
rrect.brRadiusY,
rrect.blRadiusX,
rrect.blRadiusY,
];
}
class Subpath {
......@@ -1421,14 +1197,6 @@ class Subpath {
return result;
}
List<dynamic> serializeToCssPaint() {
final List<dynamic> serialization = <dynamic>[];
for (int i = 0; i < commands.length; i++) {
serialization.add(commands[i].serializeToCssPaint());
}
return serialization;
}
@override
String toString() {
if (assertionsEnabled) {
......@@ -1439,26 +1207,11 @@ class Subpath {
}
}
/// ! Houdini implementation relies on indices here. Keep in sync.
class PathCommandTypes {
static const int moveTo = 0;
static const int lineTo = 1;
static const int ellipse = 2;
static const int close = 3;
static const int quadraticCurveTo = 4;
static const int bezierCurveTo = 5;
static const int rect = 6;
static const int rRect = 7;
}
abstract class PathCommand {
final int type;
const PathCommand(this.type);
const PathCommand();
PathCommand shifted(ui.Offset offset);
List<dynamic> serializeToCssPaint();
/// Transform the command and add to targetPath.
void transform(Float32List matrix4, SurfacePath targetPath);
......@@ -1472,18 +1225,13 @@ class MoveTo extends PathCommand {
final double x;
final double y;
const MoveTo(this.x, this.y) : super(PathCommandTypes.moveTo);
const MoveTo(this.x, this.y);
@override
MoveTo shifted(ui.Offset offset) {
return MoveTo(x + offset.dx, y + offset.dy);
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[1, x, y];
}
@override
void transform(Float32List matrix4, ui.Path targetPath) {
final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4);
......@@ -1504,18 +1252,13 @@ class LineTo extends PathCommand {
final double x;
final double y;
const LineTo(this.x, this.y) : super(PathCommandTypes.lineTo);
const LineTo(this.x, this.y);
@override
LineTo shifted(ui.Offset offset) {
return LineTo(x + offset.dx, y + offset.dy);
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[2, x, y];
}
@override
void transform(Float32List matrix4, ui.Path targetPath) {
final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4);
......@@ -1543,8 +1286,7 @@ class Ellipse extends PathCommand {
final bool anticlockwise;
const Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation,
this.startAngle, this.endAngle, this.anticlockwise)
: super(PathCommandTypes.ellipse);
this.startAngle, this.endAngle, this.anticlockwise);
@override
Ellipse shifted(ui.Offset offset) {
......@@ -1552,21 +1294,6 @@ class Ellipse extends PathCommand {
startAngle, endAngle, anticlockwise);
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[
3,
x,
y,
radiusX,
radiusY,
rotation,
startAngle,
endAngle,
anticlockwise,
];
}
@override
void transform(Float32List matrix4, SurfacePath targetPath) {
final ui.Path bezierPath = ui.Path();
......@@ -1686,8 +1413,7 @@ class QuadraticCurveTo extends PathCommand {
final double x2;
final double y2;
const QuadraticCurveTo(this.x1, this.y1, this.x2, this.y2)
: super(PathCommandTypes.quadraticCurveTo);
const QuadraticCurveTo(this.x1, this.y1, this.x2, this.y2);
@override
QuadraticCurveTo shifted(ui.Offset offset) {
......@@ -1695,11 +1421,6 @@ class QuadraticCurveTo extends PathCommand {
x1 + offset.dx, y1 + offset.dy, x2 + offset.dx, y2 + offset.dy);
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[4, x1, y1, x2, y2];
}
@override
void transform(Float32List matrix4, ui.Path targetPath) {
final double m0 = matrix4[0];
......@@ -1734,8 +1455,7 @@ class BezierCurveTo extends PathCommand {
final double x3;
final double y3;
const BezierCurveTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3)
: super(PathCommandTypes.bezierCurveTo);
const BezierCurveTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3);
@override
BezierCurveTo shifted(ui.Offset offset) {
......@@ -1743,11 +1463,6 @@ class BezierCurveTo extends PathCommand {
y2 + offset.dy, x3 + offset.dx, y3 + offset.dy);
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[5, x1, y1, x2, y2, x3, y3];
}
@override
void transform(Float32List matrix4, ui.Path targetPath) {
final double s0 = matrix4[0];
......@@ -1782,8 +1497,7 @@ class RectCommand extends PathCommand {
final double width;
final double height;
const RectCommand(this.x, this.y, this.width, this.height)
: super(PathCommandTypes.rect);
const RectCommand(this.x, this.y, this.width, this.height);
@override
RectCommand shifted(ui.Offset offset) {
......@@ -1824,11 +1538,6 @@ class RectCommand extends PathCommand {
}
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[6, x, y, width, height];
}
@override
String toString() {
if (assertionsEnabled) {
......@@ -1842,18 +1551,13 @@ class RectCommand extends PathCommand {
class RRectCommand extends PathCommand {
final ui.RRect rrect;
const RRectCommand(this.rrect) : super(PathCommandTypes.rRect);
const RRectCommand(this.rrect);
@override
RRectCommand shifted(ui.Offset offset) {
return RRectCommand(rrect.shift(offset));
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[7, _serializeRRectToCssPaint(rrect)];
}
@override
void transform(Float32List matrix4, SurfacePath targetPath) {
final ui.Path roundRectPath = ui.Path();
......@@ -1872,18 +1576,11 @@ class RRectCommand extends PathCommand {
}
class CloseCommand extends PathCommand {
const CloseCommand() : super(PathCommandTypes.close);
@override
CloseCommand shifted(ui.Offset offset) {
return this;
}
@override
List<dynamic> serializeToCssPaint() {
return <dynamic>[8];
}
@override
void transform(Float32List matrix4, ui.Path targetPath) {
targetPath.close();
......
......@@ -363,7 +363,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
if (willChangeHint) {
hints |= 2;
}
_addSurface(persistedPictureFactory(offset.dx, offset.dy, picture, hints));
_addSurface(PersistedPicture(offset.dx, offset.dy, picture as EnginePicture, hints));
}
/// Adds a backend texture to the scene.
......
......@@ -11,10 +11,6 @@ abstract class EngineGradient implements ui.Gradient {
/// Creates a fill style to be used in painting.
Object createPaintStyle(html.CanvasRenderingContext2D? ctx);
List<dynamic> webOnlySerializeToCssPaint() {
throw UnsupportedError('CSS paint not implemented for this shader type');
}
}
class GradientSweep extends EngineGradient {
......@@ -100,24 +96,6 @@ class GradientLinear extends EngineGradient {
}
return gradient;
}
@override
List<dynamic> webOnlySerializeToCssPaint() {
final List<dynamic> serializedColors = <dynamic>[];
for (int i = 0; i < colors.length; i++) {
serializedColors.add(colorToCssString(colors[i]));
}
return <dynamic>[
1,
from.dx,
from.dy,
to.dx,
to.dy,
serializedColors,
colorStops,
tileMode.index
];
}
}
// TODO(flutter_web): For transforms and tile modes implement as webgl
......
......@@ -237,7 +237,7 @@ void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) {
elementReuseCount += stats.reuseElementCount;
totalAllocatedDomNodeCount += stats.allocatedDomNodeCount;
if (surface is PersistedStandardPicture) {
if (surface is PersistedPicture) {
pictureCount += 1;
paintCount += stats.paintCount;
......
......@@ -1323,10 +1323,6 @@ abstract class ColorFilter {
/// to the RGB channels.
const factory ColorFilter.srgbToLinearGamma() =
engine.EngineColorFilter.srgbToLinearGamma;
List<dynamic> webOnlySerializeToCssPaint() {
throw UnsupportedError('ColorFilter for CSS paint not yet supported');
}
}
/// Styles to use for blurs in [MaskFilter] objects.
......@@ -1402,10 +1398,6 @@ class MaskFilter {
@override
int get hashCode => hashValues(_style, _sigma);
List<dynamic> webOnlySerializeToCssPaint() {
return <dynamic>[_style.index, _sigma];
}
@override
String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})';
}
......
......@@ -26,7 +26,6 @@ void main() {
test(description, () {
testFn(BitmapCanvas(canvasSize));
testFn(DomCanvas());
testFn(HoudiniCanvas(canvasSize));
testFn(mockCanvas = MockEngineCanvas());
if (whenDone != null) {
whenDone();
......
......@@ -133,7 +133,7 @@ void _testCullRectComputation() {
});
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(0, 0, 500, 100));
}, skip: '''TODO(https://github.com/flutter/flutter/issues/40395)
Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500''');
......@@ -147,7 +147,7 @@ void _testCullRectComputation() {
});
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(0, 0, 20, 20));
});
......@@ -161,7 +161,7 @@ void _testCullRectComputation() {
});
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, Rect.zero);
expect(picture.debugExactGlobalCullRect, Rect.zero);
});
......@@ -176,7 +176,7 @@ void _testCullRectComputation() {
});
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 60, 60));
});
......@@ -195,7 +195,7 @@ void _testCullRectComputation() {
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 60, 60));
});
......@@ -213,7 +213,7 @@ void _testCullRectComputation() {
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(
picture.debugExactGlobalCullRect, const Rect.fromLTRB(0, 70, 20, 100));
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(0, -20, 20, 10));
......@@ -244,7 +244,7 @@ void _testCullRectComputation() {
await matchGoldenFile('compositing_cull_rect_fills_layer_clip.png',
region: region);
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(40, 40, 70, 70));
});
......@@ -274,7 +274,7 @@ void _testCullRectComputation() {
'compositing_cull_rect_intersects_clip_and_paint_bounds.png',
region: region);
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, const Rect.fromLTRB(50, 40, 70, 70));
});
......@@ -305,7 +305,7 @@ void _testCullRectComputation() {
await matchGoldenFile('compositing_cull_rect_offset_inside_layer_clip.png',
region: region);
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect,
const Rect.fromLTRB(-15.0, -20.0, 15.0, 0.0));
});
......@@ -335,7 +335,7 @@ void _testCullRectComputation() {
builder.build();
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(picture.optimalLocalCullRect, Rect.zero);
expect(picture.debugExactGlobalCullRect, Rect.zero);
});
......@@ -378,7 +378,7 @@ void _testCullRectComputation() {
await matchGoldenFile('compositing_cull_rect_rotated.png', region: region);
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
expect(
picture.optimalLocalCullRect,
within(
......@@ -510,7 +510,7 @@ void _testCullRectComputation() {
await matchGoldenFile('compositing_3d_rotate1.png', region: region);
// ignore: unused_local_variable
final PersistedStandardPicture picture = enumeratePictures().single;
final PersistedPicture picture = enumeratePictures().single;
// TODO(https://github.com/flutter/flutter/issues/40395):
// Needs ability to set iframe to 500,100 size. Current screen seems to be 500,500.
// expect(
......
......@@ -91,7 +91,7 @@ typedef CanvasTest = FutureOr<void> Function(EngineCanvas canvas);
/// Runs the given test [body] with each type of canvas.
void testEachCanvas(String description, CanvasTest body,
{double maxDiffRate, bool bSkipHoudini = false}) {
{double maxDiffRate}) {
const ui.Rect bounds = ui.Rect.fromLTWH(0, 0, 600, 800);
test('$description (bitmap)', () {
try {
......@@ -123,18 +123,6 @@ void testEachCanvas(String description, CanvasTest body,
TextMeasurementService.clearCache();
}
});
if (!bSkipHoudini) {
test('$description (houdini)', () {
try {
TextMeasurementService.initialize(rulerCacheCapacity: 2);
WebExperiments.instance.useCanvasText = false;
return body(HoudiniCanvas(bounds));
} finally {
WebExperiments.instance.useCanvasText = null;
TextMeasurementService.clearCache();
}
});
}
}
final ui.TextStyle _defaultTextStyle = ui.TextStyle(
......
......@@ -30,7 +30,7 @@ void main() async {
recordingCanvas.endRecording();
recordingCanvas.apply(canvas, screenRect);
return scuba.diffCanvasScreenshot(canvas, 'text_with_placeholders');
}, bSkipHoudini: true);
});
}
const Color black = Color(0xFF000000);
......
......@@ -220,7 +220,7 @@ void main() async {
testEachCanvas('draws text with a shadow', (EngineCanvas canvas) {
drawTextWithShadow(canvas);
return scuba.diffCanvasScreenshot(canvas, 'text_shadow', maxDiffRatePercent: 0.2);
}, bSkipHoudini: true);
});
testEachCanvas('Handles disabled strut style', (EngineCanvas canvas) {
// Flutter uses [StrutStyle.disabled] for the [SelectableText] widget. This
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册