mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Exposing ColorFilter to ImageFilter conversion and Compose() (flutter/engine#20309)
This commit is contained in:
parent
5cd8bfaa81
commit
465c84321e
@ -2900,7 +2900,7 @@ class MaskFilter {
|
||||
///
|
||||
/// Instances of this class are used with [Paint.colorFilter] on [Paint]
|
||||
/// objects.
|
||||
class ColorFilter {
|
||||
class ColorFilter implements ImageFilter {
|
||||
/// 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.
|
||||
@ -2912,7 +2912,7 @@ class ColorFilter {
|
||||
: _color = color,
|
||||
_blendMode = blendMode,
|
||||
_matrix = null,
|
||||
_type = _TypeMode;
|
||||
_type = _kTypeMode;
|
||||
|
||||
/// Construct a color filter that transforms a color by a 5x5 matrix, where
|
||||
/// the fifth row is implicitly added in an identity configuration.
|
||||
@ -2978,7 +2978,7 @@ class ColorFilter {
|
||||
: _color = null,
|
||||
_blendMode = null,
|
||||
_matrix = matrix,
|
||||
_type = _TypeMatrix;
|
||||
_type = _kTypeMatrix;
|
||||
|
||||
/// Construct a color filter that applies the sRGB gamma curve to the RGB
|
||||
/// channels.
|
||||
@ -2986,7 +2986,7 @@ class ColorFilter {
|
||||
: _color = null,
|
||||
_blendMode = null,
|
||||
_matrix = null,
|
||||
_type = _TypeLinearToSrgbGamma;
|
||||
_type = _kTypeLinearToSrgbGamma;
|
||||
|
||||
/// Creates a color filter that applies the inverse of the sRGB gamma curve
|
||||
/// to the RGB channels.
|
||||
@ -2994,7 +2994,7 @@ class ColorFilter {
|
||||
: _color = null,
|
||||
_blendMode = null,
|
||||
_matrix = null,
|
||||
_type = _TypeSrgbToLinearGamma;
|
||||
_type = _kTypeSrgbToLinearGamma;
|
||||
|
||||
final Color? _color;
|
||||
final BlendMode? _blendMode;
|
||||
@ -3002,55 +3002,77 @@ class ColorFilter {
|
||||
final int _type;
|
||||
|
||||
// The type of SkColorFilter 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
|
||||
static const int _kTypeMode = 1; // MakeModeFilter
|
||||
static const int _kTypeMatrix = 2; // MakeMatrixFilterRowMajor255
|
||||
static const int _kTypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma
|
||||
static const int _kTypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma
|
||||
|
||||
// SkImageFilters::ColorFilter
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ColorFilter
|
||||
&& other._type == _type
|
||||
&& _listEquals<double>(other._matrix, _matrix)
|
||||
&& other._color == _color
|
||||
&& other._blendMode == _blendMode;
|
||||
}
|
||||
_ImageFilter _toNativeImageFilter() => _ImageFilter.fromColorFilter(this);
|
||||
|
||||
_ColorFilter? _toNativeColorFilter() {
|
||||
switch (_type) {
|
||||
case _TypeMode:
|
||||
case _kTypeMode:
|
||||
if (_color == null || _blendMode == null) {
|
||||
return null;
|
||||
}
|
||||
return _ColorFilter.mode(this);
|
||||
case _TypeMatrix:
|
||||
case _kTypeMatrix:
|
||||
if (_matrix == null) {
|
||||
return null;
|
||||
}
|
||||
assert(_matrix!.length == 20, 'Color Matrix must have 20 entries.');
|
||||
return _ColorFilter.matrix(this);
|
||||
case _TypeLinearToSrgbGamma:
|
||||
case _kTypeLinearToSrgbGamma:
|
||||
return _ColorFilter.linearToSrgbGamma(this);
|
||||
case _TypeSrgbToLinearGamma:
|
||||
case _kTypeSrgbToLinearGamma:
|
||||
return _ColorFilter.srgbToLinearGamma(this);
|
||||
default:
|
||||
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
|
||||
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
|
||||
String toString() {
|
||||
switch (_type) {
|
||||
case _TypeMode:
|
||||
case _kTypeMode:
|
||||
return 'ColorFilter.mode($_color, $_blendMode)';
|
||||
case _TypeMatrix:
|
||||
case _kTypeMatrix:
|
||||
return 'ColorFilter.matrix($_matrix)';
|
||||
case _TypeLinearToSrgbGamma:
|
||||
case _kTypeLinearToSrgbGamma:
|
||||
return 'ColorFilter.linearToSrgbGamma()';
|
||||
case _TypeSrgbToLinearGamma:
|
||||
case _kTypeSrgbToLinearGamma:
|
||||
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.';
|
||||
@ -3067,27 +3089,27 @@ class ColorFilter {
|
||||
class _ColorFilter extends NativeFieldWrapperClass2 {
|
||||
_ColorFilter.mode(this.creator)
|
||||
: assert(creator != null), // ignore: unnecessary_null_comparison
|
||||
assert(creator._type == ColorFilter._TypeMode) {
|
||||
assert(creator._type == ColorFilter._kTypeMode) {
|
||||
_constructor();
|
||||
_initMode(creator._color!.value, creator._blendMode!.index);
|
||||
}
|
||||
|
||||
_ColorFilter.matrix(this.creator)
|
||||
: assert(creator != null), // ignore: unnecessary_null_comparison
|
||||
assert(creator._type == ColorFilter._TypeMatrix) {
|
||||
assert(creator._type == ColorFilter._kTypeMatrix) {
|
||||
_constructor();
|
||||
_initMatrix(Float32List.fromList(creator._matrix!));
|
||||
}
|
||||
_ColorFilter.linearToSrgbGamma(this.creator)
|
||||
: assert(creator != null), // ignore: unnecessary_null_comparison
|
||||
assert(creator._type == ColorFilter._TypeLinearToSrgbGamma) {
|
||||
assert(creator._type == ColorFilter._kTypeLinearToSrgbGamma) {
|
||||
_constructor();
|
||||
_initLinearToSrgbGamma();
|
||||
}
|
||||
|
||||
_ColorFilter.srgbToLinearGamma(this.creator)
|
||||
: assert(creator != null), // ignore: unnecessary_null_comparison
|
||||
assert(creator._type == ColorFilter._TypeSrgbToLinearGamma) {
|
||||
assert(creator._type == ColorFilter._kTypeSrgbToLinearGamma) {
|
||||
_constructor();
|
||||
_initSrgbToLinearGamma();
|
||||
}
|
||||
@ -3113,80 +3135,134 @@ class _ColorFilter extends NativeFieldWrapperClass2 {
|
||||
/// this class as a backdrop filter.
|
||||
/// * [SceneBuilder.pushImageFilter], which is the low-level API for using
|
||||
/// this class as a child layer filter.
|
||||
class ImageFilter {
|
||||
abstract class ImageFilter {
|
||||
/// Creates an image filter that applies a Gaussian blur.
|
||||
ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 })
|
||||
: assert(sigmaX != null), // ignore: unnecessary_null_comparison
|
||||
assert(sigmaY != null), // ignore: unnecessary_null_comparison
|
||||
_data = _makeList(sigmaX, sigmaY),
|
||||
_filterQuality = null,
|
||||
_type = _kTypeBlur;
|
||||
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) {
|
||||
assert(sigmaX != null); // ignore: unnecessary_null_comparison
|
||||
assert(sigmaY != null); // ignore: unnecessary_null_comparison
|
||||
return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY);
|
||||
}
|
||||
|
||||
/// Creates an image filter that applies a matrix transformation.
|
||||
///
|
||||
/// For example, applying a positive scale matrix (see [Matrix4.diagonal3])
|
||||
/// when used with [BackdropFilter] would magnify the background image.
|
||||
ImageFilter.matrix(Float64List matrix4,
|
||||
{ FilterQuality filterQuality = FilterQuality.low })
|
||||
: assert(matrix4 != null), // ignore: unnecessary_null_comparison
|
||||
_data = Float64List.fromList(matrix4),
|
||||
_filterQuality = filterQuality,
|
||||
_type = _kTypeMatrix {
|
||||
factory ImageFilter.matrix(Float64List matrix4,
|
||||
{ FilterQuality filterQuality = FilterQuality.low }) {
|
||||
assert(matrix4 != null); // ignore: unnecessary_null_comparison
|
||||
assert(filterQuality != null); // ignore: unnecessary_null_comparison
|
||||
if (matrix4.length != 16)
|
||||
throw ArgumentError('"matrix4" must have 16 entries.');
|
||||
return _MatrixImageFilter(data: Float64List.fromList(matrix4), filterQuality: filterQuality);
|
||||
}
|
||||
|
||||
static Float64List _makeList(double a, double b) {
|
||||
final Float64List list = Float64List(2);
|
||||
list[0] = a;
|
||||
list[1] = b;
|
||||
return list;
|
||||
/// Composes the `inner` filter with `outer`, to combine their effects.
|
||||
///
|
||||
/// Creates a single [ImageFilter] that when applied, has the same effect as
|
||||
/// subsequently applying `inner` and `outer`, i.e.,
|
||||
/// 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;
|
||||
final FilterQuality? _filterQuality;
|
||||
final int _type;
|
||||
_ImageFilter? _nativeFilter;
|
||||
// Converts this to a native SkImageFilter. See the comments of this method in
|
||||
// subclasses for the exact type of SkImageFilter this method converts to.
|
||||
_ImageFilter _toNativeImageFilter();
|
||||
|
||||
// The type of SkImageFilter class to create for Skia.
|
||||
static const int _kTypeBlur = 0; // MakeBlurFilter
|
||||
static const int _kTypeMatrix = 1; // MakeMatrixFilterRowMajor255
|
||||
// The description text to show when the filter is part of a composite
|
||||
// [ImageFilter] created using [ImageFilter.compose].
|
||||
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
|
||||
bool operator ==(Object other) {
|
||||
return other is ImageFilter
|
||||
&& other._type == _type
|
||||
&& _listEquals<double>(other._data, _data)
|
||||
&& other._filterQuality == _filterQuality;
|
||||
}
|
||||
|
||||
_ImageFilter _toNativeImageFilter() => _nativeFilter ??= _makeNativeImageFilter();
|
||||
|
||||
_ImageFilter _makeNativeImageFilter() {
|
||||
switch (_type) {
|
||||
case _kTypeBlur:
|
||||
return _ImageFilter.blur(this);
|
||||
case _kTypeMatrix:
|
||||
return _ImageFilter.matrix(this);
|
||||
default:
|
||||
throw StateError('Unknown mode $_type for ImageFilter.');
|
||||
}
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
return other is _MatrixImageFilter
|
||||
&& other.filterQuality == filterQuality
|
||||
&& _listEquals<double>(other.data, data);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(_filterQuality, hashList(_data), _type);
|
||||
int get hashCode => hashValues(filterQuality, hashList(data));
|
||||
}
|
||||
|
||||
class _GaussianBlurImageFilter implements ImageFilter {
|
||||
_GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY });
|
||||
|
||||
final double sigmaX;
|
||||
final double sigmaY;
|
||||
|
||||
// MakeBlurFilter
|
||||
late final _ImageFilter nativeFilter = _ImageFilter.blur(this);
|
||||
@override
|
||||
_ImageFilter _toNativeImageFilter() => nativeFilter;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (_type) {
|
||||
case _kTypeBlur:
|
||||
return 'ImageFilter.blur(${_data[0]}, ${_data[1]})';
|
||||
case _kTypeMatrix:
|
||||
return 'ImageFilter.matrix($_data, $_filterQuality)';
|
||||
default:
|
||||
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.';
|
||||
}
|
||||
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
|
||||
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
|
||||
_ImageFilter _toNativeImageFilter() => nativeFilter;
|
||||
|
||||
@override
|
||||
String get _shortDescription => '${innerFilter._shortDescription} -> ${outerFilter._shortDescription}';
|
||||
|
||||
@override
|
||||
String toString() => 'ImageFilter.compose(source -> $_shortDescription -> result)';
|
||||
|
||||
@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.
|
||||
@ -3198,11 +3274,11 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
|
||||
void _constructor() native 'ImageFilter_constructor';
|
||||
|
||||
/// Creates an image filter that applies a Gaussian blur.
|
||||
_ImageFilter.blur(this.creator)
|
||||
: assert(creator != null), // ignore: unnecessary_null_comparison
|
||||
assert(creator._type == ImageFilter._kTypeBlur) {
|
||||
_ImageFilter.blur(_GaussianBlurImageFilter filter)
|
||||
: assert(filter != null), // ignore: unnecessary_null_comparison
|
||||
creator = filter { // ignore: prefer_initializing_formals
|
||||
_constructor();
|
||||
_initBlur(creator._data[0], creator._data[1]);
|
||||
_initBlur(filter.sigmaX, filter.sigmaY);
|
||||
}
|
||||
void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur';
|
||||
|
||||
@ -3210,16 +3286,36 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// For example, applying a positive scale matrix (see [Matrix4.diagonal3])
|
||||
/// when used with [BackdropFilter] would magnify the background image.
|
||||
_ImageFilter.matrix(this.creator)
|
||||
: assert(creator != null), // ignore: unnecessary_null_comparison
|
||||
assert(creator._type == ImageFilter._kTypeMatrix) {
|
||||
if (creator._data.length != 16)
|
||||
_ImageFilter.matrix(_MatrixImageFilter filter)
|
||||
: assert(filter != null), // ignore: unnecessary_null_comparison
|
||||
creator = filter { // ignore: prefer_initializing_formals
|
||||
if (filter.data.length != 16)
|
||||
throw ArgumentError('"matrix4" must have 16 entries.');
|
||||
_constructor();
|
||||
_initMatrix(creator._data, creator._filterQuality!.index);
|
||||
_initMatrix(filter.data, filter.filterQuality.index);
|
||||
}
|
||||
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 values used for the filter.
|
||||
final ImageFilter creator;
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include "flutter/lib/ui/painting/matrix.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/SkPictureImageFilter.h"
|
||||
#include "third_party/tonic/converter/dart_converter.h"
|
||||
@ -22,11 +23,13 @@ static void ImageFilter_constructor(Dart_NativeArguments args) {
|
||||
|
||||
IMPLEMENT_WRAPPERTYPEINFO(ui, ImageFilter);
|
||||
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(ImageFilter, initImage) \
|
||||
V(ImageFilter, initPicture) \
|
||||
V(ImageFilter, initBlur) \
|
||||
V(ImageFilter, initMatrix)
|
||||
#define FOR_EACH_BINDING(V) \
|
||||
V(ImageFilter, initImage) \
|
||||
V(ImageFilter, initPicture) \
|
||||
V(ImageFilter, initBlur) \
|
||||
V(ImageFilter, initMatrix) \
|
||||
V(ImageFilter, initColorFilter) \
|
||||
V(ImageFilter, initComposeFilter)
|
||||
|
||||
FOR_EACH_BINDING(DART_NATIVE_CALLBACK)
|
||||
|
||||
@ -64,4 +67,14 @@ void ImageFilter::initMatrix(const tonic::Float64List& matrix4,
|
||||
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
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#define FLUTTER_LIB_UI_PAINTING_IMAGE_FILTER_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/picture.h"
|
||||
#include "third_party/skia/include/core/SkImageFilter.h"
|
||||
@ -25,6 +26,8 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {
|
||||
void initPicture(Picture*);
|
||||
void initBlur(double sigma_x, double sigma_y);
|
||||
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_; }
|
||||
|
||||
|
||||
@ -573,15 +573,10 @@ class BitmapCanvas extends EngineCanvas {
|
||||
ui.Image image, ui.Offset p, SurfacePaintData paint) {
|
||||
final HtmlImage htmlImage = image as HtmlImage;
|
||||
final ui.BlendMode? blendMode = paint.blendMode;
|
||||
final EngineColorFilter? colorFilter =
|
||||
paint.colorFilter as EngineColorFilter?;
|
||||
final ui.BlendMode? colorFilterBlendMode = colorFilter?._blendMode;
|
||||
final EngineColorFilter? colorFilter = paint.colorFilter as EngineColorFilter?;
|
||||
html.HtmlElement imgElement;
|
||||
if (colorFilterBlendMode == null) {
|
||||
// No Blending, create an image by cloning original loaded image.
|
||||
imgElement = _reuseOrCreateImage(htmlImage);
|
||||
} else {
|
||||
switch (colorFilterBlendMode) {
|
||||
if (colorFilter is _CkBlendModeColorFilter) {
|
||||
switch (colorFilter.blendMode) {
|
||||
case ui.BlendMode.colorBurn:
|
||||
case ui.BlendMode.colorDodge:
|
||||
case ui.BlendMode.hue:
|
||||
@ -595,14 +590,17 @@ class BitmapCanvas extends EngineCanvas {
|
||||
case ui.BlendMode.color:
|
||||
case ui.BlendMode.luminosity:
|
||||
case ui.BlendMode.xor:
|
||||
imgElement = _createImageElementWithSvgFilter(
|
||||
image, colorFilter!._color, colorFilterBlendMode, paint);
|
||||
imgElement = _createImageElementWithSvgFilter(image,
|
||||
colorFilter.color, colorFilter.blendMode, paint);
|
||||
break;
|
||||
default:
|
||||
imgElement = _createBackgroundImageWithBlend(
|
||||
image, colorFilter!._color, colorFilterBlendMode, paint);
|
||||
imgElement = _createBackgroundImageWithBlend(image,
|
||||
colorFilter.color, colorFilter.blendMode, paint);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No Blending, create an image by cloning original loaded image.
|
||||
imgElement = _reuseOrCreateImage(htmlImage);
|
||||
}
|
||||
imgElement.style.mixBlendMode = _stringForBlendMode(blendMode) ?? '';
|
||||
if (_canvasPool.isClipped) {
|
||||
|
||||
@ -241,11 +241,11 @@ class CkCanvas {
|
||||
}
|
||||
|
||||
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
|
||||
final CkImageFilter skImageFilter = filter as CkImageFilter;
|
||||
final _CkManagedSkImageFilterConvertible convertible = filter as _CkManagedSkImageFilterConvertible;
|
||||
return skCanvas.saveLayer(
|
||||
null,
|
||||
toSkRect(bounds),
|
||||
skImageFilter.skiaObject,
|
||||
convertible._imageFilter.skiaObject,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
@ -823,6 +823,16 @@ class SkImageFilterNamespace {
|
||||
SkFilterQuality filterQuality,
|
||||
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()
|
||||
|
||||
@ -5,62 +5,147 @@
|
||||
// @dart = 2.10
|
||||
part of engine;
|
||||
|
||||
/// A [ui.ColorFilter] backed by Skia's [CkColorFilter].
|
||||
class CkColorFilter extends ManagedSkiaObject<SkColorFilter> {
|
||||
final EngineColorFilter _engineFilter;
|
||||
/// A concrete [ManagedSkiaObject] subclass that owns a [SkColorFilter] and
|
||||
/// manages its lifecycle.
|
||||
///
|
||||
/// Seealso:
|
||||
///
|
||||
/// * [CkPaint.colorFilter], which uses a [_ManagedSkColorFilter] to manage
|
||||
/// the lifecycle of its [SkColorFilter].
|
||||
class _ManagedSkColorFilter extends ManagedSkiaObject<SkColorFilter> {
|
||||
_ManagedSkColorFilter(CkColorFilter ckColorFilter)
|
||||
: this.ckColorFilter = ckColorFilter;
|
||||
|
||||
CkColorFilter.mode(EngineColorFilter filter) : _engineFilter = filter;
|
||||
|
||||
CkColorFilter.matrix(EngineColorFilter filter) : _engineFilter = filter;
|
||||
|
||||
CkColorFilter.linearToSrgbGamma(EngineColorFilter filter)
|
||||
: _engineFilter = filter;
|
||||
|
||||
CkColorFilter.srgbToLinearGamma(EngineColorFilter filter)
|
||||
: _engineFilter = filter;
|
||||
|
||||
SkColorFilter _createSkiaObjectFromFilter() {
|
||||
SkColorFilter skColorFilter;
|
||||
switch (_engineFilter._type) {
|
||||
case EngineColorFilter._TypeMode:
|
||||
skColorFilter = canvasKit.SkColorFilter.MakeBlend(
|
||||
toSharedSkColor1(_engineFilter._color!),
|
||||
toSkBlendMode(_engineFilter._blendMode!),
|
||||
);
|
||||
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;
|
||||
}
|
||||
final CkColorFilter ckColorFilter;
|
||||
|
||||
@override
|
||||
SkColorFilter createDefault() {
|
||||
return _createSkiaObjectFromFilter();
|
||||
}
|
||||
SkColorFilter createDefault() => ckColorFilter._initRawColorFilter();
|
||||
|
||||
@override
|
||||
SkColorFilter resurrect() {
|
||||
return _createSkiaObjectFromFilter();
|
||||
}
|
||||
SkColorFilter resurrect() => ckColorFilter._initRawColorFilter();
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
rawSkiaObject?.delete();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => ckColorFilter.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
return other is _ManagedSkColorFilter
|
||||
&& other.ckColorFilter == ckColorFilter;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => ckColorFilter.toString();
|
||||
}
|
||||
|
||||
/// 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
|
||||
int get hashCode => ui.hashValues(color, blendMode);
|
||||
|
||||
@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 @@
|
||||
// @dart = 2.10
|
||||
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].
|
||||
///
|
||||
/// Currently only supports `blur`.
|
||||
class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.ImageFilter {
|
||||
CkImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0})
|
||||
: _sigmaX = sigmaX,
|
||||
_sigmaY = sigmaY;
|
||||
abstract class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements _CkManagedSkImageFilterConvertible<SkImageFilter> {
|
||||
factory CkImageFilter.blur({ required double sigmaX, required double sigmaY }) = _CkBlurImageFilter;
|
||||
factory CkImageFilter.color({ required CkColorFilter colorFilter }) = _CkColorFilterImageFilter;
|
||||
|
||||
final double _sigmaX;
|
||||
final double _sigmaY;
|
||||
CkImageFilter._();
|
||||
|
||||
@override
|
||||
ManagedSkiaObject<SkImageFilter> get _imageFilter => this;
|
||||
|
||||
SkImageFilter _initSkiaObject();
|
||||
|
||||
@override
|
||||
SkImageFilter createDefault() => _initSkiaObject();
|
||||
@ -26,11 +40,42 @@ class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.Image
|
||||
void 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() {
|
||||
return canvasKit.SkImageFilter.MakeBlur(
|
||||
_sigmaX,
|
||||
_sigmaY,
|
||||
sigmaX,
|
||||
sigmaY,
|
||||
canvasKit.TileMode.Clamp,
|
||||
null,
|
||||
);
|
||||
@ -38,16 +83,19 @@ class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements ui.Image
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is CkImageFilter
|
||||
&& other._sigmaX == _sigmaX
|
||||
&& other._sigmaY == _sigmaY;
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
return other is _CkBlurImageFilter
|
||||
&& other.sigmaX == sigmaX
|
||||
&& other.sigmaY == sigmaY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => ui.hashValues(_sigmaX, _sigmaY);
|
||||
int get hashCode => ui.hashValues(sigmaX, sigmaY);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ImageFilter.blur($_sigmaX, $_sigmaY)';
|
||||
return 'ImageFilter.blur($sigmaX, $sigmaY)';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -165,20 +165,22 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
|
||||
ui.FilterQuality _filterQuality = ui.FilterQuality.none;
|
||||
|
||||
@override
|
||||
ui.ColorFilter? get colorFilter => _colorFilter;
|
||||
ui.ColorFilter? get colorFilter => _managedColorFilter?.ckColorFilter;
|
||||
@override
|
||||
set colorFilter(ui.ColorFilter? value) {
|
||||
if (_colorFilter == value) {
|
||||
if (colorFilter == value) {
|
||||
return;
|
||||
}
|
||||
final EngineColorFilter? engineValue = value as EngineColorFilter?;
|
||||
_colorFilter = engineValue;
|
||||
_ckColorFilter = engineValue?._toCkColorFilter();
|
||||
skiaObject.setColorFilter(_ckColorFilter?.skiaObject);
|
||||
|
||||
if (value == null) {
|
||||
_managedColorFilter = null;
|
||||
} else {
|
||||
_managedColorFilter = _ManagedSkColorFilter(value as CkColorFilter);
|
||||
}
|
||||
skiaObject.setColorFilter(_managedColorFilter?.skiaObject);
|
||||
}
|
||||
|
||||
EngineColorFilter? _colorFilter;
|
||||
CkColorFilter? _ckColorFilter;
|
||||
_ManagedSkColorFilter? _managedColorFilter;
|
||||
|
||||
@override
|
||||
double get strokeMiterLimit => _strokeMiterLimit;
|
||||
@ -200,11 +202,14 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
|
||||
if (_imageFilter == value) {
|
||||
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
|
||||
SkPaint createDefault() {
|
||||
@ -224,8 +229,8 @@ class CkPaint extends ManagedSkiaObject<SkPaint> implements ui.Paint {
|
||||
paint.setColorInt(_color.value);
|
||||
paint.setShader(_shader?.skiaObject);
|
||||
paint.setMaskFilter(_ckMaskFilter?.skiaObject);
|
||||
paint.setColorFilter(_ckColorFilter?.skiaObject);
|
||||
paint.setImageFilter(_imageFilter?.skiaObject);
|
||||
paint.setColorFilter(_managedColorFilter?.skiaObject);
|
||||
paint.setImageFilter(_managedImageFilter?.skiaObject);
|
||||
paint.setFilterQuality(toSkFilterQuality(_filterQuality));
|
||||
paint.setStrokeCap(toSkStrokeCap(_strokeCap));
|
||||
paint.setStrokeJoin(toSkStrokeJoin(_strokeJoin));
|
||||
|
||||
@ -21,11 +21,7 @@ class EngineColorFilter implements ui.ColorFilter {
|
||||
/// 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;
|
||||
const factory EngineColorFilter.mode(ui.Color color, ui.BlendMode blendMode) = _CkBlendModeColorFilter;
|
||||
|
||||
/// Construct a color filter that transforms a color by a 5x5 matrix, where
|
||||
/// the fifth row is implicitly added in an identity configuration.
|
||||
@ -87,86 +83,13 @@ class EngineColorFilter implements ui.ColorFilter {
|
||||
/// 0, 0, 0, 1, 0,
|
||||
/// ]);
|
||||
/// ```
|
||||
const EngineColorFilter.matrix(List<double> matrix)
|
||||
: _color = null,
|
||||
_blendMode = null,
|
||||
_matrix = matrix,
|
||||
_type = _TypeMatrix;
|
||||
const factory EngineColorFilter.matrix(List<double> matrix) = _CkMatrixColorFilter;
|
||||
|
||||
/// 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;
|
||||
const factory EngineColorFilter.linearToSrgbGamma() = _CkLinearToSrgbGammaColorFilter;
|
||||
|
||||
/// 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 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.';
|
||||
}
|
||||
}
|
||||
const factory EngineColorFilter.srgbToLinearGamma() = _CkSrgbToLinearGammaColorFilter;
|
||||
}
|
||||
|
||||
@ -60,74 +60,71 @@ class PersistedColorFilter extends PersistedContainerSurface
|
||||
childContainer?.style.visibility = 'visible';
|
||||
return;
|
||||
}
|
||||
if (engineValue._blendMode == null) {
|
||||
rootElement!.style.backgroundColor =
|
||||
colorToCssString(engineValue._color!);
|
||||
|
||||
if (engineValue is! _CkBlendModeColorFilter) {
|
||||
childContainer?.style.visibility = 'visible';
|
||||
return;
|
||||
}
|
||||
|
||||
ui.Color filterColor = engineValue._color!;
|
||||
ui.BlendMode? colorFilterBlendMode = engineValue._blendMode;
|
||||
ui.Color filterColor = engineValue.color;
|
||||
ui.BlendMode colorFilterBlendMode = engineValue.blendMode;
|
||||
html.CssStyleDeclaration style = rootElement!.style;
|
||||
if (colorFilterBlendMode != null) {
|
||||
switch (colorFilterBlendMode) {
|
||||
case ui.BlendMode.clear:
|
||||
case ui.BlendMode.dstOut:
|
||||
case ui.BlendMode.srcOut:
|
||||
childContainer?.style.visibility = 'hidden';
|
||||
return;
|
||||
case ui.BlendMode.dst:
|
||||
case ui.BlendMode.dstIn:
|
||||
// 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);
|
||||
}
|
||||
switch (colorFilterBlendMode) {
|
||||
case ui.BlendMode.clear:
|
||||
case ui.BlendMode.dstOut:
|
||||
case ui.BlendMode.srcOut:
|
||||
childContainer?.style.visibility = 'hidden';
|
||||
return;
|
||||
case ui.BlendMode.dst:
|
||||
case ui.BlendMode.dstIn:
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -406,6 +406,12 @@ class ImageFilter {
|
||||
// if (matrix4.length != 16)
|
||||
// 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 {
|
||||
|
||||
@ -474,6 +474,26 @@ void _imageFilterTests() {
|
||||
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() {
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
// 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';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const Color red = Color(0xFFAA0000);
|
||||
const Color green = Color(0xFF00AA00);
|
||||
|
||||
const int greenCenterBlurred = 0x1C001300;
|
||||
@ -18,6 +19,34 @@ const int greenCenterScaled = 0xFF00AA00;
|
||||
const int greenSideScaled = 0x80005500;
|
||||
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() {
|
||||
Future<Uint32List> getBytesForPaint(Paint paint, {int width = 3, int height = 3}) async {
|
||||
final PictureRecorder recorder = PictureRecorder();
|
||||
@ -31,6 +60,18 @@ void main() {
|
||||
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.blur(sigmaX: sigmaX, sigmaY: sigmaY);
|
||||
|
||||
@ -47,6 +88,20 @@ void main() {
|
||||
]), 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() {
|
||||
return <ImageFilter>[
|
||||
makeBlur(10.0, 10.0),
|
||||
@ -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.high),
|
||||
makeScale(10.0, 10.0, 0.0, 0.0, FilterQuality.none),
|
||||
...colorFilters(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
final List<ImageFilter> A = makeList();
|
||||
final List<ImageFilter> B = makeList();
|
||||
checkEquality(A, A);
|
||||
checkEquality(A, 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) {
|
||||
@ -117,4 +180,117 @@ void main() {
|
||||
final Uint32List bytes = await getBytesForPaint(paint);
|
||||
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)'
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user