| 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 | |
| 5 | /// @docImport 'basic.dart'; |
| 6 | /// @docImport 'color_filter.dart'; |
| 7 | library; |
| 8 | |
| 9 | import 'dart:ui'; |
| 10 | |
| 11 | import 'package:flutter/foundation.dart'; |
| 12 | import 'package:flutter/rendering.dart'; |
| 13 | |
| 14 | import 'framework.dart'; |
| 15 | |
| 16 | /// Applies an [ImageFilter] to its child. |
| 17 | /// |
| 18 | /// An image filter will always apply its filter operation to the child widget, |
| 19 | /// even if said filter is conceptually a "no-op", such as an ImageFilter.blur |
| 20 | /// with a radius of 0 or an ImageFilter.matrix with an identity matrix. Setting |
| 21 | /// [ImageFiltered.enabled] to `false` is a more efficient manner of disabling |
| 22 | /// an image filter. |
| 23 | /// |
| 24 | /// The framework does not attempt to optimize out "no-op" filters because it |
| 25 | /// cannot tell the difference between an intentional no-op and a filter that is |
| 26 | /// only incidentally a no-op. Consider an ImageFilter.matrix that is animated |
| 27 | /// and happens to pass through the identity matrix. If the framework identified it |
| 28 | /// as a no-op it would drop and then recreate the layer during the animation which |
| 29 | /// would be more expensive than keeping it around. |
| 30 | /// |
| 31 | /// {@youtube 560 315 https://www.youtube.com/watch?v=7Lftorq4i2o} |
| 32 | /// |
| 33 | /// See also: |
| 34 | /// |
| 35 | /// * [BackdropFilter], which applies an [ImageFilter] to everything |
| 36 | /// beneath its child. |
| 37 | /// * [ColorFiltered], which applies a [ColorFilter] to its child. |
| 38 | @immutable |
| 39 | class ImageFiltered extends SingleChildRenderObjectWidget { |
| 40 | /// Creates a widget that applies an [ImageFilter] to its child. |
| 41 | const ImageFiltered({super.key, required this.imageFilter, super.child, this.enabled = true}); |
| 42 | |
| 43 | /// The image filter to apply to the child of this widget. |
| 44 | final ImageFilter imageFilter; |
| 45 | |
| 46 | /// Whether or not to apply the image filter operation to the child of this |
| 47 | /// widget. |
| 48 | /// |
| 49 | /// Prefer setting enabled to `false` instead of creating a "no-op" filter |
| 50 | /// type for performance reasons. |
| 51 | final bool enabled; |
| 52 | |
| 53 | @override |
| 54 | RenderObject createRenderObject(BuildContext context) => |
| 55 | _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 | |
| 71 | class _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 | |