Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/fonts.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/image.dart
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ part 'engine/assets.dart';
part 'engine/bitmap_canvas.dart';
part 'engine/browser_detection.dart';
part 'engine/browser_location.dart';
part 'engine/color_filter.dart';
part 'engine/compositor/canvas.dart';
part 'engine/compositor/color_filter.dart';
part 'engine/compositor/engine_delegate.dart';
part 'engine/compositor/fonts.dart';
part 'engine/compositor/image.dart';
Expand Down
185 changes: 185 additions & 0 deletions lib/web_ui/lib/src/engine/color_filter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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.

part of engine;

/// A description of a color filter to apply when drawing a shape or compositing
/// a layer with a particular [Paint]. A color filter is a function that takes
/// two colors, and outputs one color. When applied during compositing, it is
/// independently applied to each pixel of the layer being drawn before the
/// entire layer is merged with the destination.
///
/// Instances of this class are used with [Paint.colorFilter] on [Paint]
/// objects.
class EngineColorFilter implements ui.ColorFilter {
/// Creates a color filter that applies the blend mode given as the second
/// argument. The source color is the one given as the first argument, and the
/// destination color is the one from the layer being composited.
///
/// The output of this filter is then composited into the background according
/// to the [Paint.blendMode], using the output of this filter as the source
/// and the background as the destination.
const EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode)
: _color = color,
_blendMode = blendMode,
_matrix = null,
_type = _TypeMode;

/// Construct a color filter that transforms a color by a 5x5 matrix, where
/// the fifth row is implicitly added in an identity configuration.
///
/// Every pixel's color value, repsented as an `[R, G, B, A]`, is matrix
/// multiplied to create a new color:
///
/// ```text
/// | R' | | a00 a01 a02 a03 a04 | | R |
/// | G' | | a10 a11 a22 a33 a44 | | G |
/// | B' | = | a20 a21 a22 a33 a44 | * | B |
/// | A' | | a30 a31 a22 a33 a44 | | A |
/// | 1 | | 0 0 0 0 1 | | 1 |
/// ```
///
/// The matrix is in row-major order and the translation column is specified
/// in unnormalized, 0...255, space. For example, the identity matrix is:
///
/// ```
/// const ColorMatrix identity = ColorFilter.matrix(<double>[
/// 1, 0, 0, 0, 0,
/// 0, 1, 0, 0, 0,
/// 0, 0, 1, 0, 0,
/// 0, 0, 0, 1, 0,
/// ]);
/// ```
///
/// ## Examples
///
/// An inversion color matrix:
///
/// ```
/// const ColorFilter invert = ColorFilter.matrix(<double>[
/// -1, 0, 0, 0, 255,
/// 0, -1, 0, 0, 255,
/// 0, 0, -1, 0, 255,
/// 0, 0, 0, 1, 0,
/// ]);
/// ```
///
/// A sepia-toned color matrix (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent)):
///
/// ```
/// const ColorFilter sepia = ColorFilter.matrix(<double>[
/// 0.393, 0.769, 0.189, 0, 0,
/// 0.349, 0.686, 0.168, 0, 0,
/// 0.272, 0.534, 0.131, 0, 0,
/// 0, 0, 0, 1, 0,
/// ]);
/// ```
///
/// A greyscale color filter (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent)):
///
/// ```
/// const ColorFilter greyscale = ColorFilter.matrix(<double>[
/// 0.2126, 0.7152, 0.0722, 0, 0,
/// 0.2126, 0.7152, 0.0722, 0, 0,
/// 0.2126, 0.7152, 0.0722, 0, 0,
/// 0, 0, 0, 1, 0,
/// ]);
/// ```
const EngineColorFilter.matrix(List<double> matrix)
: _color = null,
_blendMode = null,
_matrix = matrix,
_type = _TypeMatrix;

/// Construct a color filter that applies the sRGB gamma curve to the RGB
/// channels.
const EngineColorFilter.linearToSrgbGamma()
: _color = null,
_blendMode = null,
_matrix = null,
_type = _TypeLinearToSrgbGamma;

/// Creates a color filter that applies the inverse of the sRGB gamma curve
/// to the RGB channels.
const EngineColorFilter.srgbToLinearGamma()
: _color = null,
_blendMode = null,
_matrix = null,
_type = _TypeSrgbToLinearGamma;

final ui.Color _color;
final ui.BlendMode _blendMode;
final List<double> _matrix;
final int _type;

