未验证 提交 e872177c 编写于 作者: L LongCatIsLooong 提交者: GitHub

Exposing ColorFilter to ImageFilter conversion and Compose() (#20309)

上级 47f9e134
...@@ -2900,7 +2900,7 @@ class MaskFilter { ...@@ -2900,7 +2900,7 @@ class MaskFilter {
/// ///
/// Instances of this class are used with [Paint.colorFilter] on [Paint] /// Instances of this class are used with [Paint.colorFilter] on [Paint]
/// objects. /// objects.
class ColorFilter { class ColorFilter implements ImageFilter {
/// Creates a color filter that applies the blend mode given as the second /// 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 /// argument. The source color is the one given as the first argument, and the
/// destination color is the one from the layer being composited. /// destination color is the one from the layer being composited.
...@@ -2912,7 +2912,7 @@ class ColorFilter { ...@@ -2912,7 +2912,7 @@ class ColorFilter {
: _color = color, : _color = color,
_blendMode = blendMode, _blendMode = blendMode,
_matrix = null, _matrix = null,
_type = _TypeMode; _type = _kTypeMode;
/// Construct a color filter that transforms a color by a 5x5 matrix, where /// Construct a color filter that transforms a color by a 5x5 matrix, where
/// the fifth row is implicitly added in an identity configuration. /// the fifth row is implicitly added in an identity configuration.
...@@ -2978,7 +2978,7 @@ class ColorFilter { ...@@ -2978,7 +2978,7 @@ class ColorFilter {
: _color = null, : _color = null,
_blendMode = null, _blendMode = null,
_matrix = matrix, _matrix = matrix,
_type = _TypeMatrix; _type = _kTypeMatrix;
/// Construct a color filter that applies the sRGB gamma curve to the RGB /// Construct a color filter that applies the sRGB gamma curve to the RGB
/// channels. /// channels.
...@@ -2986,7 +2986,7 @@ class ColorFilter { ...@@ -2986,7 +2986,7 @@ class ColorFilter {
: _color = null, : _color = null,
_blendMode = null, _blendMode = null,
_matrix = null, _matrix = null,
_type = _TypeLinearToSrgbGamma; _type = _kTypeLinearToSrgbGamma;
/// Creates a color filter that applies the inverse of the sRGB gamma curve /// Creates a color filter that applies the inverse of the sRGB gamma curve
/// to the RGB channels. /// to the RGB channels.
...@@ -2994,7 +2994,7 @@ class ColorFilter { ...@@ -2994,7 +2994,7 @@ class ColorFilter {
: _color = null, : _color = null,
_blendMode = null, _blendMode = null,
_matrix = null, _matrix = null,
_type = _TypeSrgbToLinearGamma; _type = _kTypeSrgbToLinearGamma;
final Color? _color; final Color? _color;
final BlendMode? _blendMode; final BlendMode? _blendMode;
...@@ -3002,55 +3002,77 @@ class ColorFilter { ...@@ -3002,55 +3002,77 @@ class ColorFilter {
final int _type; final int _type;
// The type of SkColorFilter class to create for Skia. // The type of SkColorFilter class to create for Skia.
static const int _TypeMode = 1; // MakeModeFilter static const int _kTypeMode = 1; // MakeModeFilter
static const int _TypeMatrix = 2; // MakeMatrixFilterRowMajor255 static const int _kTypeMatrix = 2; // MakeMatrixFilterRowMajor255
static const int _TypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma static const int _kTypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma
static const int _TypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma static const int _kTypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma
// SkImageFilters::ColorFilter
@override @override
bool operator ==(Object other) { _ImageFilter _toNativeImageFilter() => _ImageFilter.fromColorFilter(this);
return other is ColorFilter
&& other._type == _type
&& _listEquals<double>(other._matrix, _matrix)
&& other._color == _color
&& other._blendMode == _blendMode;
}
_ColorFilter? _toNativeColorFilter() { _ColorFilter? _toNativeColorFilter() {
switch (_type) { switch (_type) {
case _TypeMode: case _kTypeMode:
if (_color == null || _blendMode == null) { if (_color == null || _blendMode == null) {
return null; return null;
} }
return _ColorFilter.mode(this); return _ColorFilter.mode(this);
case _TypeMatrix: case _kTypeMatrix:
if (_matrix == null) { if (_matrix == null) {
return null; return null;
} }
assert(_matrix!.length == 20, 'Color Matrix must have 20 entries.'); assert(_matrix!.length == 20, 'Color Matrix must have 20 entries.');
return _ColorFilter.matrix(this); return _ColorFilter.matrix(this);
case _TypeLinearToSrgbGamma: case _kTypeLinearToSrgbGamma:
return _ColorFilter.linearToSrgbGamma(this); return _ColorFilter.linearToSrgbGamma(this);
case _TypeSrgbToLinearGamma: case _kTypeSrgbToLinearGamma:
return _ColorFilter.srgbToLinearGamma(this); return _ColorFilter.srgbToLinearGamma(this);
default: default:
throw StateError('Unknown mode $_type for ColorFilter.'); throw StateError('Unknown mode $_type for ColorFilter.');
} }
} }
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is ColorFilter
&& other._type == _type
&& _listEquals<double>(other._matrix, _matrix)
&& other._color == _color
&& other._blendMode == _blendMode;
}
@override @override
int get hashCode => hashValues(_color, _blendMode, hashList(_matrix), _type); int get hashCode => hashValues(_color, _blendMode, hashList(_matrix), _type);
@override
String get _shortDescription {
switch (_type) {
case _kTypeMode:
return 'ColorFilter.mode($_color, $_blendMode)';
case _kTypeMatrix:
return 'ColorFilter.matrix($_matrix)';
case _kTypeLinearToSrgbGamma:
return 'ColorFilter.linearToSrgbGamma()';
case _kTypeSrgbToLinearGamma:
return 'ColorFilter.srgbToLinearGamma()';
default:
return 'unknow ColorFilter';
}
}
@override @override
String toString() { String toString() {
switch (_type) { switch (_type) {
case _TypeMode: case _kTypeMode:
return 'ColorFilter.mode($_color, $_blendMode)'; return 'ColorFilter.mode($_color, $_blendMode)';
case _TypeMatrix: case _kTypeMatrix:
return 'ColorFilter.matrix($_matrix)'; return 'ColorFilter.matrix($_matrix)';
case _TypeLinearToSrgbGamma: case _kTypeLinearToSrgbGamma:
return 'ColorFilter.linearToSrgbGamma()'; return 'ColorFilter.linearToSrgbGamma()';
case _TypeSrgbToLinearGamma: case _kTypeSrgbToLinearGamma:
return 'ColorFilter.srgbToLinearGamma()'; return 'ColorFilter.srgbToLinearGamma()';
default: 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.'; 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.';
...@@ -3067,27 +3089,27 @@ class ColorFilter { ...@@ -3067,27 +3089,27 @@ class ColorFilter {
class _ColorFilter extends NativeFieldWrapperClass2 { class _ColorFilter extends NativeFieldWrapperClass2 {
_ColorFilter.mode(this.creator) _ColorFilter.mode(this.creator)
: assert(creator != null), // ignore: unnecessary_null_comparison : assert(creator != null), // ignore: unnecessary_null_comparison
assert(creator._type == ColorFilter._TypeMode) { assert(creator._type == ColorFilter._kTypeMode) {
_constructor(); _constructor();
_initMode(creator._color!.value, creator._blendMode!.index); _initMode(creator._color!.value, creator._blendMode!.index);
} }
_ColorFilter.matrix(this.creator) _ColorFilter.matrix(this.creator)
: assert(creator != null), // ignore: unnecessary_null_comparison : assert(creator != null), // ignore: unnecessary_null_comparison
assert(creator._type == ColorFilter._TypeMatrix) { assert(creator._type == ColorFilter._kTypeMatrix) {
_constructor(); _constructor();
_initMatrix(Float32List.fromList(creator._matrix!)); _initMatrix(Float32List.fromList(creator._matrix!));
} }
_ColorFilter.linearToSrgbGamma(this.creator) _ColorFilter.linearToSrgbGamma(this.creator)
: assert(creator != null), // ignore: unnecessary_null_comparison : assert(creator != null), // ignore: unnecessary_null_comparison
assert(creator._type == ColorFilter._TypeLinearToSrgbGamma) { assert(creator._type == ColorFilter._kTypeLinearToSrgbGamma) {
_constructor(); _constructor();
_initLinearToSrgbGamma(); _initLinearToSrgbGamma();
} }
_ColorFilter.srgbToLinearGamma(this.creator) _ColorFilter.srgbToLinearGamma(this.creator)
: assert(creator != null), // ignore: unnecessary_null_comparison : assert(creator != null), // ignore: unnecessary_null_comparison
assert(creator._type == ColorFilter._TypeSrgbToLinearGamma) { assert(creator._type == ColorFilter._kTypeSrgbToLinearGamma) {
_constructor(); _constructor();
_initSrgbToLinearGamma(); _initSrgbToLinearGamma();
} }
...@@ -3113,80 +3135,134 @@ class _ColorFilter extends NativeFieldWrapperClass2 { ...@@ -3113,80 +3135,134 @@ class _ColorFilter extends NativeFieldWrapperClass2 {
/// this class as a backdrop filter. /// this class as a backdrop filter.
/// * [SceneBuilder.pushImageFilter], which is the low-level API for using /// * [SceneBuilder.pushImageFilter], which is the low-level API for using
/// this class as a child layer filter. /// this class as a child layer filter.
class ImageFilter { abstract class ImageFilter {
/// Creates an image filter that applies a Gaussian blur. /// Creates an image filter that applies a Gaussian blur.
ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) {
: assert(sigmaX != null), // ignore: unnecessary_null_comparison assert(sigmaX != null); // ignore: unnecessary_null_comparison
assert(sigmaY != null), // ignore: unnecessary_null_comparison assert(sigmaY != null); // ignore: unnecessary_null_comparison
_data = _makeList(sigmaX, sigmaY), return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY);
_filterQuality = null, }
_type = _kTypeBlur;
/// Creates an image filter that applies a matrix transformation. /// Creates an image filter that applies a matrix transformation.
/// ///
/// For example, applying a positive scale matrix (see [Matrix4.diagonal3]) /// For example, applying a positive scale matrix (see [Matrix4.diagonal3])
/// when used with [BackdropFilter] would magnify the background image. /// when used with [BackdropFilter] would magnify the background image.
ImageFilter.matrix(Float64List matrix4, factory ImageFilter.matrix(Float64List matrix4,
{ FilterQuality filterQuality = FilterQuality.low }) { FilterQuality filterQuality = FilterQuality.low }) {
: assert(matrix4 != null), // ignore: unnecessary_null_comparison assert(matrix4 != null); // ignore: unnecessary_null_comparison
_data = Float64List.fromList(matrix4), assert(filterQuality != null); // ignore: unnecessary_null_comparison
_filterQuality = filterQuality,
_type = _kTypeMatrix {
if (matrix4.length != 16) if (matrix4.length != 16)
throw ArgumentError('"matrix4" must have 16 entries.'); throw ArgumentError('"matrix4" must have 16 entries.');
return _MatrixImageFilter(data: Float64List.fromList(matrix4), filterQuality: filterQuality);
} }
static Float64List _makeList(double a, double b) { /// Composes the `inner` filter with `outer`, to combine their effects.
final Float64List list = Float64List(2); ///
list[0] = a; /// Creates a single [ImageFilter] that when applied, has the same effect as
list[1] = b; /// subsequently applying `inner` and `outer`, i.e.,
return list; /// result = outer(inner(source)).
factory ImageFilter.compose({ required ImageFilter outer, required ImageFilter inner }) {
assert (inner != null && outer != null); // ignore: unnecessary_null_comparison
return _ComposeImageFilter(innerFilter: inner, outerFilter: outer);
} }
final Float64List _data; // Converts this to a native SkImageFilter. See the comments of this method in
final FilterQuality? _filterQuality; // subclasses for the exact type of SkImageFilter this method converts to.
final int _type; _ImageFilter _toNativeImageFilter();
_ImageFilter? _nativeFilter;
// The type of SkImageFilter class to create for Skia. // The description text to show when the filter is part of a composite
static const int _kTypeBlur = 0; // MakeBlurFilter // [ImageFilter] created using [ImageFilter.compose].
static const int _kTypeMatrix = 1; // MakeMatrixFilterRowMajor255 String get _shortDescription;
}
class _MatrixImageFilter implements ImageFilter {
_MatrixImageFilter({ required this.data, required this.filterQuality });
final Float64List data;
final FilterQuality filterQuality;
// MakeMatrixFilterRowMajor255
late final _ImageFilter nativeFilter = _ImageFilter.matrix(this);
@override
_ImageFilter _toNativeImageFilter() => nativeFilter;
@override
String get _shortDescription => 'matrix($data, $filterQuality)';
@override
String toString() => 'ImageFilter.matrix($data, $filterQuality)';
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is ImageFilter if (other.runtimeType != runtimeType)
&& other._type == _type return false;
&& _listEquals<double>(other._data, _data) return other is _MatrixImageFilter
&& other._filterQuality == _filterQuality; && other.filterQuality == filterQuality
&& _listEquals<double>(other.data, data);
} }
_ImageFilter _toNativeImageFilter() => _nativeFilter ??= _makeNativeImageFilter(); @override
int get hashCode => hashValues(filterQuality, hashList(data));
}
_ImageFilter _makeNativeImageFilter() { class _GaussianBlurImageFilter implements ImageFilter {
switch (_type) { _GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY });
case _kTypeBlur:
return _ImageFilter.blur(this); final double sigmaX;
case _kTypeMatrix: final double sigmaY;
return _ImageFilter.matrix(this);
default: // MakeBlurFilter
throw StateError('Unknown mode $_type for ImageFilter.'); late final _ImageFilter nativeFilter = _ImageFilter.blur(this);
} @override
_ImageFilter _toNativeImageFilter() => nativeFilter;
@override
String get _shortDescription => 'blur($sigmaX, $sigmaY)';
@override
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY)';
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is _GaussianBlurImageFilter
&& other.sigmaX == sigmaX
&& other.sigmaY == sigmaY;
} }
@override @override
int get hashCode => hashValues(_filterQuality, hashList(_data), _type); int get hashCode => hashValues(sigmaX, sigmaY);
}
class _ComposeImageFilter implements ImageFilter {
_ComposeImageFilter({ required this.innerFilter, required this.outerFilter });
final ImageFilter innerFilter;
final ImageFilter outerFilter;
// SkImageFilters::Compose
late final _ImageFilter nativeFilter = _ImageFilter.composed(this);
@override @override
String toString() { _ImageFilter _toNativeImageFilter() => nativeFilter;
switch (_type) {
case _kTypeBlur: @override
return 'ImageFilter.blur(${_data[0]}, ${_data[1]})'; String get _shortDescription => '${innerFilter._shortDescription} -> ${outerFilter._shortDescription}';
case _kTypeMatrix:
return 'ImageFilter.matrix($_data, $_filterQuality)'; @override
default: String toString() => 'ImageFilter.compose(source -> $_shortDescription -> result)';
return 'Unknown ImageFilter type. This is an error. If you\'re seeing this, please file an issue at https://github.com/flutter/flutter/issues/new.';
} @override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is _ComposeImageFilter
&& other.innerFilter == innerFilter
&& other.outerFilter == outerFilter;
} }
@override
int get hashCode => hashValues(innerFilter, outerFilter);
} }
/// An [ImageFilter] that is backed by a native SkImageFilter. /// An [ImageFilter] that is backed by a native SkImageFilter.
...@@ -3198,11 +3274,11 @@ class _ImageFilter extends NativeFieldWrapperClass2 { ...@@ -3198,11 +3274,11 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
void _constructor() native 'ImageFilter_constructor'; void _constructor() native 'ImageFilter_constructor';
/// Creates an image filter that applies a Gaussian blur. /// Creates an image filter that applies a Gaussian blur.
_ImageFilter.blur(this.creator) _ImageFilter.blur(_GaussianBlurImageFilter filter)
: assert(creator != null), // ignore: unnecessary_null_comparison : assert(filter != null), // ignore: unnecessary_null_comparison
assert(creator._type == ImageFilter._kTypeBlur) { creator = filter { // ignore: prefer_initializing_formals
_constructor(); _constructor();
_initBlur(creator._data[0], creator._data[1]); _initBlur(filter.sigmaX, filter.sigmaY);
} }
void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur'; void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur';
...@@ -3210,16 +3286,36 @@ class _ImageFilter extends NativeFieldWrapperClass2 { ...@@ -3210,16 +3286,36 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
/// ///
/// For example, applying a positive scale matrix (see [Matrix4.diagonal3]) /// For example, applying a positive scale matrix (see [Matrix4.diagonal3])
/// when used with [BackdropFilter] would magnify the background image. /// when used with [BackdropFilter] would magnify the background image.
_ImageFilter.matrix(this.creator) _ImageFilter.matrix(_MatrixImageFilter filter)
: assert(creator != null), // ignore: unnecessary_null_comparison : assert(filter != null), // ignore: unnecessary_null_comparison
assert(creator._type == ImageFilter._kTypeMatrix) { creator = filter { // ignore: prefer_initializing_formals
if (creator._data.length != 16) if (filter.data.length != 16)
throw ArgumentError('"matrix4" must have 16 entries.'); throw ArgumentError('"matrix4" must have 16 entries.');
_constructor(); _constructor();
_initMatrix(creator._data, creator._filterQuality!.index); _initMatrix(filter.data, filter.filterQuality.index);
} }
void _initMatrix(Float64List matrix4, int filterQuality) native 'ImageFilter_initMatrix'; void _initMatrix(Float64List matrix4, int filterQuality) native 'ImageFilter_initMatrix';
/// Converts a color filter to an image filter.
_ImageFilter.fromColorFilter(ColorFilter filter)
: assert(filter != null), // ignore: unnecessary_null_comparison
creator = filter { // ignore: prefer_initializing_formals
_constructor();
final _ColorFilter? nativeFilter = filter._toNativeColorFilter();
_initColorFilter(nativeFilter);
}
void _initColorFilter(_ColorFilter? colorFilter) native 'ImageFilter_initColorFilter';
/// Composes `_innerFilter` with `_outerFilter`.
_ImageFilter.composed(_ComposeImageFilter filter)
: assert(filter != null), // ignore: unnecessary_null_comparison
creator = filter { // ignore: prefer_initializing_formals
_constructor();
final _ImageFilter nativeFilterInner = filter.innerFilter._toNativeImageFilter();
final _ImageFilter nativeFilterOuter = filter.outerFilter._toNativeImageFilter();
_initComposed(nativeFilterOuter, nativeFilterInner);
}
void _initComposed(_ImageFilter outerFilter, _ImageFilter innerFilter) native 'ImageFilter_initComposeFilter';
/// The original Dart object that created the native wrapper, which retains /// The original Dart object that created the native wrapper, which retains
/// the values used for the filter. /// the values used for the filter.
final ImageFilter creator; final ImageFilter creator;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "flutter/lib/ui/painting/matrix.h" #include "flutter/lib/ui/painting/matrix.h"
#include "third_party/skia/include/effects/SkBlurImageFilter.h" #include "third_party/skia/include/effects/SkBlurImageFilter.h"
#include "third_party/skia/include/effects/SkImageFilters.h"
#include "third_party/skia/include/effects/SkImageSource.h" #include "third_party/skia/include/effects/SkImageSource.h"
#include "third_party/skia/include/effects/SkPictureImageFilter.h" #include "third_party/skia/include/effects/SkPictureImageFilter.h"
#include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/converter/dart_converter.h"
...@@ -22,11 +23,13 @@ static void ImageFilter_constructor(Dart_NativeArguments args) { ...@@ -22,11 +23,13 @@ static void ImageFilter_constructor(Dart_NativeArguments args) {
IMPLEMENT_WRAPPERTYPEINFO(ui, ImageFilter); IMPLEMENT_WRAPPERTYPEINFO(ui, ImageFilter);
#define FOR_EACH_BINDING(V) \ #define FOR_EACH_BINDING(V) \
V(ImageFilter, initImage) \ V(ImageFilter, initImage) \
V(ImageFilter, initPicture) \ V(ImageFilter, initPicture) \
V(ImageFilter, initBlur) \ V(ImageFilter, initBlur) \
V(ImageFilter, initMatrix) V(ImageFilter, initMatrix) \
V(ImageFilter, initColorFilter) \
V(ImageFilter, initComposeFilter)
FOR_EACH_BINDING(DART_NATIVE_CALLBACK) FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
...@@ -64,4 +67,14 @@ void ImageFilter::initMatrix(const tonic::Float64List& matrix4, ...@@ -64,4 +67,14 @@ void ImageFilter::initMatrix(const tonic::Float64List& matrix4,
nullptr); nullptr);
} }
void ImageFilter::initColorFilter(ColorFilter* colorFilter) {
filter_ = SkImageFilters::ColorFilter(
colorFilter ? colorFilter->filter() : nullptr, nullptr);
}
void ImageFilter::initComposeFilter(ImageFilter* outer, ImageFilter* inner) {
filter_ = SkImageFilters::Compose(outer ? outer->filter() : nullptr,
inner ? inner->filter() : nullptr);
}
} // namespace flutter } // namespace flutter
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define FLUTTER_LIB_UI_PAINTING_IMAGE_FILTER_H_ #define FLUTTER_LIB_UI_PAINTING_IMAGE_FILTER_H_
#include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/painting/color_filter.h"
#include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/painting/picture.h"
#include "third_party/skia/include/core/SkImageFilter.h" #include "third_party/skia/include/core/SkImageFilter.h"
...@@ -25,6 +26,8 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> { ...@@ -25,6 +26,8 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {
void initPicture(Picture*); void initPicture(Picture*);
void initBlur(double sigma_x, double sigma_y); void initBlur(double sigma_x, double sigma_y);
void initMatrix(const tonic::Float64List& matrix4, int filter_quality); void initMatrix(const tonic::Float64List& matrix4, int filter_quality);
void initColorFilter(ColorFilter* colorFilter);
void initComposeFilter(ImageFilter* outer, ImageFilter* inner);
const sk_sp<SkImageFilter>& filter() const { return filter_; } const sk_sp<SkImageFilter>& filter() const { return filter_; }
......
...@@ -573,15 +573,10 @@ class BitmapCanvas extends EngineCanvas { ...@@ -573,15 +573,10 @@ class BitmapCanvas extends EngineCanvas {
ui.Image image, ui.Offset p, SurfacePaintData paint) { ui.Image image, ui.Offset p, SurfacePaintData paint) {
final HtmlImage htmlImage = image as HtmlImage; final HtmlImage htmlImage = image as HtmlImage;
final ui.BlendMode? blendMode = paint.blendMode; final ui.BlendMode? blendMode = paint.blendMode;
final EngineColorFilter? colorFilter = final EngineColorFilter? colorFilter = paint.colorFilter as EngineColorFilter?;
paint.colorFilter as EngineColorFilter?;
final ui.BlendMode? colorFilterBlendMode = colorFilter?._blendMode;
html.HtmlElement imgElement; html.HtmlElement imgElement;
if (colorFilterBlendMode == null) { if (colorFilter is _CkBlendModeColorFilter) {
// No Blending, create an image by cloning original loaded image. switch (colorFilter.blendMode) {
imgElement = _reuseOrCreateImage(htmlImage);
} else {
switch (colorFilterBlendMode) {
case ui.BlendMode.colorBurn: case ui.BlendMode.colorBurn:
case ui.BlendMode.colorDodge: case ui.BlendMode.colorDodge:
case ui.BlendMode.hue: case ui.BlendMode.hue:
...@@ -595,14 +590,17 @@ class BitmapCanvas extends EngineCanvas { ...@@ -595,14 +590,17 @@ class BitmapCanvas extends EngineCanvas {
case ui.BlendMode.color: case ui.BlendMode.color:
case ui.BlendMode.luminosity: case ui.BlendMode.luminosity:
case ui.BlendMode.xor: case ui.BlendMode.xor:
imgElement = _createImageElementWithSvgFilter( imgElement = _createImageElementWithSvgFilter(image,
image, colorFilter!._color, colorFilterBlendMode, paint); colorFilter.color, colorFilter.blendMode, paint);
break; break;
default: default:
imgElement = _createBackgroundImageWithBlend( imgElement = _createBackgroundImageWithBlend(image,
image, colorFilter!._color, colorFilterBlendMode, paint); colorFilter.color, colorFilter.blendMode, paint);
break; break;
} }
} else {
// No Blending, create an image by cloning original loaded image.
imgElement = _reuseOrCreateImage(htmlImage);
} }
imgElement.style.mixBlendMode = _stringForBlendMode(blendMode) ?? ''; imgElement.style.mixBlendMode = _stringForBlendMode(blendMode) ?? '';
if (_canvasPool.isClipped) { if (_canvasPool.isClipped) {
......
...@@ -241,11 +241,11 @@ class CkCanvas { ...@@ -241,11 +241,11 @@ class CkCanvas {
} }
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) { void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
final CkImageFilter skImageFilter = filter as CkImageFilter; final _CkManagedSkImageFilterConvertible convertible = filter as _CkManagedSkImageFilterConvertible;
return skCanvas.saveLayer( return skCanvas.saveLayer(
null, null,
toSkRect(bounds), toSkRect(bounds),
skImageFilter.skiaObject, convertible._imageFilter.skiaObject,
0, 0,
); );
} }
......
...@@ -823,6 +823,16 @@ class SkImageFilterNamespace { ...@@ -823,6 +823,16 @@ class SkImageFilterNamespace {
SkFilterQuality filterQuality, SkFilterQuality filterQuality,
Null input, // we don't use this yet Null input, // we don't use this yet
); );
external SkImageFilter MakeColorFilter(
SkColorFilter colorFilter,
Null input, // we don't use this yet
);
external SkImageFilter MakeCompose(
SkImageFilter outer,
SkImageFilter inner,
);
} }
@JS() @JS()
......
...@@ -5,62 +5,147 @@ ...@@ -5,62 +5,147 @@
// @dart = 2.10 // @dart = 2.10
part of engine; part of engine;
/// A [ui.ColorFilter] backed by Skia's [CkColorFilter]. /// A concrete [ManagedSkiaObject] subclass that owns a [SkColorFilter] and
class CkColorFilter extends ManagedSkiaObject<SkColorFilter> { /// manages its lifecycle.
final EngineColorFilter _engineFilter; ///
/// Seealso:
CkColorFilter.mode(EngineColorFilter filter) : _engineFilter = filter; ///
/// * [CkPaint.colorFilter], which uses a [_ManagedSkColorFilter] to manage
CkColorFilter.matrix(EngineColorFilter filter) : _engineFilter = filter; /// the lifecycle of its [SkColorFilter].
class _ManagedSkColorFilter extends ManagedSkiaObject<SkColorFilter> {
CkColorFilter.linearToSrgbGamma(EngineColorFilter filter) _ManagedSkColorFilter(CkColorFilter ckColorFilter)
: _engineFilter = filter; : this.ckColorFilter = ckColorFilter;
CkColorFilter.srgbToLinearGamma(EngineColorFilter filter) final CkColorFilter ckColorFilter;
: _engineFilter = filter;
@override
SkColorFilter _createSkiaObjectFromFilter() { SkColorFilter createDefault() => ckColorFilter._initRawColorFilter();
SkColorFilter skColorFilter;
switch (_engineFilter._type) { @override
case EngineColorFilter._TypeMode: SkColorFilter resurrect() => ckColorFilter._initRawColorFilter();
skColorFilter = canvasKit.SkColorFilter.MakeBlend(
toSharedSkColor1(_engineFilter._color!), @override
toSkBlendMode(_engineFilter._blendMode!), void delete() {
); rawSkiaObject?.delete();
break;
case EngineColorFilter._TypeMatrix:
final Float32List colorMatrix = Float32List(20);
final List<double> matrix = _engineFilter._matrix!;
for (int i = 0; i < 20; i++) {
colorMatrix[i] = matrix[i];
}
skColorFilter = canvasKit.SkColorFilter.MakeMatrix(colorMatrix);
break;
case EngineColorFilter._TypeLinearToSrgbGamma:
skColorFilter = canvasKit.SkColorFilter.MakeLinearToSRGBGamma();
break;
case EngineColorFilter._TypeSrgbToLinearGamma:
skColorFilter = canvasKit.SkColorFilter.MakeSRGBToLinearGamma();
break;
default:
throw StateError(
'Unknown mode ${_engineFilter._type} for ColorFilter.');
}
return skColorFilter;
} }
@override @override
SkColorFilter createDefault() { int get hashCode => ckColorFilter.hashCode;
return _createSkiaObjectFromFilter();
@override
bool operator ==(Object other) {
if (runtimeType != other.runtimeType)
return false;
return other is _ManagedSkColorFilter
&& other.ckColorFilter == ckColorFilter;
} }
@override @override
SkColorFilter resurrect() { String toString() => ckColorFilter.toString();
return _createSkiaObjectFromFilter(); }
/// A [ui.ColorFilter] backed by Skia's [SkColorFilter].
///
/// Additionally, this class provides the interface for converting itself to a
/// [ManagedSkiaObject] that manages a skia image filter.
abstract class CkColorFilter implements _CkManagedSkImageFilterConvertible<SkImageFilter>, EngineColorFilter {
const CkColorFilter();
/// Called by [ManagedSkiaObject.createDefault] and
/// [ManagedSkiaObject.resurrect] to create a new [SKImageFilter], when this
/// filter is used as an [ImageFilter].
SkImageFilter _initRawImageFilter() => canvasKit.SkImageFilter.MakeColorFilter(_initRawColorFilter(), null);
/// Called by [ManagedSkiaObject.createDefault] and
/// [ManagedSkiaObject.resurrect] to create a new [SKColorFilter], when this
/// filter is used as a [ColorFilter].
SkColorFilter _initRawColorFilter();
ManagedSkiaObject<SkImageFilter> get _imageFilter => _CkColorFilterImageFilter(colorFilter: this);
}
class _CkBlendModeColorFilter extends CkColorFilter {
const _CkBlendModeColorFilter(this.color, this.blendMode);
final ui.Color color;
final ui.BlendMode blendMode;
@override
SkColorFilter _initRawColorFilter() {
return canvasKit.SkColorFilter.MakeBlend(
toSharedSkColor1(color),
toSkBlendMode(blendMode),
);
} }
@override @override
void delete() { int get hashCode => ui.hashValues(color, blendMode);
rawSkiaObject?.delete();
@override
bool operator ==(Object other) {
if (runtimeType != other.runtimeType)
return false;
return other is _CkBlendModeColorFilter
&& other.color == color
&& other.blendMode == blendMode;
}
@override
String toString() => 'ColorFilter.mode($color, $blendMode)';
}
class _CkMatrixColorFilter extends CkColorFilter {
const _CkMatrixColorFilter(this.matrix);
final List<double> matrix;
@override
SkColorFilter _initRawColorFilter() {
assert(this.matrix.length == 20, 'Color Matrix must have 20 entries.');
final List<double> matrix = this.matrix;
if (matrix is Float32List)
return canvasKit.SkColorFilter.MakeMatrix(matrix);
final Float32List float32Matrix = Float32List(20);
for (int i = 0; i < 20; i++) {
float32Matrix[i] = matrix[i];
}
return canvasKit.SkColorFilter.MakeMatrix(float32Matrix);
} }
@override
int get hashCode => ui.hashList(matrix);
@override
bool operator ==(Object other) {
return runtimeType == other.runtimeType
&& other is _CkMatrixColorFilter
&& _listEquals<double>(matrix, other.matrix);
}
@override
String toString() => 'ColorFilter.matrix($matrix)';
}
class _CkLinearToSrgbGammaColorFilter extends CkColorFilter {
const _CkLinearToSrgbGammaColorFilter();
@override
SkColorFilter _initRawColorFilter() => canvasKit.SkColorFilter.MakeLinearToSRGBGamma();
@override
bool operator ==(Object other) => runtimeType == other.runtimeType;
@override
String toString() => 'ColorFilter.linearToSrgbGamma()';
}
class _CkSrgbToLinearGammaColorFilter extends CkColorFilter {
const _CkSrgbToLinearGammaColorFilter();
@override
SkColorFilter _initRawColorFilter() => canvasKit.SkColorFilter.MakeSRGBToLinearGamma();
@override
bool operator ==(Object other) => runtimeType == other.runtimeType;
@override
String toString() => 'ColorFilter.srgbToLinearGamma()';
} }
...@@ -5,16 +5,30 @@ ...@@ -5,16 +5,30 @@
// @dart = 2.10 // @dart = 2.10
part of engine; part of engine;
/// An [ImageFilter] that can create a managed skia [SkImageFilter] object.
///
/// Concrete subclasses of this interface must provide efficient implementation
/// of [operator==], to avoid re-creating the underlying skia filters
/// whenever possible.
///
/// Currently implemented by [CkImageFilter] and [CkColorFilter].
abstract class _CkManagedSkImageFilterConvertible<T extends Object> implements ui.ImageFilter {
ManagedSkiaObject<SkImageFilter> get _imageFilter;
}
/// The CanvasKit implementation of [ui.ImageFilter]. /// The CanvasKit implementation of [ui.ImageFilter].
/// ///
/// Currently only supports `blur`. /// Currently only supports `blur`.
class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.ImageFilter { abstract class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements _CkManagedSkImageFilterConvertible<SkImageFilter> {
CkImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0}) factory CkImageFilter.blur({ required double sigmaX, required double sigmaY }) = _CkBlurImageFilter;
: _sigmaX = sigmaX, factory CkImageFilter.color({ required CkColorFilter colorFilter }) = _CkColorFilterImageFilter;
_sigmaY = sigmaY;
CkImageFilter._();
@override
ManagedSkiaObject<SkImageFilter> get _imageFilter => this;
final double _sigmaX; SkImageFilter _initSkiaObject();
final double _sigmaY;
@override @override
SkImageFilter createDefault() => _initSkiaObject(); SkImageFilter createDefault() => _initSkiaObject();
...@@ -26,11 +40,42 @@ class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.Image ...@@ -26,11 +40,42 @@ class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.Image
void delete() { void delete() {
rawSkiaObject?.delete(); rawSkiaObject?.delete();
} }
}
class _CkColorFilterImageFilter extends CkImageFilter {
_CkColorFilterImageFilter({ required this.colorFilter }) : super._();
final CkColorFilter colorFilter;
@override
SkImageFilter _initSkiaObject() => colorFilter._initRawImageFilter();
@override
int get hashCode => colorFilter.hashCode;
@override
bool operator ==(Object other) {
if (runtimeType != other.runtimeType)
return false;
return other is _CkColorFilterImageFilter
&& other.colorFilter == colorFilter;
}
@override
String toString() => colorFilter.toString();
}
class _CkBlurImageFilter extends CkImageFilter {
_CkBlurImageFilter({ required this.sigmaX, required this.sigmaY }) : super._();
final double sigmaX;
final double sigmaY;
@override
SkImageFilter _initSkiaObject() { SkImageFilter _initSkiaObject() {
return canvasKit.SkImageFilter.MakeBlur( return canvasKit.SkImageFilter.MakeBlur(
_sigmaX, sigmaX,
_sigmaY, sigmaY,
canvasKit.TileMode.Clamp, canvasKit.TileMode.Clamp,
null, null,
); );
...@@ -38,16 +83,19 @@ class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.Image ...@@ -38,16 +83,19 @@ class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.Image
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is CkImageFilter if (runtimeType != other.runtimeType)
&& other._sigmaX == _sigmaX return false;
&& other._sigmaY == _sigmaY; return other is _CkBlurImageFilter
&& other.sigmaX == sigmaX
&& other.sigmaY == sigmaY;
} }
@override @override
int get hashCode => ui.hashValues(_sigmaX, _sigmaY); int get hashCode => ui.hashValues(sigmaX, sigmaY);
@override @override
String toString() { String toString() {
return 'ImageFilter.blur($_sigmaX, $_sigmaY)'; return 'ImageFilter.blur($sigmaX, $sigmaY)';
} }
} }
...@@ -165,20 +165,22 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint { ...@@ -165,20 +165,22 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
ui.FilterQuality _filterQuality = ui.FilterQuality.none; ui.FilterQuality _filterQuality = ui.FilterQuality.none;
@override @override
ui.ColorFilter? get colorFilter => _colorFilter; ui.ColorFilter? get colorFilter => _managedColorFilter?.ckColorFilter;
@override @override
set colorFilter(ui.ColorFilter? value) { set colorFilter(ui.ColorFilter? value) {
if (_colorFilter == value) { if (colorFilter == value) {
return; return;
} }
final EngineColorFilter? engineValue = value as EngineColorFilter?;
_colorFilter = engineValue; if (value == null) {
_ckColorFilter = engineValue?._toCkColorFilter(); _managedColorFilter = null;
skiaObject.setColorFilter(_ckColorFilter?.skiaObject); } else {
_managedColorFilter = _ManagedSkColorFilter(value as CkColorFilter);
}
skiaObject.setColorFilter(_managedColorFilter?.skiaObject);
} }
EngineColorFilter? _colorFilter; _ManagedSkColorFilter? _managedColorFilter;
CkColorFilter? _ckColorFilter;
@override @override
double get strokeMiterLimit => _strokeMiterLimit; double get strokeMiterLimit => _strokeMiterLimit;
...@@ -200,11 +202,14 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint { ...@@ -200,11 +202,14 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
if (_imageFilter == value) { if (_imageFilter == value) {
return; return;
} }
_imageFilter = value as CkImageFilter?;
skiaObject.setImageFilter(_imageFilter?.skiaObject); _imageFilter = value as _CkManagedSkImageFilterConvertible?;
_managedImageFilter = _imageFilter?._imageFilter;
skiaObject.setImageFilter(_managedImageFilter?.skiaObject);
} }
CkImageFilter? _imageFilter; _CkManagedSkImageFilterConvertible? _imageFilter;
ManagedSkiaObject<SkImageFilter>? _managedImageFilter;
@override @override
SkPaint createDefault() { SkPaint createDefault() {
...@@ -224,8 +229,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint { ...@@ -224,8 +229,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
paint.setColorInt(_color.value); paint.setColorInt(_color.value);
paint.setShader(_shader?.skiaObject); paint.setShader(_shader?.skiaObject);
paint.setMaskFilter(_ckMaskFilter?.skiaObject); paint.setMaskFilter(_ckMaskFilter?.skiaObject);
paint.setColorFilter(_ckColorFilter?.skiaObject); paint.setColorFilter(_managedColorFilter?.skiaObject);
paint.setImageFilter(_imageFilter?.skiaObject); paint.setImageFilter(_managedImageFilter?.skiaObject);
paint.setFilterQuality(toSkFilterQuality(_filterQuality)); paint.setFilterQuality(toSkFilterQuality(_filterQuality));
paint.setStrokeCap(toSkStrokeCap(_strokeCap)); paint.setStrokeCap(toSkStrokeCap(_strokeCap));
paint.setStrokeJoin(toSkStrokeJoin(_strokeJoin)); paint.setStrokeJoin(toSkStrokeJoin(_strokeJoin));
......
...@@ -21,11 +21,7 @@ class EngineColorFilter implements ui.ColorFilter { ...@@ -21,11 +21,7 @@ class EngineColorFilter implements ui.ColorFilter {
/// The output of this filter is then composited into the background according /// 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 /// to the [Paint.blendMode], using the output of this filter as the source
/// and the background as the destination. /// and the background as the destination.
const EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode) const factory EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode) = _CkBlendModeColorFilter;
: _color = color,
_blendMode = blendMode,
_matrix = null,
_type = _TypeMode;
/// Construct a color filter that transforms a color by a 5x5 matrix, where /// Construct a color filter that transforms a color by a 5x5 matrix, where
/// the fifth row is implicitly added in an identity configuration. /// the fifth row is implicitly added in an identity configuration.
...@@ -87,86 +83,13 @@ class EngineColorFilter implements ui.ColorFilter { ...@@ -87,86 +83,13 @@ class EngineColorFilter implements ui.ColorFilter {
/// 0, 0, 0, 1, 0, /// 0, 0, 0, 1, 0,
/// ]); /// ]);
/// ``` /// ```
const EngineColorFilter.matrix(List<double> matrix) const factory EngineColorFilter.matrix(List<double> matrix) = _CkMatrixColorFilter;
: _color = null,
_blendMode = null,
_matrix = matrix,
_type = _TypeMatrix;
/// Construct a color filter that applies the sRGB gamma curve to the RGB /// Construct a color filter that applies the sRGB gamma curve to the RGB
/// channels. /// channels.
const EngineColorFilter.linearToSrgbGamma() const factory EngineColorFilter.linearToSrgbGamma() = _CkLinearToSrgbGammaColorFilter;
: _color = null,
_blendMode = null,
_matrix = null,
_type = _TypeLinearToSrgbGamma;
/// Creates a color filter that applies the inverse of the sRGB gamma curve /// Creates a color filter that applies the inverse of the sRGB gamma curve
/// to the RGB channels. /// to the RGB channels.
const EngineColorFilter.srgbToLinearGamma() const factory EngineColorFilter.srgbToLinearGamma() = _CkSrgbToLinearGammaColorFilter;
: _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 CkColorFilter class to create for Skia.
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 ==(Object other) {
return other is EngineColorFilter
&& other._type == _type
&& _listEquals<double>(other._matrix, _matrix)
&& other._color == _color
&& other._blendMode == _blendMode;
}
CkColorFilter? _toCkColorFilter() {
switch (_type) {
case _TypeMode:
if (_color == null || _blendMode == null) {
return null;
}
return CkColorFilter.mode(this);
case _TypeMatrix:
if (_matrix == null) {
return null;
}
assert(_matrix!.length == 20, 'Color Matrix must have 20 entries.');
return CkColorFilter.matrix(this);
case _TypeLinearToSrgbGamma:
return CkColorFilter.linearToSrgbGamma(this);
case _TypeSrgbToLinearGamma:
return CkColorFilter.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.';
}
}
} }
...@@ -60,74 +60,71 @@ class PersistedColorFilter extends PersistedContainerSurface ...@@ -60,74 +60,71 @@ class PersistedColorFilter extends PersistedContainerSurface
childContainer?.style.visibility = 'visible'; childContainer?.style.visibility = 'visible';
return; return;
} }
if (engineValue._blendMode == null) {
rootElement!.style.backgroundColor = if (engineValue is! _CkBlendModeColorFilter) {
colorToCssString(engineValue._color!);
childContainer?.style.visibility = 'visible'; childContainer?.style.visibility = 'visible';
return; return;
} }
ui.Color filterColor = engineValue._color!; ui.Color filterColor = engineValue.color;
ui.BlendMode? colorFilterBlendMode = engineValue._blendMode; ui.BlendMode colorFilterBlendMode = engineValue.blendMode;
html.CssStyleDeclaration style = rootElement!.style; html.CssStyleDeclaration style = rootElement!.style;
if (colorFilterBlendMode != null) { switch (colorFilterBlendMode) {
switch (colorFilterBlendMode) { case ui.BlendMode.clear:
case ui.BlendMode.clear: case ui.BlendMode.dstOut:
case ui.BlendMode.dstOut: case ui.BlendMode.srcOut:
case ui.BlendMode.srcOut: childContainer?.style.visibility = 'hidden';
childContainer?.style.visibility = 'hidden'; return;
return; case ui.BlendMode.dst:
case ui.BlendMode.dst: case ui.BlendMode.dstIn:
case ui.BlendMode.dstIn: // Noop.
// Noop.
return;
case ui.BlendMode.src:
case ui.BlendMode.srcOver:
// Uses source filter color.
// Since we don't have a size, we can't use background color.
// Use svg filter srcIn instead.
colorFilterBlendMode = ui.BlendMode.srcIn;
break;
case ui.BlendMode.dstOver:
case ui.BlendMode.srcIn:
case ui.BlendMode.srcATop:
case ui.BlendMode.dstATop:
case ui.BlendMode.xor:
case ui.BlendMode.plus:
case ui.BlendMode.modulate:
case ui.BlendMode.screen:
case ui.BlendMode.overlay:
case ui.BlendMode.darken:
case ui.BlendMode.lighten:
case ui.BlendMode.colorDodge:
case ui.BlendMode.colorBurn:
case ui.BlendMode.hardLight:
case ui.BlendMode.softLight:
case ui.BlendMode.difference:
case ui.BlendMode.exclusion:
case ui.BlendMode.multiply:
case ui.BlendMode.hue:
case ui.BlendMode.saturation:
case ui.BlendMode.color:
case ui.BlendMode.luminosity:
break;
}
// Use SVG filter for blend mode.
String? svgFilter =
svgFilterFromBlendMode(filterColor, colorFilterBlendMode);
if (svgFilter != null) {
_filterElement =
html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer());
rootElement!.append(_filterElement!);
rootElement!.style.filter = 'url(#_fcf${_filterIdCounter})';
if (colorFilterBlendMode == ui.BlendMode.saturation ||
colorFilterBlendMode == ui.BlendMode.multiply ||
colorFilterBlendMode == ui.BlendMode.modulate) {
style.backgroundColor = colorToCssString(filterColor);
}
return; return;
case ui.BlendMode.src:
case ui.BlendMode.srcOver:
// Uses source filter color.
// Since we don't have a size, we can't use background color.
// Use svg filter srcIn instead.
colorFilterBlendMode = ui.BlendMode.srcIn;
break;
case ui.BlendMode.dstOver:
case ui.BlendMode.srcIn:
case ui.BlendMode.srcATop:
case ui.BlendMode.dstATop:
case ui.BlendMode.xor:
case ui.BlendMode.plus:
case ui.BlendMode.modulate:
case ui.BlendMode.screen:
case ui.BlendMode.overlay:
case ui.BlendMode.darken:
case ui.BlendMode.lighten:
case ui.BlendMode.colorDodge:
case ui.BlendMode.colorBurn:
case ui.BlendMode.hardLight:
case ui.BlendMode.softLight:
case ui.BlendMode.difference:
case ui.BlendMode.exclusion:
case ui.BlendMode.multiply:
case ui.BlendMode.hue:
case ui.BlendMode.saturation:
case ui.BlendMode.color:
case ui.BlendMode.luminosity:
break;
}
// Use SVG filter for blend mode.
String? svgFilter =
svgFilterFromBlendMode(filterColor, colorFilterBlendMode);
if (svgFilter != null) {
_filterElement =
html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer());
rootElement!.append(_filterElement!);
rootElement!.style.filter = 'url(#_fcf${_filterIdCounter})';
if (colorFilterBlendMode == ui.BlendMode.saturation ||
colorFilterBlendMode == ui.BlendMode.multiply ||
colorFilterBlendMode == ui.BlendMode.modulate) {
style.backgroundColor = colorToCssString(filterColor);
} }
return;
} }
} }
......
...@@ -406,6 +406,12 @@ class ImageFilter { ...@@ -406,6 +406,12 @@ class ImageFilter {
// if (matrix4.length != 16) // if (matrix4.length != 16)
// throw ArgumentError('"matrix4" must have 16 entries.'); // throw ArgumentError('"matrix4" must have 16 entries.');
} }
ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) {
// TODO(flutter_web): add implementation.
throw UnimplementedError(
'ImageFilter.compose not implemented for web platform.');
}
} }
enum ImageByteFormat { enum ImageByteFormat {
......
...@@ -474,6 +474,26 @@ void _imageFilterTests() { ...@@ -474,6 +474,26 @@ void _imageFilterTests() {
isNotNull, isNotNull,
); );
}); });
test('MakeColorFilter', () {
expect(
canvasKit.SkImageFilter.MakeColorFilter(
canvasKit.SkColorFilter.MakeLinearToSRGBGamma(),
null,
),
isNotNull,
);
});
test('MakeCompose', () {
expect(
canvasKit.SkImageFilter.MakeCompose(
canvasKit.SkImageFilter.MakeBlur(1, 2, canvasKit.TileMode.Repeat, null),
canvasKit.SkImageFilter.MakeBlur(1, 2, canvasKit.TileMode.Repeat, null),
),
isNotNull,
);
});
} }
void _mallocTests() { void _mallocTests() {
......
// 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.
// @dart = 2.6
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'common.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
List<CkColorFilter> createColorFilters() {
return <CkColorFilter>[
EngineColorFilter.mode(ui.Color(0x12345678), ui.BlendMode.srcOver) as CkColorFilter,
EngineColorFilter.mode(ui.Color(0x12345678), ui.BlendMode.dstOver) as CkColorFilter,
EngineColorFilter.mode(ui.Color(0x87654321), ui.BlendMode.dstOver) as CkColorFilter,
EngineColorFilter.matrix(<double>[
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
]) as CkColorFilter,
EngineColorFilter.matrix(Float32List.fromList(<double>[
2, 0, 0, 0, 0,
0, 2, 0, 0, 0,
0, 0, 2, 0, 0,
0, 0, 0, 2, 0,
])) as CkColorFilter,
EngineColorFilter.linearToSrgbGamma() as CkColorFilter,
EngineColorFilter.srgbToLinearGamma() as CkColorFilter,
];
}
List<CkImageFilter> createImageFilters() {
return <CkImageFilter>[
CkImageFilter.blur(sigmaX: 5, sigmaY: 6),
CkImageFilter.blur(sigmaX: 6, sigmaY: 5),
for (final CkColorFilter colorFilter in createColorFilters()) CkImageFilter.color(colorFilter: colorFilter),
];
}
group('ImageFilters', () {
setUpAll(() async {
await ui.webOnlyInitializePlatform();
});
test('can be constructed', () {
final CkImageFilter imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10);
expect(imageFilter, isA<CkImageFilter>());
expect(imageFilter.createDefault(), isNotNull);
expect(imageFilter.resurrect(), isNotNull);
});
test('== operator', () {
final List<ui.ImageFilter> filters1 = <ui.ImageFilter>[
...createImageFilters(),
...createColorFilters(),
];
final List<ui.ImageFilter> filters2 = <ui.ImageFilter>[
...createImageFilters(),
...createColorFilters(),
];
for (int index1 = 0; index1 < filters1.length; index1 += 1) {
final ui.ImageFilter imageFilter1 = filters1[index1];
expect(imageFilter1 == imageFilter1, isTrue);
for (int index2 = 0; index2 < filters2.length; index2 += 1) {
final ui.ImageFilter imageFilter2 = filters2[index2];
expect(imageFilter1 == imageFilter2, imageFilter2 == imageFilter1);
expect(imageFilter1 == imageFilter2, index1 == index2);
}
}
});
test('reuses the Skia filter', () {
final CkPaint paint = CkPaint();
paint.imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10);
final ManagedSkiaObject managedFilter = paint.imageFilter as ManagedSkiaObject;
final Object skiaFilter = managedFilter?.skiaObject;
paint.imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10);
expect((paint.imageFilter as ManagedSkiaObject).skiaObject, same(skiaFilter));
});
// TODO: https://github.com/flutter/flutter/issues/60040
}, skip: isIosSafari);
}
...@@ -8,6 +8,7 @@ import 'dart:ui'; ...@@ -8,6 +8,7 @@ import 'dart:ui';
import 'package:test/test.dart'; import 'package:test/test.dart';
const Color red = Color(0xFFAA0000);
const Color green = Color(0xFF00AA00); const Color green = Color(0xFF00AA00);
const int greenCenterBlurred = 0x1C001300; const int greenCenterBlurred = 0x1C001300;
...@@ -18,6 +19,34 @@ const int greenCenterScaled = 0xFF00AA00; ...@@ -18,6 +19,34 @@ const int greenCenterScaled = 0xFF00AA00;
const int greenSideScaled = 0x80005500; const int greenSideScaled = 0x80005500;
const int greenCornerScaled = 0x40002B00; const int greenCornerScaled = 0x40002B00;
const List<double> grayscaleColorMatrix = <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 List<double> identityColorMatrix = <double>[
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const List<double> constValueColorMatrix = <double>[
0, 0, 0, 0, 2,
0, 0, 0, 0, 2,
0, 0, 0, 0, 2,
0, 0, 0, 0, 255,
];
const List<double> halvesBrightnessColorMatrix = <double>[
0.5, 0, 0, 0, 0,
0, 0.5, 0, 0, 0,
0, 0, 0.5, 0, 0,
0, 0, 0, 1, 0,
];
void main() { void main() {
Future<Uint32List> getBytesForPaint(Paint paint, {int width = 3, int height = 3}) async { Future<Uint32List> getBytesForPaint(Paint paint, {int width = 3, int height = 3}) async {
final PictureRecorder recorder = PictureRecorder(); final PictureRecorder recorder = PictureRecorder();
...@@ -31,6 +60,18 @@ void main() { ...@@ -31,6 +60,18 @@ void main() {
return bytes.buffer.asUint32List(); return bytes.buffer.asUint32List();
} }
Future<Uint32List> getBytesForColorPaint(Paint paint, {int width = 1, int height = 1}) async {
final PictureRecorder recorder = PictureRecorder();
final Canvas recorderCanvas = Canvas(recorder);
recorderCanvas.drawPaint(paint);
final Picture picture = recorder.endRecording();
final Image image = await picture.toImage(width, height);
final ByteData bytes = await image.toByteData();
expect(bytes.lengthInBytes, width * height * 4);
return bytes.buffer.asUint32List();
}
ImageFilter makeBlur(double sigmaX, double sigmaY) => ImageFilter makeBlur(double sigmaX, double sigmaY) =>
ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY); ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
...@@ -47,6 +88,20 @@ void main() { ...@@ -47,6 +88,20 @@ void main() {
]), filterQuality: quality); ]), filterQuality: quality);
} }
List<ColorFilter> colorFilters() {
// Create new color filter instances on each invocation.
return <ColorFilter> [ // ignore: prefer_const_constructors
ColorFilter.mode(null, null), // ignore: prefer_const_constructors
ColorFilter.mode(green, BlendMode.color), // ignore: prefer_const_constructors
ColorFilter.mode(red, BlendMode.color), // ignore: prefer_const_constructors
ColorFilter.mode(red, BlendMode.screen), // ignore: prefer_const_constructors
ColorFilter.matrix(null), // ignore: prefer_const_constructors
ColorFilter.matrix(grayscaleColorMatrix), // ignore: prefer_const_constructors
ColorFilter.linearToSrgbGamma(), // ignore: prefer_const_constructors
ColorFilter.srgbToLinearGamma(), // ignore: prefer_const_constructors
];
}
List<ImageFilter> makeList() { List<ImageFilter> makeList() {
return <ImageFilter>[ return <ImageFilter>[
makeBlur(10.0, 10.0), makeBlur(10.0, 10.0),
...@@ -59,6 +114,7 @@ void main() { ...@@ -59,6 +114,7 @@ void main() {
makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.medium), makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.medium),
makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.high), makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.high),
makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.none), makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.none),
...colorFilters(),
]; ];
} }
...@@ -78,12 +134,19 @@ void main() { ...@@ -78,12 +134,19 @@ void main() {
} }
} }
List<ImageFilter> composed(List<ImageFilter> a, List<ImageFilter> b) {
return <ImageFilter>[for (final ImageFilter x in a) for (final ImageFilter y in b) ImageFilter.compose(outer: x, inner: y)];
}
test('ImageFilter - equals', () async { test('ImageFilter - equals', () async {
final List<ImageFilter> A = makeList(); final List<ImageFilter> A = makeList();
final List<ImageFilter> B = makeList(); final List<ImageFilter> B = makeList();
checkEquality(A, A); checkEquality(A, A);
checkEquality(A, B); checkEquality(A, B);
checkEquality(B, B); checkEquality(B, B);
checkEquality(composed(A, A), composed(A, A));
checkEquality(composed(A, B), composed(B, A));
checkEquality(composed(B, B), composed(B, B));
}); });
void checkBytes(Uint32List bytes, int center, int side, int corner) { void checkBytes(Uint32List bytes, int center, int side, int corner) {
...@@ -117,4 +180,117 @@ void main() { ...@@ -117,4 +180,117 @@ void main() {
final Uint32List bytes = await getBytesForPaint(paint); final Uint32List bytes = await getBytesForPaint(paint);
checkBytes(bytes, greenCenterScaled, greenSideScaled, greenCornerScaled); checkBytes(bytes, greenCenterScaled, greenSideScaled, greenCornerScaled);
}); });
test('ImageFilter - matrix: copies the list', () async {
final Float64List matrix = Float64List.fromList(<double>[
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
]);
final ImageFilter filter = ImageFilter.matrix(matrix);
final String originalDescription = filter.toString();
// Modify the matrix.
matrix[0] = 12345;
expect(filter.toString(), contains('[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]'));
expect(filter.toString(), originalDescription);
});
test('ImageFilter - null color filters do not throw', () {
dynamic error;
final Paint paint = Paint();
try {
paint
..color = green
..imageFilter = const ColorFilter.mode(null, null);
} catch (e) {
error = e;
}
expect(error, isNull);
});
test('ImageFilter - from color filters', () async {
final Paint paint = Paint()
..color = green
..imageFilter = const ColorFilter.matrix(constValueColorMatrix);
final Uint32List bytes = await getBytesForColorPaint(paint);
expect(bytes[0], 0xFF020202);
});
test('ImageFilter - null filter composition', () async {
const ImageFilter nullFilter = ColorFilter.mode(null, null);
const ImageFilter identityFilter = ColorFilter.matrix(identityColorMatrix);
// Verify that null filter == identity.
Future<void> verifyAgainst(ImageFilter filter) async {
final ImageFilter comp0 = ImageFilter.compose(outer: filter, inner: identityFilter);
final ImageFilter comp1 = ImageFilter.compose(outer: filter, inner: nullFilter);
final ImageFilter comp2 = ImageFilter.compose(outer: nullFilter, inner: filter);
final Paint paint = Paint()..color = green;
paint.imageFilter = comp0;
final Uint32List bytes = await getBytesForColorPaint(paint);
paint.imageFilter = comp1;
expect(bytes, equals(await getBytesForColorPaint(paint)));
paint.imageFilter = comp2;
expect(bytes, equals(await getBytesForColorPaint(paint)));
}
makeList().forEach(verifyAgainst);
});
test('ImageFilter - color filter composition', () async {
final ImageFilter compOrder1 = ImageFilter.compose(
outer: const ColorFilter.matrix(halvesBrightnessColorMatrix),
inner: const ColorFilter.matrix(constValueColorMatrix),
);
final ImageFilter compOrder2 = ImageFilter.compose(
outer: const ColorFilter.matrix(constValueColorMatrix),
inner: const ColorFilter.matrix(halvesBrightnessColorMatrix),
);
final Paint paint = Paint()
..color = green
..imageFilter = compOrder1;
Uint32List bytes = await getBytesForColorPaint(paint);
expect(bytes[0], 0xFF010101);
paint
..color = green
..imageFilter = compOrder2;
bytes = await getBytesForColorPaint(paint);
expect(bytes[0], 0xFF020202);
});
test('Composite ImageFilter toString', () {
expect(
ImageFilter.compose(outer: makeBlur(20.0, 20.0), inner: makeBlur(10.0, 10.0)).toString(),
contains('blur(10.0, 10.0) -> blur(20.0, 20.0)'),
);
// Produces a flat list of filters
expect(
ImageFilter.compose(
outer: ImageFilter.compose(outer: makeBlur(30.0, 30.0), inner: makeBlur(20.0, 20.0)),
inner: ImageFilter.compose(
outer: const ColorFilter.mode(null, null),
inner: makeScale(10.0, 10.0),
),
).toString(),
contains(
'matrix([10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.0, -0.0, 0.0, 1.0], FilterQuality.low) -> '
'ColorFilter.mode(null, null) -> '
'blur(20.0, 20.0) -> '
'blur(30.0, 30.0)'
),
);
});
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册