Make BoxDecoration image faded/transparent
For those who wonder whether the performance is OK or not (since images and opacity are both resource-heavy things), here is my dig into the doc and the source code and the answer.
Conclusion: Use DecorationImage(colorFilter: ...)
will be as fast as what official doc suggests. (But Opacity
, ColorFilter
widgets are not)
Firstly, we should not use Opacity
or ColorFilter
widget since it may trigger saveLayer
and is expensive (by official doc).
Instead, we should
Use the Opacity widget only when necessary. See the Transparent image section in the Opacity API page for an example of applying opacity directly to an image, which is faster than using the Opacity widget.
Looking at the suggested method, we see the following sample:
Image.network(
'https://raw.githubusercontent.com/flutter/assets-for-api-docs/master/packages/diagrams/assets/blend_mode_destination.jpeg',
color: Color.fromRGBO(255, 255, 255, 0.5),
colorBlendMode: BlendMode.modulate
)
Now, the problem is, is the highly-voted answer, i.e. the following code, as fast as what the official doc mentions for Image
widget?
Container(
child: Text('hi'),
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: new DecorationImage(
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(Colors.black.withOpacity(0.2), BlendMode.dstATop),
image: new NetworkImage(
'http://www.allwhitebackground.com/images/2/2582-190x190.jpg',
),
),
),
),
To answer this, let us look at Image.network
's source. This constructor will directly fill in the colorBlendMode
field of Image
.
In Image
's build
, it will be directly passed to RawImage
's colorBlendMode
field.
Then, RawImage
will create RenderImage
(which is a RenderObject) and updates RenderImage._colorBlendMode
.
Next, notice how RenderImage handles this -
BlendMode? _colorBlendMode;
set colorBlendMode(BlendMode? value) {
if (value == _colorBlendMode)
return;
_colorBlendMode = value;
_updateColorFilter();
markNeedsPaint();
}
...
/// If non-null, this color is blended with each image pixel using [colorBlendMode].
Color? get color => _color;
Color? _color;
set color(Color? value) {
if (value == _color)
return;
_color = value;
_updateColorFilter();
markNeedsPaint();
}
...
ColorFilter? _colorFilter;
void _updateColorFilter() {
if (_color == null)
_colorFilter = null;
else
_colorFilter = ColorFilter.mode(_color!, _colorBlendMode ?? BlendMode.srcIn);
}
A more dig into rendering/image.dart
will show that, colorBlendMode
(and _colorBlendMode
will not be used in other places except to create this _colorFilter
.
Thus, we know the two arguments of Image.network
will finally go into RenderImage._colorFilter
.
Indeed, that _colorFilter
will be used in RenderImage.paint
as
@override
void paint(PaintingContext context, Offset offset) {
...
paintImage(
canvas: context.canvas,
rect: offset & size,
image: _image!,
colorFilter: _colorFilter,
...
);
}
So we know it! It will be used in paintImage
which communicates with native methods. No wonder it is faster than Opacity
.
No go back to our DecorationImage
. Inside painting/decoration_image.dart
, we see DecorationImagePainter
:
class DecorationImagePainter {
DecorationImagePainter._(this._details, ...);
final DecorationImage _details;
void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) {
...
paintImage(
canvas: canvas,
rect: rect,
image: _image!.image,
colorFilter: _details.colorFilter,
...
);
}
}
Hey, that is exactly the same!
You could give your DecorationImage
a ColorFilter
to make the background image grey (use a saturation
color filter) or semi transparent (use a dstATop
color filter).
Code for this example is below.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) => new Scaffold(
appBar: new AppBar(
title: new Text('Grey Example'),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
new Card(
child: new Container(
child: new Text(
'Hello world',
style: Theme.of(context).textTheme.display4
),
decoration: new BoxDecoration(
color: const Color(0xff7c94b6),
image: new DecorationImage(
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(Colors.black.withOpacity(0.2), BlendMode.dstATop),
image: new NetworkImage(
'http://www.allwhitebackground.com/images/2/2582-190x190.jpg',
),
),
),
),
),
],
),
);
}
The Opacity
widget is another option.
You could also pre apply the effect to the asset.
You can simply use
ColorFiltered(
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.2), BlendMode.dstATop),
child: YourWidget(),
)