mirror of
https://github.com/encounter/engine.git
synced 2026-03-30 11:09:55 -07:00
Imagefilter wrapper object (#13711)
Make ImageFilter objects comparable and printable. This will help in areas in the Widget and RenderObject trees which try to avoid marking objects for updates if a setter is called with the same value (previously all ImageFilter objects would compare as not equal and appear to be new values).
This commit is contained in:
@@ -410,11 +410,11 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// See [pop] for details about the operation stack.
|
||||
BackdropFilterEngineLayer pushBackdropFilter(ImageFilter filter, { BackdropFilterEngineLayer oldLayer }) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushBackdropFilter'));
|
||||
final BackdropFilterEngineLayer layer = BackdropFilterEngineLayer._(_pushBackdropFilter(filter));
|
||||
final BackdropFilterEngineLayer layer = BackdropFilterEngineLayer._(_pushBackdropFilter(filter._toNativeImageFilter()));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushBackdropFilter(ImageFilter filter) native 'SceneBuilder_pushBackdropFilter';
|
||||
EngineLayer _pushBackdropFilter(_ImageFilter filter) native 'SceneBuilder_pushBackdropFilter';
|
||||
|
||||
/// Pushes a shader mask operation onto the operation stack.
|
||||
///
|
||||
|
||||
+121
-14
@@ -1387,15 +1387,23 @@ class Paint {
|
||||
///
|
||||
/// * [MaskFilter], which is used for drawing geometry.
|
||||
ImageFilter get imageFilter {
|
||||
if (_objects == null)
|
||||
if (_objects == null || _objects[_kImageFilterIndex] == null)
|
||||
return null;
|
||||
return _objects[_kImageFilterIndex];
|
||||
}
|
||||
set imageFilter(ImageFilter value) {
|
||||
_objects ??= List<dynamic>(_kObjectCount);
|
||||
_objects[_kImageFilterIndex] = value;
|
||||
return _objects[_kImageFilterIndex].creator;
|
||||
}
|
||||
|
||||
set imageFilter(ImageFilter value) {
|
||||
if (value == null) {
|
||||
if (_objects != null) {
|
||||
_objects[_kImageFilterIndex] = null;
|
||||
}
|
||||
} else {
|
||||
_objects ??= List<dynamic>(_kObjectCount);
|
||||
if (_objects[_kImageFilterIndex]?.creator != value) {
|
||||
_objects[_kImageFilterIndex] = value._toNativeImageFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the colors of the image are inverted when drawn.
|
||||
///
|
||||
@@ -2659,7 +2667,7 @@ class ColorFilter {
|
||||
/// This is a private class, rather than being the implementation of the public
|
||||
/// ColorFilter, because we want ColorFilter to be const constructible and
|
||||
/// efficiently comparable, so that widgets can check for ColorFilter equality to
|
||||
// avoid repainting.
|
||||
/// avoid repainting.
|
||||
class _ColorFilter extends NativeFieldWrapperClass2 {
|
||||
_ColorFilter.mode(this.creator)
|
||||
: assert(creator != null),
|
||||
@@ -2706,13 +2714,107 @@ class _ColorFilter extends NativeFieldWrapperClass2 {
|
||||
/// * [BackdropFilter], a widget that applies [ImageFilter] to its rendering.
|
||||
/// * [SceneBuilder.pushBackdropFilter], which is the low-level API for using
|
||||
/// this class.
|
||||
class ImageFilter extends NativeFieldWrapperClass2 {
|
||||
class ImageFilter {
|
||||
/// Creates an image filter that applies a Gaussian blur.
|
||||
ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 })
|
||||
: _data = _makeList(sigmaX, sigmaY),
|
||||
_filterQuality = null,
|
||||
_type = _kTypeBlur;
|
||||
|
||||
/// 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 })
|
||||
: _data = Float64List.fromList(matrix4),
|
||||
_filterQuality = filterQuality,
|
||||
_type = _kTypeMatrix {
|
||||
if (matrix4.length != 16)
|
||||
throw ArgumentError('"matrix4" must have 16 entries.');
|
||||
}
|
||||
|
||||
static Float64List _makeList(double a, double b) {
|
||||
final Float64List list = Float64List(2);
|
||||
if (a != null)
|
||||
list[0] = a;
|
||||
if (b != null)
|
||||
list[1] = b;
|
||||
return list;
|
||||
}
|
||||
|
||||
final Float64List _data;
|
||||
final FilterQuality _filterQuality;
|
||||
final int _type;
|
||||
_ImageFilter _nativeFilter;
|
||||
|
||||
// The type of SkImageFilter class to create for Skia.
|
||||
static const int _kTypeBlur = 0; // MakeBlurFilter
|
||||
static const int _kTypeMatrix = 1; // MakeMatrixFilterRowMajor255
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! ImageFilter) {
|
||||
return false;
|
||||
}
|
||||
final ImageFilter typedOther = other;
|
||||
|
||||
if (_type != typedOther._type) {
|
||||
return false;
|
||||
}
|
||||
if (!_listEquals<double>(_data, typedOther._data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _filterQuality == typedOther._filterQuality;
|
||||
}
|
||||
|
||||
_ImageFilter _toNativeImageFilter() => _nativeFilter ??= _makeNativeImageFilter();
|
||||
|
||||
_ImageFilter _makeNativeImageFilter() {
|
||||
if (_data == null) {
|
||||
return null;
|
||||
}
|
||||
switch (_type) {
|
||||
case _kTypeBlur:
|
||||
return _ImageFilter.blur(this);
|
||||
case _kTypeMatrix:
|
||||
return _ImageFilter.matrix(this);
|
||||
default:
|
||||
throw StateError('Unknown mode $_type for ImageFilter.');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(_filterQuality, hashList(_data), _type);
|
||||
|
||||
@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.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An [ImageFilter] that is backed by a native SkImageFilter.
|
||||
///
|
||||
/// This is a private class, rather than being the implementation of the public
|
||||
/// ImageFilter, because we want ImageFilter to be efficiently comparable, so that
|
||||
/// widgets can check for ImageFilter equality to avoid repainting.
|
||||
class _ImageFilter extends NativeFieldWrapperClass2 {
|
||||
void _constructor() native 'ImageFilter_constructor';
|
||||
|
||||
/// Creates an image filter that applies a Gaussian blur.
|
||||
ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) {
|
||||
_ImageFilter.blur(this.creator)
|
||||
: assert(creator != null),
|
||||
assert(creator._type == ImageFilter._kTypeBlur) {
|
||||
_constructor();
|
||||
_initBlur(sigmaX, sigmaY);
|
||||
_initBlur(creator._data[0], creator._data[1]);
|
||||
}
|
||||
void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur';
|
||||
|
||||
@@ -2720,14 +2822,19 @@ 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(Float64List matrix4,
|
||||
{ FilterQuality filterQuality = FilterQuality.low }) {
|
||||
if (matrix4.length != 16)
|
||||
_ImageFilter.matrix(this.creator)
|
||||
: assert(creator != null),
|
||||
assert(creator._type == ImageFilter._kTypeMatrix) {
|
||||
if (creator._data.length != 16)
|
||||
throw ArgumentError('"matrix4" must have 16 entries.');
|
||||
_constructor();
|
||||
_initMatrix(matrix4, filterQuality.index);
|
||||
_initMatrix(creator._data, creator._filterQuality.index);
|
||||
}
|
||||
void _initMatrix(Float64List matrix4, int filterQuality) native 'ImageFilter_initMatrix';
|
||||
|
||||
/// The original Dart object that created the native wrapper, which retains
|
||||
/// the values used for the filter.
|
||||
final ImageFilter creator;
|
||||
}
|
||||
|
||||
/// Base class for objects such as [Gradient] and [ImageShader] which
|
||||
|
||||
@@ -10,7 +10,9 @@ part of engine;
|
||||
class SkImageFilter implements ui.ImageFilter {
|
||||
js.JsObject skImageFilter;
|
||||
|
||||
SkImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0}) {
|
||||
SkImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0})
|
||||
: _sigmaX = sigmaX,
|
||||
_sigmaY = sigmaY {
|
||||
skImageFilter = canvasKit['SkImageFilter'].callMethod(
|
||||
'MakeBlur',
|
||||
<dynamic>[
|
||||
@@ -21,4 +23,24 @@ class SkImageFilter implements ui.ImageFilter {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final double _sigmaX;
|
||||
final double _sigmaY;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! SkImageFilter) {
|
||||
return false;
|
||||
}
|
||||
final SkImageFilter typedOther = other;
|
||||
return _sigmaX == typedOther._sigmaX && _sigmaY == typedOther._sigmaY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => ui.hashValues(_sigmaX, _sigmaY);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ImageFilter.blur($_sigmaX, $_sigmaY)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,4 +249,21 @@ class EngineImageFilter implements ui.ImageFilter {
|
||||
|
||||
final double sigmaX;
|
||||
final double sigmaY;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! EngineImageFilter) {
|
||||
return false;
|
||||
}
|
||||
final EngineImageFilter typedOther = other;
|
||||
return sigmaX == typedOther.sigmaX && sigmaY == typedOther.sigmaY;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => ui.hashValues(sigmaX, sigmaY);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ImageFilter.blur($sigmaX, $sigmaY)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const Color green = Color(0xFF00AA00);
|
||||
|
||||
const int greenCenterBlurred = 0x1C001300;
|
||||
const int greenSideBlurred = 0x15000E00;
|
||||
const int greenCornerBlurred = 0x10000A00;
|
||||
|
||||
const int greenCenterScaled = 0xFF00AA00;
|
||||
const int greenSideScaled = 0x80005500;
|
||||
const int greenCornerScaled = 0x40002B00;
|
||||
|
||||
void main() {
|
||||
Future<Uint32List> getBytesForPaint(Paint paint, {int width = 3, int height = 3}) async {
|
||||
final PictureRecorder recorder = PictureRecorder();
|
||||
final Canvas recorderCanvas = Canvas(recorder);
|
||||
recorderCanvas.drawRect(const Rect.fromLTRB(1.0, 1.0, 2.0, 2.0), paint);
|
||||
final Picture picture = recorder.endRecording();
|
||||
final Image image = await picture.toImage(width, height);
|
||||
final ByteData bytes = await image.toByteData();
|
||||
|
||||
expect(bytes.lengthInBytes, equals(width * height * 4));
|
||||
return bytes.buffer.asUint32List();
|
||||
}
|
||||
|
||||
ImageFilter makeBlur(double sigmaX, double sigmaY) =>
|
||||
ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
|
||||
|
||||
ImageFilter makeScale(double scX, double scY,
|
||||
[double trX = 0.0, double trY = 0.0,
|
||||
FilterQuality quality = FilterQuality.low]) {
|
||||
trX *= 1.0 - scX;
|
||||
trY *= 1.0 - scY;
|
||||
return ImageFilter.matrix(Float64List.fromList(<double>[
|
||||
scX, 0.0, 0.0, 0.0,
|
||||
0.0, scY, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
trX, trY, 0.0, 1.0,
|
||||
]), filterQuality: quality);
|
||||
}
|
||||
|
||||
List<ImageFilter> makeList() {
|
||||
return <ImageFilter>[
|
||||
makeBlur(10.0, 10.0),
|
||||
makeBlur(10.0, 20.0),
|
||||
makeBlur(20.0, 20.0),
|
||||
makeScale(10.0, 10.0),
|
||||
makeScale(10.0, 20.0),
|
||||
makeScale(20.0, 10.0),
|
||||
makeScale(10.0, 10.0, 1.0, 1.0),
|
||||
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),
|
||||
];
|
||||
}
|
||||
|
||||
void checkEquality(List<ImageFilter> a, List<ImageFilter> b) {
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
for(int j = 0; j < a.length; j++) {
|
||||
if (i == j) {
|
||||
expect(a[i], equals(b[j]));
|
||||
expect(a[i].hashCode, equals(b[j].hashCode));
|
||||
expect(a[i].toString(), equals(b[j].toString()));
|
||||
} else {
|
||||
expect(a[i], isNot(b[j]));
|
||||
// No expectations on hashCode if objects are not equal
|
||||
expect(a[i].toString(), isNot(b[j].toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('ImageFilter - equals', () async {
|
||||
final List<ImageFilter> A = makeList();
|
||||
final List<ImageFilter> B = makeList();
|
||||
checkEquality(A, A);
|
||||
checkEquality(A, B);
|
||||
checkEquality(B, B);
|
||||
});
|
||||
|
||||
test('ImageFilter - nulls', () async {
|
||||
final Paint paint = Paint()..imageFilter = ImageFilter.blur(sigmaX: null, sigmaY: null);
|
||||
expect(paint.imageFilter, equals(ImageFilter.blur()));
|
||||
|
||||
expect(() => ImageFilter.matrix(null), throwsNoSuchMethodError);
|
||||
});
|
||||
|
||||
void checkBytes(Uint32List bytes, int center, int side, int corner) {
|
||||
expect(bytes[0], equals(corner));
|
||||
expect(bytes[1], equals(side));
|
||||
expect(bytes[2], equals(corner));
|
||||
|
||||
expect(bytes[3], equals(side));
|
||||
expect(bytes[4], equals(center));
|
||||
expect(bytes[5], equals(side));
|
||||
|
||||
expect(bytes[6], equals(corner));
|
||||
expect(bytes[7], equals(side));
|
||||
expect(bytes[8], equals(corner));
|
||||
}
|
||||
|
||||
test('ImageFilter - blur', () async {
|
||||
final Paint paint = Paint()
|
||||
..color = green
|
||||
..imageFilter = makeBlur(1.0, 1.0);
|
||||
|
||||
final Uint32List bytes = await getBytesForPaint(paint);
|
||||
checkBytes(bytes, greenCenterBlurred, greenSideBlurred, greenCornerBlurred);
|
||||
});
|
||||
|
||||
test('ImageFilter - matrix', () async {
|
||||
final Paint paint = Paint()
|
||||
..color = green
|
||||
..imageFilter = makeScale(2.0, 2.0, 1.5, 1.5);
|
||||
|
||||
final Uint32List bytes = await getBytesForPaint(paint);
|
||||
checkBytes(bytes, greenCenterScaled, greenSideScaled, greenCornerScaled);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user