未验证 提交 ffcd8564 编写于 作者: J Jim Graham 提交者: GitHub

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).
上级 f240462b
......@@ -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.
///
......
......@@ -1387,16 +1387,24 @@ 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];
return _objects[_kImageFilterIndex].creator;
}
set imageFilter(ImageFilter value) {
_objects ??= List<dynamic>(_kObjectCount);
_objects[_kImageFilterIndex] = 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.
///
/// Inverting the colors of an image applies a new color filter that will
......@@ -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)';
}
}
// 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);
});
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册