// The type of SkColorFilter class to create for Skia.
// These constants must be kept in sync with ColorFilterType in paint.cc.
static const int _TypeNone = 0; // null
static const int _TypeMode = 1; // MakeModeFilter
static const int _TypeMatrix = 2; // MakeMatrixFilterRowMajor255
static const int _TypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma
static const int _TypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma

@override
bool operator ==(dynamic other) {
if (other is! EngineColorFilter) {
return false;
}
final EngineColorFilter typedOther = other;

if (_type != typedOther._type) {
return false;
}
if (!_listEquals<double>(_matrix, typedOther._matrix)) {
return false;
}

return _color == typedOther._color && _blendMode == typedOther._blendMode;
}

SkColorFilter _toSkColorFilter() {
switch (_type) {
case _TypeMode:
if (_color == null || _blendMode == null) {
return null;
}
return SkColorFilter.mode(this);
case _TypeMatrix:
if (_matrix == null) {
return null;
}
assert(_matrix.length == 20, 'Color Matrix must have 20 entries.');
return SkColorFilter.matrix(this);
case _TypeLinearToSrgbGamma:
return SkColorFilter.linearToSrgbGamma(this);
case _TypeSrgbToLinearGamma:
return SkColorFilter.srgbToLinearGamma(this);
default:
throw StateError('Unknown mode $_type for ColorFilter.');
}
}

@override
int get hashCode => ui.hashValues(_color, _blendMode, ui.hashList(_matrix), _type);

@override
String toString() {
switch (_type) {
case _TypeMode:
return 'ColorFilter.mode($_color, $_blendMode)';
case _TypeMatrix:
return 'ColorFilter.matrix($_matrix)';
case _TypeLinearToSrgbGamma:
return 'ColorFilter.linearToSrgbGamma()';
case _TypeSrgbToLinearGamma:
return 'ColorFilter.srgbToLinearGamma()';
default:
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');
}
}
37 changes: 37 additions & 0 deletions lib/web_ui/lib/src/engine/compositor/color_filter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.

part of engine;

/// A [ui.ColorFilter] backed by Skia's [SkColorFilter].
class SkColorFilter {
js.JsObject skColorFilter;

SkColorFilter.mode(EngineColorFilter filter) {
skColorFilter =
canvasKit['SkColorFilter'].callMethod('MakeBlend', <dynamic>[
filter._color.value,
makeSkBlendMode(filter._blendMode),
]);
}

SkColorFilter.matrix(EngineColorFilter filter) {
// TODO(het): Find a way to remove these array conversions.
final js.JsArray colorMatrix = js.JsArray();
colorMatrix.length = 20;
for (int i = 0; i < 20; i++) {
colorMatrix[i] = filter._matrix[i];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave a todo to remove this double-conversion. This should, at least, convert straight to a Float64List, but ideally we should write to WASM memory directly.

/cc @kjlubick - here's an example for excessive conversion that we talked about at the last sync.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
skColorFilter = canvasKit['SkColorFilter']
.callMethod('MakeMatrix', <js.JsArray>[colorMatrix]);
}

SkColorFilter.linearToSrgbGamma(EngineColorFilter filter) {
skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeLinearToSRGBGamma');
}

SkColorFilter.srgbToLinearGamma(EngineColorFilter filter) {
skColorFilter = canvasKit['SkColorFilter'].callMethod('MakeSRGBToLinearGamma');
}
}
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/compositor/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const bool experimentalUseSkia =
bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false);

/// The URL to use when downloading the CanvasKit script and associated wasm.
const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.6.0/bin/';
const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.7.0/bin/';

/// Initialize the Skia backend.
///
Expand Down
29 changes: 9 additions & 20 deletions lib/web_ui/lib/src/engine/compositor/path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,26 +114,15 @@ class SkPath implements ui.Path {
double rotation = 0.0,
bool largeArc = false,
bool clockwise = true}) {
assert(rotation == 0.0,
'Skia backend does not support `arcToPoint` rotation.');
assert(!largeArc, 'Skia backend does not support `arcToPoint` largeArc.');
assert(radius.x == radius.y,
'Skia backend does not support `arcToPoint` with elliptical radius.');

// TODO(het): Remove asserts above and use the correct override of `arcTo`
// when it is available in CanvasKit.
// The only `arcTo` method exposed in CanvasKit is:
// arcTo(x1, y1, x2, y2, radius)
final ui.Offset lastPoint = _getCurrentPoint();
_skPath.callMethod('arcTo',
<double>[lastPoint.dx, lastPoint.dy, arcEnd.dx, arcEnd.dy, radius.x]);
}

