1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:ui';
6
7import 'package:flutter/foundation.dart';
8import 'package:flutter/rendering.dart';
9
10import 'framework.dart';
11
12/// Applies an [ImageFilter] to its child.
13///
14/// An image filter will always apply its filter operation to the child widget,
15/// even if said filter is conceptually a "no-op", such as an ImageFilter.blur
16/// with a radius of 0 or an ImageFilter.matrix with an identity matrix. Setting
17/// [ImageFiltered.enabled] to `false` is a more efficient manner of disabling
18/// an image filter.
19///
20/// The framework does not attempt to optimize out "no-op" filters because it
21/// cannot tell the difference between an intentional no-op and a filter that is
22/// only incidentally a no-op. Consider an ImageFilter.matrix that is animated
23/// and happens to pass through the identity matrix. If the framework identified it
24/// as a no-op it would drop and then recreate the layer during the animation which
25/// would be more expensive than keeping it around.
26///
27/// {@youtube 560 315 https://www.youtube.com/watch?v=7Lftorq4i2o}
28///
29/// See also:
30///
31/// * [BackdropFilter], which applies an [ImageFilter] to everything
32/// beneath its child.
33/// * [ColorFiltered], which applies a [ColorFilter] to its child.
34@immutable
35class ImageFiltered extends SingleChildRenderObjectWidget {
36 /// Creates a widget that applies an [ImageFilter] to its child.
37 const ImageFiltered({
38 super.key,
39 required this.imageFilter,
40 super.child,
41 this.enabled = true,
42 });
43
44 /// The image filter to apply to the child of this widget.
45 final ImageFilter imageFilter;
46
47 /// Whether or not to apply the image filter operation to the child of this
48 /// widget.
49 ///
50 /// Prefer setting enabled to `false` instead of creating a "no-op" filter
51 /// type for performance reasons.
52 final bool enabled;
53
54 @override
55 RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter, enabled);
56
57 @override
58 void updateRenderObject(BuildContext context, RenderObject renderObject) {
59 (renderObject as _ImageFilterRenderObject)
60 ..enabled = enabled
61 ..imageFilter = imageFilter;
62 }
63
64 @override
65 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
66 super.debugFillProperties(properties);
67 properties.add(DiagnosticsProperty<ImageFilter>('imageFilter', imageFilter));
68 }
69}
70
71class _ImageFilterRenderObject extends RenderProxyBox {
72 _ImageFilterRenderObject(this._imageFilter, this._enabled);
73
74 bool get enabled => _enabled;
75 bool _enabled;
76 set enabled(bool value) {
77 if (enabled == value) {
78 return;
79 }
80 final bool wasRepaintBoundary = isRepaintBoundary;
81 _enabled = value;
82 if (isRepaintBoundary != wasRepaintBoundary) {
83 markNeedsCompositingBitsUpdate();
84 }
85 markNeedsPaint();
86 }
87
88 ImageFilter get imageFilter => _imageFilter;
89 ImageFilter _imageFilter;
90 set imageFilter(ImageFilter value) {
91 if (value != _imageFilter) {
92 _imageFilter = value;
93 markNeedsCompositedLayerUpdate();
94 }
95 }
96
97 @override
98 bool get alwaysNeedsCompositing => child != null && enabled;
99
100 @override
101 bool get isRepaintBoundary => alwaysNeedsCompositing;
102
103 @override
104 OffsetLayer updateCompositedLayer({required covariant ImageFilterLayer? oldLayer}) {
105 final ImageFilterLayer layer = oldLayer ?? ImageFilterLayer();
106 layer.imageFilter = imageFilter;
107 return layer;
108 }
109}
110