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

Web ImageFilter.matrix support (#25982)

上级 62e09512
......@@ -2,11 +2,14 @@
// 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 'package:ui/ui.dart' as ui;
import 'canvaskit_api.dart';
import 'color_filter.dart';
import 'skia_object_cache.dart';
import '../util.dart';
/// An [ImageFilter] that can create a managed skia [SkImageFilter] object.
///
......@@ -22,7 +25,7 @@ abstract class CkManagedSkImageFilterConvertible<T extends Object>
/// The CanvasKit implementation of [ui.ImageFilter].
///
/// Currently only supports `blur`.
/// Currently only supports `blur`, `matrix`, and ColorFilters.
abstract class CkImageFilter extends ManagedSkiaObject<SkImageFilter>
implements CkManagedSkImageFilterConvertible<SkImageFilter> {
factory CkImageFilter.blur(
......@@ -31,6 +34,9 @@ abstract class CkImageFilter extends ManagedSkiaObject<SkImageFilter>
required ui.TileMode tileMode}) = _CkBlurImageFilter;
factory CkImageFilter.color({required CkColorFilter colorFilter}) =
CkColorFilterImageFilter;
factory CkImageFilter.matrix(
{required Float64List matrix,
required ui.FilterQuality filterQuality}) = _CkMatrixImageFilter;
CkImageFilter._();
......@@ -126,3 +132,35 @@ class _CkBlurImageFilter extends CkImageFilter {
return 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)';
}
}
class _CkMatrixImageFilter extends CkImageFilter {
_CkMatrixImageFilter({ required Float64List matrix, required this.filterQuality })
: this.matrix = Float64List.fromList(matrix),
super._();
final Float64List matrix;
final ui.FilterQuality filterQuality;
SkImageFilter _initSkiaObject() {
return canvasKit.ImageFilter.MakeMatrixTransform(
toSkMatrixFromFloat64(matrix),
toSkFilterQuality(filterQuality),
null,
);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is _CkMatrixImageFilter
&& other.filterQuality == filterQuality
&& listEquals<double>(other.matrix, matrix);
}
@override
int get hashCode => ui.hashValues(filterQuality, ui.hashList(matrix));
@override
String toString() => 'ImageFilter.matrix($matrix, $filterQuality)';
}
......@@ -113,9 +113,9 @@ class PersistedBackdropFilter extends PersistedContainerSurface
// the blur will fall within 2 * sigma pixels.
if (browserEngine == BrowserEngine.webkit) {
DomRenderer.setElementStyle(_filterElement!, '-webkit-backdrop-filter',
_imageFilterToCss(filter));
filter.filterAttribute);
}
DomRenderer.setElementStyle(_filterElement!, 'backdrop-filter', _imageFilterToCss(filter));
DomRenderer.setElementStyle(_filterElement!, 'backdrop-filter', filter.filterAttribute);
}
}
......
......@@ -18,7 +18,8 @@ class PersistedImageFilter extends PersistedContainerSurface
@override
void apply() {
rootElement!.style.filter = _imageFilterToCss(filter as EngineImageFilter);
rootElement!.style.filter = (filter as EngineImageFilter).filterAttribute;
rootElement!.style.transform = (filter as EngineImageFilter).transformAttribute;
}
@override
......
......@@ -582,10 +582,3 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
throw UnimplementedError();
}
}
// HTML only supports a single radius, but Flutter ImageFilter supports separate
// horizontal and vertical radii. The best approximation we can provide is to
// average the two radii together for a single compromise value.
String _imageFilterToCss(EngineImageFilter filter) {
return 'blur(${(filter.sigmaX + filter.sigmaY) / 2}px)';
}
......@@ -660,25 +660,79 @@ class GradientConical extends GradientRadial {
/// Backend implementation of [ui.ImageFilter].
///
/// Currently only `blur` is supported.
class EngineImageFilter implements ui.ImageFilter {
EngineImageFilter.blur({this.sigmaX = 0.0, this.sigmaY = 0.0});
/// Currently only `blur` and `matrix` are supported.
abstract class EngineImageFilter implements ui.ImageFilter {
factory EngineImageFilter.blur({
required double sigmaX,
required double sigmaY,
required ui.TileMode tileMode,
}) = _BlurEngineImageFilter;
factory EngineImageFilter.matrix({
required Float64List matrix,
required ui.FilterQuality filterQuality,
}) = _MatrixEngineImageFilter;
EngineImageFilter._();
String get filterAttribute => '';
String get transformAttribute => '';
}
class _BlurEngineImageFilter extends EngineImageFilter {
_BlurEngineImageFilter({ this.sigmaX = 0.0, this.sigmaY = 0.0, this.tileMode = ui.TileMode.clamp }) : super._();
final double sigmaX;
final double sigmaY;
final ui.TileMode tileMode;
// TODO(flutter_web): implement TileMode.
String get filterAttribute => blurSigmasToCssString(sigmaX, sigmaY);
@override
bool operator ==(Object other) {
return other is EngineImageFilter &&
if (other.runtimeType != runtimeType)
return false;
return other is _BlurEngineImageFilter &&
other.tileMode == tileMode &&
other.sigmaX == sigmaX &&
other.sigmaY == sigmaY;
}
@override
int get hashCode => ui.hashValues(sigmaX, sigmaY);
int get hashCode => ui.hashValues(sigmaX, sigmaY, tileMode);
@override
String toString() {
return 'ImageFilter.blur($sigmaX, $sigmaY, $tileMode)';
}
}
class _MatrixEngineImageFilter extends EngineImageFilter {
_MatrixEngineImageFilter({ required Float64List matrix, required this.filterQuality })
: webMatrix = Float64List.fromList(matrix),
super._();
final Float64List webMatrix;
final ui.FilterQuality filterQuality;
// TODO(flutter_web): implement FilterQuality.
String get transformAttribute => float64ListToCssTransform(webMatrix);
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is _MatrixEngineImageFilter
&& other.filterQuality == filterQuality
&& listEquals<double>(other.webMatrix, webMatrix);
}
@override
int get hashCode => ui.hashValues(ui.hashList(webMatrix), filterQuality);
@override
String toString() {
return 'ImageFilter.blur($sigmaX, $sigmaY)';
return 'ImageFilter.matrix($webMatrix, $filterQuality)';
}
}
......@@ -81,7 +81,7 @@ void setElementTransform(html.Element element, Float32List matrix4) {
/// See also:
/// * https://github.com/flutter/flutter/issues/32274
/// * https://bugs.chromium.org/p/chromium/issues/detail?id=1040222
String float64ListToCssTransform(Float32List matrix) {
String float64ListToCssTransform(List<double> matrix) {
assert(matrix.length == 16);
final TransformKind transformKind = transformKindOf(matrix);
if (transformKind == TransformKind.transform2d) {
......@@ -113,9 +113,9 @@ enum TransformKind {
}
/// Detects the kind of transform the [matrix] performs.
TransformKind transformKindOf(Float32List matrix) {
TransformKind transformKindOf(List<double> matrix) {
assert(matrix.length == 16);
final Float32List m = matrix;
final List<double> m = matrix;
// If matrix contains scaling, rotation, z translation or
// perspective transform, it is not considered simple.
......@@ -171,15 +171,15 @@ bool isIdentityFloat32ListTransform(Float32List matrix) {
/// permitted. However, it is inefficient to construct a matrix for an identity
/// transform. Consider removing the CSS `transform` property from elements
/// that apply identity transform.
String float64ListToCssTransform2d(Float32List matrix) {
String float64ListToCssTransform2d(List<double> matrix) {
assert(transformKindOf(matrix) != TransformKind.complex);
return 'matrix(${matrix[0]},${matrix[1]},${matrix[4]},${matrix[5]},${matrix[12]},${matrix[13]})';
}
/// Converts [matrix] to a 3D CSS transform value.
String float64ListToCssTransform3d(Float32List matrix) {
String float64ListToCssTransform3d(List<double> matrix) {
assert(matrix.length == 16);
final Float32List m = matrix;
final List<double> m = matrix;
if (m[0] == 1.0 &&
m[1] == 0.0 &&
m[2] == 0.0 &&
......@@ -552,3 +552,10 @@ bool listEquals<T>(List<T>? a, List<T>? b) {
}
return true;
}
// HTML only supports a single radius, but Flutter ImageFilter supports separate
// horizontal and vertical radii. The best approximation we can provide is to
// average the two radii together for a single compromise value.
String blurSigmasToCssString(double sigmaX, double sigmaY) {
return 'blur(${(sigmaX + sigmaY) * 0.5}px)';
}
......@@ -399,14 +399,18 @@ class ImageFilter {
if (engine.useCanvasKit) {
return engine.CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
}
return engine.EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
// TODO(flutter_web): implement TileMode.
return engine.EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
}
ImageFilter.matrix(Float64List matrix4, {FilterQuality filterQuality = FilterQuality.low}) {
// TODO(flutter_web): add implementation.
throw UnimplementedError('ImageFilter.matrix not implemented for web platform.');
// if (matrix4.length != 16)
// throw ArgumentError('"matrix4" must have 16 entries.');
factory ImageFilter.matrix(Float64List matrix4, {FilterQuality filterQuality = FilterQuality.low}) {
if (matrix4.length != 16)
throw ArgumentError('"matrix4" must have 16 entries.');
if (engine.useCanvasKit) {
return engine.CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality);
}
// TODO(flutter_web): implement FilterQuality.
return engine.EngineImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality);
}
ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) {
......
// 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.
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';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
group('ImageFilter constructors', () {
test('matrix is copied', () {
Matrix4 matrix = Matrix4.identity();
Float64List storage = matrix.toFloat64();
ImageFilter filter1 = ImageFilter.matrix(storage);
storage[0] = 2.0;
ImageFilter filter2 = ImageFilter.matrix(storage);
expect(filter1, filter1);
expect(filter2, filter2);
expect(filter1, isNot(equals(filter2)));
expect(filter2, isNot(equals(filter1)));
});
test('matrix tests all values on ==', () {
Matrix4 matrix = Matrix4.identity();
Float64List storage = matrix.toFloat64();
ImageFilter filter1a = ImageFilter.matrix(storage, filterQuality: FilterQuality.none);
ImageFilter filter1b = ImageFilter.matrix(storage, filterQuality: FilterQuality.high);
storage[0] = 2.0;
ImageFilter filter2a = ImageFilter.matrix(storage, filterQuality: FilterQuality.none);
ImageFilter filter2b = ImageFilter.matrix(storage, filterQuality: FilterQuality.high);
expect(filter1a, filter1a);
expect(filter1a, isNot(equals(filter1b)));
expect(filter1a, isNot(equals(filter2a)));
expect(filter1a, isNot(equals(filter2b)));
expect(filter1b, isNot(equals(filter1a)));
expect(filter1b, filter1b);
expect(filter1b, isNot(equals(filter2a)));
expect(filter1b, isNot(equals(filter2b)));
expect(filter2a, isNot(equals(filter1a)));
expect(filter2a, isNot(equals(filter1b)));
expect(filter2a, filter2a);
expect(filter2a, isNot(equals(filter2b)));
expect(filter2b, isNot(equals(filter1a)));
expect(filter2b, isNot(equals(filter1b)));
expect(filter2b, isNot(equals(filter2a)));
expect(filter2b, filter2b);
});
test('blur tests all values on ==', () {
ImageFilter filter1 = ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0, tileMode: TileMode.decal);
ImageFilter filter2 = ImageFilter.blur(sigmaX: 2.0, sigmaY: 3.0, tileMode: TileMode.decal);
ImageFilter filter3 = ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0, tileMode: TileMode.mirror);
expect(filter1, filter1);
expect(filter1, isNot(equals(filter2)));
expect(filter1, isNot(equals(filter3)));
expect(filter2, isNot(equals(filter1)));
expect(filter2, filter2);
expect(filter2, isNot(equals(filter3)));
expect(filter3, isNot(equals(filter1)));
expect(filter3, isNot(equals(filter2)));
expect(filter3, filter3);
});
});
}
......@@ -361,7 +361,7 @@ void testMain() async {
viewElement6.remove();
});
test('pushImageFilter', () async {
test('pushImageFilter blur', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
builder.pushImageFilter(
ImageFilter.blur(sigmaX: 1, sigmaY: 3),
......@@ -374,6 +374,25 @@ void testMain() async {
await matchGoldenFile('compositing_image_filter.png', region: region);
});
test('pushImageFilter matrix', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
builder.pushImageFilter(
ImageFilter.matrix(
(
Matrix4.identity()
..translate(40, 10)
..rotateZ(math.pi / 6)
..scale(0.75, 0.75)
).toFloat64()),
);
_drawTestPicture(builder);
builder.pop();
html.document.body.append(builder.build().webOnlyRootElement);
await matchGoldenFile('compositing_image_filter_matrix.png', region: region);
});
group('Cull rect computation', () {
_testCullRectComputation();
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册