ui.Offset _getCurrentPoint() {
final int pointCount = _skPath.callMethod('countPoints');
final js.JsObject lastPoint =
_skPath.callMethod('getPoint', <int>[pointCount - 1]);
return ui.Offset(lastPoint[0], lastPoint[1]);
_skPath.callMethod('arcTo', <dynamic>[
radius.x,
radius.y,
rotation,
!largeArc,
!clockwise,
arcEnd.dx,
arcEnd.dy,
]);
}

@override
Expand Down
33 changes: 13 additions & 20 deletions lib/web_ui/lib/src/engine/compositor/recording_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ class SkRecordingCanvas implements RecordingCanvas {

@override
void drawCircle(ui.Offset c, double radius, ui.Paint paint) {
final js.JsObject skPaint = makeSkPaint(paint);
// TODO(het): Use `drawCircle` when CanvasKit makes it available.
// Since CanvasKit does not expose `drawCircle`, use `drawOval` instead.
final js.JsObject skRect = makeSkRect(ui.Rect.fromLTWH(
c.dx - radius, c.dy - radius, 2.0 * radius, 2.0 * radius));
skCanvas.callMethod('drawOval', <js.JsObject>[skRect, skPaint]);
skCanvas.callMethod('drawCircle', <dynamic>[
c.dx,
c.dy,
radius,
makeSkPaint(paint),
]);
}

@override
Expand All @@ -114,7 +114,11 @@ class SkRecordingCanvas implements RecordingCanvas {

@override
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
throw 'drawDRRect';
skCanvas.callMethod('drawDRRect', <js.JsObject>[
makeSkRRect(outer),
makeSkRRect(inner),
makeSkPaint(paint),
]);
}

@override
Expand Down Expand Up @@ -190,19 +194,8 @@ class SkRecordingCanvas implements RecordingCanvas {

@override
void drawRRect(ui.RRect rrect, ui.Paint paint) {
// Since CanvasKit does not expose `drawRRect` we have to make do with
// `drawRoundRect`. The downside of `drawRoundRect` is that all of the
// corner radii must be the same.
assert(
rrect.tlRadius == rrect.trRadius &&
rrect.tlRadius == rrect.brRadius &&
rrect.tlRadius == rrect.blRadius,
'CanvasKit only supports drawing RRects where the radii are all the same.',
);
skCanvas.callMethod('drawRoundRect', <dynamic>[
makeSkRect(rrect.outerRect),
rrect.tlRadiusX,
rrect.tlRadiusY,
skCanvas.callMethod('drawRRect', <js.JsObject>[
makeSkRRect(rrect),
makeSkPaint(paint),
]);
}
Expand Down
20 changes: 20 additions & 0 deletions lib/web_ui/lib/src/engine/compositor/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ js.JsObject makeSkRect(ui.Rect rect) {
<double>[rect.left, rect.top, rect.right, rect.bottom]);
}

js.JsObject makeSkRRect(ui.RRect rrect) {
return js.JsObject.jsify({
'rect': makeSkRect(rrect.outerRect),
'rx1': rrect.tlRadiusX,
'ry1': rrect.tlRadiusY,
'rx2': rrect.trRadiusX,
'ry2': rrect.trRadiusY,
'rx3': rrect.brRadiusX,
'ry3': rrect.brRadiusY,
'rx4': rrect.blRadiusX,
'ry4': rrect.blRadiusY,
});
}

js.JsArray<double> makeSkPoint(ui.Offset point) {
final js.JsArray<double> skPoint = js.JsArray<double>();
skPoint.length = 2;
Expand Down Expand Up @@ -142,6 +156,12 @@ js.JsObject makeSkPaint(ui.Paint paint) {
skPaint.callMethod('setMaskFilter', <js.JsObject>[skMaskFilter]);
}

if (paint.colorFilter != null) {
EngineColorFilter engineFilter = paint.colorFilter;
SkColorFilter skFilter = engineFilter._toSkColorFilter();
skPaint.callMethod('setColorFilter', <js.JsObject>[skFilter.skColorFilter]);
}

return skPaint;
}

Expand Down
Loading