Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web] Update CanvasPool documentation #27823

Merged
merged 8 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 84 additions & 37 deletions lib/web_ui/lib/src/engine/canvas_pool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,37 @@ class CanvasPool extends _SaveStackTracking {
// List of canvases available to reuse from prior paint cycle.
List<html.CanvasElement>? _reusablePool;
// Current canvas element or null if marked for lazy allocation.
html.CanvasElement? get canvas => _canvas;
html.CanvasElement? _canvas;

html.HtmlElement? _rootElement;
int _saveContextCount = 0;
final double _density;

/// Initializes canvas pool for target size and dpi.
CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels,
this._density);

/// Initializes canvas pool to be hosted on a surface.
void mount(html.HtmlElement rootElement) {
_rootElement = rootElement;
}

/// Sets the translate transform to be applied to canvas to compensate for
/// pixel padding applied to hosting [BitmapCanvas].
///
/// Should be called during initialization after [CanvasPool] is mounted.
set initialTransform(ui.Offset transform) {
translate(transform.dx, transform.dy);
}

/// Returns true if no canvas has been allocated yet.
bool get isEmpty => _canvas == null;

/// Returns true if a canvas has been allocated for use.
bool get isNotEmpty => _canvas != null;


/// Returns [CanvasRenderingContext2D] api to draw into this canvas.
html.CanvasRenderingContext2D get context {
html.CanvasRenderingContext2D? ctx = _context;
if (ctx == null) {
Expand All @@ -75,6 +96,8 @@ class CanvasPool extends _SaveStackTracking {
return ctx;
}

/// Returns [ContextStateHandle] API to efficiently update state of
/// drawing context.
ContextStateHandle get contextHandle {
if (_canvas == null) {
_createCanvas();
Expand All @@ -84,11 +107,11 @@ class CanvasPool extends _SaveStackTracking {
return _contextHandle!;
}

// Prevents active canvas to be used for rendering and prepares a new
// canvas allocation on next drawing request that will require one.
//
// Saves current canvas so we can dispose
// and replay the clip/transform stack on top of new canvas.
/// Prevents active canvas to be used for rendering and prepares a new
/// canvas allocation on next drawing request that will require one.
///
/// Saves current canvas so we can dispose
/// and replay the clip/transform stack on top of new canvas.
void closeCurrentCanvas() {
assert(_rootElement != null);
// Place clean copy of current canvas with context stack restored and paint
Expand All @@ -104,10 +127,6 @@ class CanvasPool extends _SaveStackTracking {
}
}

void allocateCanvas(html.HtmlElement rootElement) {
_rootElement = rootElement;
}

void _createCanvas() {
bool requiresClearRect = false;
bool reused = false;
Expand Down Expand Up @@ -227,11 +246,6 @@ class CanvasPool extends _SaveStackTracking {
}
}
reuse();
resetTransform();
}

set initialTransform(ui.Offset transform) {
translate(transform.dx, transform.dy);
}

int _replaySingleSaveEntry(int clipDepth, Matrix4 prevTransform,
Expand Down Expand Up @@ -311,7 +325,7 @@ class CanvasPool extends _SaveStackTracking {
clipDepth, prevTransform, _currentTransform, clipStack);
}

// Marks this pool for reuse.
/// Marks this pool for reuse.
void reuse() {
if (_canvas != null) {
_restoreContextSave();
Expand All @@ -326,8 +340,11 @@ class CanvasPool extends _SaveStackTracking {
_canvas = null;
_context = null;
_contextHandle = null;
_resetTransform();
}

/// Signals to canvas pool the end of drawing commands so cached resources
/// that are reused from last instance can be cleanup.
void endOfPaint() {
if (_reusablePool != null) {
for (final html.CanvasElement e in _reusablePool!) {
Expand Down Expand Up @@ -375,16 +392,16 @@ class CanvasPool extends _SaveStackTracking {
double get dpi =>
EnginePlatformDispatcher.browserDevicePixelRatio * _density;

void resetTransform() {
void _resetTransform() {
final html.CanvasElement? canvas = _canvas;
if (canvas != null) {
canvas.style.transformOrigin = '';
canvas.style.transform = '';
}
}

// Returns a "data://" URI containing a representation of the image in this
// canvas in PNG format.
/// Returns a "data://" URI containing a representation of the image in this
/// canvas in PNG format.
String toDataUrl() => _canvas?.toDataUrl() ?? '';

@override
Expand Down Expand Up @@ -518,6 +535,7 @@ class CanvasPool extends _SaveStackTracking {
}
}

/// Fill a virtually infinite rect with a color and optional blendMode.
void drawColor(ui.Color color, ui.BlendMode blendMode) {
final html.CanvasRenderingContext2D ctx = context;
contextHandle.blendMode = blendMode;
Expand All @@ -531,7 +549,7 @@ class CanvasPool extends _SaveStackTracking {
ctx.fillRect(-10000, -10000, 20000, 20000);
}

// Fill a virtually infinite rect with the color.
/// Fill a virtually infinite rect with the color.
void fill() {
final html.CanvasRenderingContext2D ctx = context;
ctx.beginPath();
Expand All @@ -540,6 +558,7 @@ class CanvasPool extends _SaveStackTracking {
ctx.fillRect(-10000, -10000, 20000, 20000);
}

/// Draws a line from [p1] to [p2].
void strokeLine(ui.Offset p1, ui.Offset p2) {
final html.CanvasRenderingContext2D ctx = context;
ctx.beginPath();
Expand All @@ -554,6 +573,8 @@ class CanvasPool extends _SaveStackTracking {
ctx.stroke();
}

/// Draws a set of points with given radius, lines between points or
/// a polygon.
void drawPoints(ui.PointMode pointMode, Float32List points, double radius) {
final html.CanvasRenderingContext2D ctx = context;
final int len = points.length;
Expand Down Expand Up @@ -634,6 +655,19 @@ class CanvasPool extends _SaveStackTracking {
}
}

/// Draws a rectangle filled or stroked based on [style].
void drawRect(ui.Rect rect, ui.PaintingStyle? style) {
context.beginPath();
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
if (shaderBounds == null) {
context.rect(rect.left, rect.top, rect.width, rect.height);
} else {
context.rect(rect.left - shaderBounds.left, rect.top - shaderBounds.top,
rect.width, rect.height);
}
contextHandle.paint(style);
}

/// Applies path to drawing context, preparing for fill and other operations.
///
/// WARNING: Don't refactor _runPath/_runPathWithOffset. Latency sensitive
Expand Down Expand Up @@ -682,18 +716,7 @@ class CanvasPool extends _SaveStackTracking {
}
}

void drawRect(ui.Rect rect, ui.PaintingStyle? style) {
context.beginPath();
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
if (shaderBounds == null) {
context.rect(rect.left, rect.top, rect.width, rect.height);
} else {
context.rect(rect.left - shaderBounds.left, rect.top - shaderBounds.top,
rect.width, rect.height);
}
contextHandle.paint(style);
}

/// Draws a rounded rectangle filled or stroked based on [style].
void drawRRect(ui.RRect roundRect, ui.PaintingStyle? style) {
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
RRectToCanvasRenderer(context).render(
Expand All @@ -702,6 +725,9 @@ class CanvasPool extends _SaveStackTracking {
contextHandle.paint(style);
}

/// Fills or strokes the area between [outer] and [inner] rounded rectangles.
///
/// Typically used to draw a thick round border.
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintingStyle? style) {
final RRectRenderer renderer = RRectToCanvasRenderer(context);
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
Expand All @@ -716,6 +742,7 @@ class CanvasPool extends _SaveStackTracking {
contextHandle.paint(style);
}

/// Draws an axis-aligned oval that fills the given axis-aligned rectangle.
void drawOval(ui.Rect rect, ui.PaintingStyle? style) {
context.beginPath();
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
Expand All @@ -728,6 +755,7 @@ class CanvasPool extends _SaveStackTracking {
contextHandle.paint(style);
}

/// Draws a circle centered at [c] with [radius].
void drawCircle(ui.Offset c, double radius, ui.PaintingStyle? style) {
context.beginPath();
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
Expand All @@ -737,6 +765,7 @@ class CanvasPool extends _SaveStackTracking {
contextHandle.paint(style);
}

/// Draws or strokes a path based on [style] and current context state.
void drawPath(ui.Path path, ui.PaintingStyle? style) {
final ui.Rect? shaderBounds = contextHandle._shaderBounds;
if (shaderBounds == null) {
Expand All @@ -748,6 +777,7 @@ class CanvasPool extends _SaveStackTracking {
contextHandle.paintPath(style, path.fillType);
}

/// Draws a shadow for a Path representing the given material elevation.
void drawShadow(ui.Path path, ui.Color color, double elevation,
bool transparentOccluder) {
final SurfaceShadowData? shadow = computeShadow(path.getBounds(), elevation);
Expand Down Expand Up @@ -810,6 +840,11 @@ class CanvasPool extends _SaveStackTracking {
}
}

/// Disposes html canvas element(s) used by this pool when persistent surface
/// is disposed.
///
/// When this pool is reused, [clear] is called instead to be able to
/// draw using existing canvas elements.
void dispose() {
// Webkit has a threshold for the amount of canvas pixels an app can
// allocate. Even though our canvases are being garbage-collected as
Expand All @@ -836,16 +871,18 @@ class CanvasPool extends _SaveStackTracking {
}
}

// Optimizes applying paint parameters to html canvas.
//
// See https://www.w3.org/TR/2dcontext/ for defaults used in this class
// to initialize current values.
//
/// Optimizes applying paint parameters to html canvas.
///
/// See https://www.w3.org/TR/2dcontext/ for defaults used in this class
/// to initialize current values.
class ContextStateHandle {
/// Associated canvas element context tracked by this context state.
final html.CanvasRenderingContext2D context;
final CanvasPool _canvasPool;
/// Dpi of context.
final double density;

/// Initializes context state for a [CanvasPool].
ContextStateHandle(this._canvasPool, this.context, this.density);
ui.BlendMode? _currentBlendMode = ui.BlendMode.srcOver;
ui.StrokeCap? _currentStrokeCap = ui.StrokeCap.butt;
Expand All @@ -856,6 +893,7 @@ class ContextStateHandle {
Object? _currentStrokeStyle;
double _currentLineWidth = 1.0;

/// See [html.CanvasRenderingContext2D].
set blendMode(ui.BlendMode? blendMode) {
if (blendMode != _currentBlendMode) {
_currentBlendMode = blendMode;
Expand All @@ -864,6 +902,7 @@ class ContextStateHandle {
}
}

/// See [html.CanvasRenderingContext2D].
set strokeCap(ui.StrokeCap? strokeCap) {
strokeCap ??= ui.StrokeCap.butt;
if (strokeCap != _currentStrokeCap) {
Expand All @@ -872,13 +911,15 @@ class ContextStateHandle {
}
}

/// See [html.CanvasRenderingContext2D].
set lineWidth(double lineWidth) {
if (lineWidth != _currentLineWidth) {
_currentLineWidth = lineWidth;
context.lineWidth = lineWidth;
}
}

/// See [html.CanvasRenderingContext2D].
set strokeJoin(ui.StrokeJoin? strokeJoin) {
strokeJoin ??= ui.StrokeJoin.miter;
if (strokeJoin != _currentStrokeJoin) {
Expand All @@ -887,13 +928,15 @@ class ContextStateHandle {
}
}

/// See [html.CanvasRenderingContext2D].
set fillStyle(Object? colorOrGradient) {
if (!identical(colorOrGradient, _currentFillStyle)) {
_currentFillStyle = colorOrGradient;
context.fillStyle = colorOrGradient;
}
}

/// See [html.CanvasRenderingContext2D].
set strokeStyle(Object? colorOrGradient) {
if (!identical(colorOrGradient, _currentStrokeStyle)) {
_currentStrokeStyle = colorOrGradient;
Expand Down Expand Up @@ -1052,6 +1095,7 @@ class ContextStateHandle {
}
}

/// Fills or strokes the currently active path.
void paint(ui.PaintingStyle? style) {
if (style == ui.PaintingStyle.stroke) {
context.stroke();
Expand All @@ -1060,6 +1104,7 @@ class ContextStateHandle {
}
}

/// Fills or strokes the currently active path based on fill type.
void paintPath(ui.PaintingStyle? style, ui.PathFillType pathFillType) {
if (style == ui.PaintingStyle.stroke) {
context.stroke();
Expand All @@ -1072,6 +1117,8 @@ class ContextStateHandle {
}
}

/// Resets drawing context state to defaults for
/// [html.CanvasRenderingContext2D].
void reset() {
context.fillStyle = '';
// Read back fillStyle/strokeStyle values from context so that input such
Expand Down
6 changes: 3 additions & 3 deletions lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class BitmapCanvas extends EngineCanvas {
_canvasPositionX = _bounds.left.floor() - kPaddingPixels;
_canvasPositionY = _bounds.top.floor() - kPaddingPixels;
_updateRootElementTransform();
_canvasPool.allocateCanvas(rootElement as html.HtmlElement);
_canvasPool.mount(rootElement as html.HtmlElement);
_setupInitialTransform();
}

Expand Down Expand Up @@ -372,7 +372,7 @@ class BitmapCanvas extends EngineCanvas {
_renderStrategy.isInsideSvgFilterTree ||
(_preserveImageData == false && _contains3dTransform) ||
(_childOverdraw &&
_canvasPool.canvas == null &&
_canvasPool.isEmpty &&
paint.maskFilter == null &&
paint.shader == null &&
paint.style != ui.PaintingStyle.stroke);
Expand All @@ -386,7 +386,7 @@ class BitmapCanvas extends EngineCanvas {
((_childOverdraw ||
_renderStrategy.hasImageElements ||
_renderStrategy.hasParagraphs) &&
_canvasPool.canvas == null &&
_canvasPool.isEmpty &&
paint.maskFilter == null &&
paint.shader == null);

Expand Down