New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[web]: filter/blur incorrectly rendered by the HTML backend #58546
Comments
Thanks @mariamhas for opening the MaskFilter.blur issue based just on the rough eMail discussion of it. Below is a more detailed presentation of it with reproducible code example as well. Example of MaskFilter.blur IssueTLDRUsing MaskFilter.blur with BlurStyle.outer produces a totally different result on Flutter WEB when using DomCanvas versus CanvasKit. The CanvasKit version is correct, well at least it matches the result we get when using Flutter with SKIA on devices and desktops. Applies toAll Flutter Web DomCanvas versions (past ones too). Steps to reproduceThis Gist: https://gist.github.com/rydmike/78cdff7f63515a9ea18f172b8640de55 contain an interactive version that can be used to demo and reproduce the issue. A pre-built live demo using CanvasKit (SKIA) can be found here: And a pre-built live demo using DomCanvas can be found here: These versions were built on master channel (version details below): Live demo versions built with version**Used version**
While the above DomCanvas and CanvasKit demos were built with master channel, we can also demonstrate the same issue in stable Web version with DartPad, using the supplied Gist. This can be tried here: https://dartpad.dartlang.org/78cdff7f63515a9ea18f172b8640de55 Detailed InformationThe supplied code example show a huge difference between the result when building with DomCanvas versus CanvasKit. We can also observe a significant difference between different DomCanvas versions when using the stable version used by DartPad versus the supplied live version that was built using master channel. Clearly there is also some work in progress that also already impacts the filter result. The stable DartPad DomCanvas version is actually even worse than the one built with master, but they are both totally different from the CanvasKit and device versions. Example caseWith the provided example, the Maskfilter.blur effect can be turned ON and OFF and we can compare the results side by side from CanvasKit and DomCanvas. The example code contain some other interesting effects too that can be turned on and off. They were left in there as I wanted to see and make sure what was causing the issue, as I tore down and isolated the case from a more involved case with continuous animation. Only the MaskFilter.blur was relevant, as can be shown with the example. The ImageFilter.blur effect that was used to create a blurry lake reflection effect, did not have any impact on this use case. Below we can see the relevant differences in screenshot from identical code. The first screen shot using CanvasKit show the CORRECT result, the other two DomCanvas versions are very different. Screenshot examples1) CORRECT RESULT : The live example built with CanvasKit using master channel:https://rydmike.github.io/maskfilterskia/#/ 2) INCORRECT RESULT : The live example built with DomCanvas using master channel:https://rydmike.github.io/maskfilterdom/index.html#/ 3) EVEN WORSE RESULT : A live DartPad version using the current 'stable/beta' WEB version it uses:https://dartpad.dartlang.org/78cdff7f63515a9ea18f172b8640de55 SidenoteThis example code snippet can also demonstrate the ClipRect issue reported in #58547 with a more actual useful use case. Here we can see an example where something else than a BoxShadow we on purpose put around a container is drawn outside the container that we then clip away. Here the shadow around the container is actually a in this case unwanted side effect of the used filter on the CustomPaint object, that we then need/want to cut away this effect. However, as reported in #58547 it was found that ClipRect cannot do this consistently. |
Using. code samples from #58546 (comment), I can reproduce the issue flutter doctor -v[✓] Flutter (Channel master, 1.24.0-8.0.pre.194, on Mac OS X 10.15.7 19H15
darwin-x64, locale en-GB)
• Flutter version 1.24.0-8.0.pre.194 at /Users/tahatesser/Code/flutter_master
• Framework revision 018467cdb1 (4 hours ago), 2020-11-11 02:04:03 -0500
• Engine revision 81f219c59c
• Dart version 2.12.0 (build 2.12.0-31.0.dev)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
• Android SDK at /Users/tahatesser/Code/sdk
• Platform android-30, build-tools 30.0.2
• ANDROID_HOME = /Users/tahatesser/Code/sdk
• Java binary at: /Applications/Android
Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 12.2)
• Xcode at /Volumes/SanDisk/Xcode-beta.app/Contents/Developer
• Xcode 12.2, Build version 12B5044c
• CocoaPods version 1.10.0
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 4.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)
[✓] VS Code (version 1.51.0)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.16.0
[✓] Connected device (4 available)
• Taha’s iPad (mobile) • 00008020-000255113EE8402E • ios • iOS 14.2
• macOS (desktop) • macos • darwin-x64 • Mac OS X
10.15.7 19H15 darwin-x64
• Web Server (web) • web-server • web-javascript • Flutter
Tools
• Chrome (web) • chrome • web-javascript • Google
Chrome 86.0.4240.193
• No issues found! |
This issue is reproducible on the latest Updated sample code (compatible with new Flutter versions)import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(IssueDemoApp());
}
// The Maskfilter style we want to demo/test
const kMaskFilter = MaskFilter.blur(BlurStyle.outer, 10);
class IssueDemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.red,
scaffoldBackgroundColor: Colors.grey[100],
buttonTheme: ButtonThemeData(
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.red),
textTheme: ButtonTextTheme.primary,
),
),
debugShowCheckedModeBanner: false,
home: ClipRectIssueDemo(),
);
}
}
class ClipRectIssueDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _ClipRectIssueDemoState();
}
}
class _ClipRectIssueDemoState extends State<ClipRectIssueDemo> {
bool _showHouseWithSun = true;
bool _showFrostedGlassLake = true;
bool _showWaves = true;
bool _useBlurfilter = true;
bool _clipRectOn = true;
double _width = 301.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('MaskFilter Issue Demo'),
centerTitle: true,
elevation: 0,
),
body: SingleChildScrollView(
child: Column(
children: [
// And then some gap space too
const SizedBox(height: 20),
Text('MaskFilter.blur Outer Blur Issue',
style: Theme.of(context).textTheme.headline6),
const SizedBox(height: 20),
SizedBox(
width: 420,
height: 420,
child: Center(
child: SizedBox(
height: _width,
width: _width,
child: _clipRectOn
? ClipRect(
clipBehavior: Clip.hardEdge,
child: BlurMaskDemoWidget(
showHouseWithSun: _showHouseWithSun,
showFrostedGlassLake: _showFrostedGlassLake,
showWaves: _showWaves,
useBlurFilter: _useBlurfilter,
),
)
: BlurMaskDemoWidget(
showHouseWithSun: _showHouseWithSun,
showFrostedGlassLake: _showFrostedGlassLake,
showWaves: _showWaves,
useBlurFilter: _useBlurfilter,
),
),
),
),
const SizedBox(height: 10),
const Divider(),
Center(
child: SizedBox(
width: 450,
child: SwitchListTile(
title: const Text('MaskFilter.blur outer ON/OFF'),
subtitle: const Text(
'Turn on too compare the difference between '
'CanvasKit (SKIA) and DomCanvas.\nThe filter causes an '
'expected blur effect outside the paint canvas that we '
'will also cut away with ClipRect.'),
value: _useBlurfilter,
onChanged: (value) {
setState(() {
_useBlurfilter = value;
});
},
),
),
),
const Divider(),
Center(
child: SizedBox(
width: 450,
child: SwitchListTile(
title: const Text('Show static waves on the lake'),
subtitle: const Text(
'The above MaskFilter.blur outer is only applied '
'to these CustomPaint wave objects.'),
value: _showWaves,
onChanged: (value) {
setState(() {
_showWaves = value;
});
},
),
),
),
Center(
child: SizedBox(
width: 450,
child: SwitchListTile(
title: const Text('Show house over a blood-red sun'),
subtitle: const Text('This one is just here to look cool.'),
value: _showHouseWithSun,
onChanged: (value) {
setState(() {
_showHouseWithSun = value;
});
},
),
),
),
Center(
child: SizedBox(
width: 450,
child: SwitchListTile(
title: const Text('Make a frosted glass lake'),
subtitle:
const Text('Just to show and test that this effect is '
'not what caused the issue on DomCanvas.'),
value: _showFrostedGlassLake,
onChanged: (value) {
setState(() {
_showFrostedGlassLake = value;
});
},
),
),
),
const Divider(),
const SizedBox(height: 10),
Center(
child: SizedBox(
width: 450,
child: SwitchListTile(
title: const Text('ClipRect ON/OFF'),
subtitle: const Text(
'Turn on ClipRect to see edge remnants. \nIf you resize '
'window/media size or change container size, you can '
'observe the edge remnants appearing and dissapearing '
'at different edges.'),
value: _clipRectOn,
onChanged: (value) {
setState(() {
_clipRectOn = value;
});
},
),
),
),
const SizedBox(height: 10),
Center(
child: SizedBox(
width: 450,
child: ListTile(
title: const Text('Change Container size'),
subtitle: Slider(
min: 200.0,
max: 400.0,
divisions: (400 - 100).floor(),
label: _width.floor().toString(),
value: _width,
onChanged: (value) {
setState(() {
_width = value;
});
},
),
trailing: Padding(
padding: const EdgeInsets.only(right: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
const Text(
'Width',
style: TextStyle(fontSize: 11),
),
Text(
_width.floor().toString(),
style: const TextStyle(fontSize: 15),
),
],
),
),
),
),
),
],
),
),
);
}
}
class BlurMaskDemoWidget extends StatelessWidget {
const BlurMaskDemoWidget({
Key? key,
this.showHouseWithSun = false,
this.showFrostedGlassLake = false,
this.showWaves = false,
this.useBlurFilter = false,
}) : super(key: key);
final bool showHouseWithSun;
final bool showFrostedGlassLake;
final bool showWaves;
final bool useBlurFilter;
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
if (showHouseWithSun) _buildSunWithHouse(),
if (showFrostedGlassLake) _buildFrostedGlass(),
if (showWaves) _buildWave1(),
if (showWaves) _buildWave2(),
if (showWaves) _buildWave3(),
],
);
}
// The RED ROUND SUN Circle with a HOUSE in it
Align _buildSunWithHouse() {
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 80),
child: Stack(
children: <Widget>[
// Simple way to make a red "sun" circle
Container(
height: 150,
width: 150,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red[700],
),
),
// The house in front of the red Sun, just using a home icon
Container(
height: 89,
width: 150,
alignment: Alignment.bottomCenter,
child: const Icon(
Icons.home,
size: 85,
color: Colors.white,
),
),
// The same house rotated 180 degrees to be upside down as a
// reflection the water
Container(
height: 145,
width: 150,
alignment: Alignment.bottomCenter,
child: const RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.home,
size: 85,
color: Colors.white,
),
),
),
],
),
),
);
}
// Put a "frosted glass" effect over half of the sun and the upside down
// house to make them look like they are reflected in water
Align _buildFrostedGlass() {
return Align(
alignment: Alignment.bottomCenter,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6.5, sigmaY: 6.5),
child: Container(
height: 160,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey.shade200.withOpacity(0.3),
),
),
),
),
);
}
// Put some waves with red shimmer from the red sun on top of the
// "frosted glass" water. The actual creation of the moving animated
// waves depends on the pub.dev wave package.
Align _buildWave1() {
return Align(
alignment: Alignment.bottomCenter,
child: CustomPaint(
painter: _CustomWavePainter(
color: Colors.red[800]!,
heightPercentange: 0,
waveFrequency: 4,
wavePhaseValue: 0,
waveAmplitude: 8,
blur: useBlurFilter ? kMaskFilter : null,
),
size: const Size(
double.infinity,
170.0,
),
),
);
}
Align _buildWave2() {
return Align(
alignment: Alignment.bottomCenter,
child: CustomPaint(
painter: _CustomWavePainter(
color: Colors.red[600],
heightPercentange: 0.2,
waveFrequency: 3.5,
wavePhaseValue: 100,
waveAmplitude: 10,
blur: useBlurFilter ? kMaskFilter : null,
),
size: const Size(
double.infinity,
170.0,
),
),
);
}
Align _buildWave3() {
return Align(
alignment: Alignment.bottomCenter,
child: CustomPaint(
painter: _CustomWavePainter(
color: Colors.red[500],
heightPercentange: 0.4,
waveFrequency: 3,
wavePhaseValue: 80,
waveAmplitude: 9,
blur: useBlurFilter ? kMaskFilter : null,
),
size: const Size(
double.infinity,
170.0,
),
),
);
}
}
class _CustomWavePainter extends CustomPainter {
_CustomWavePainter({
this.color,
this.gradient,
this.gradientBegin,
this.gradientEnd,
this.blur,
this.heightPercentange,
this.waveFrequency,
this.wavePhaseValue,
this.waveAmplitude,
});
final Color? color;
final List<Color>? gradient;
final Alignment? gradientBegin;
final Alignment? gradientEnd;
final MaskFilter? blur;
final double? waveAmplitude;
final double? wavePhaseValue;
final double? waveFrequency;
final double? heightPercentange;
double _tempA = 0.0;
double _tempB = 0.0;
double viewWidth = 0.0;
final Paint _paint = Paint();
void _setPaths(double viewCenterY, Size size, Canvas canvas) {
final Layer _layer = Layer(
path: Path(),
color: color,
gradient: gradient,
blur: blur,
amplitude: (-1.6 + 0.8) * waveAmplitude!,
phase: wavePhaseValue! * 2 + 30,
);
_layer.path?.reset();
_layer.path?.moveTo(
0.0,
viewCenterY +
_layer.amplitude! * _getSinY(_layer.phase!, waveFrequency!, -1));
for (int i = 1; i < size.width + 1; i++) {
_layer.path?.lineTo(
i.toDouble(),
viewCenterY +
_layer.amplitude! * _getSinY(_layer.phase!, waveFrequency!, i));
}
_layer.path?.lineTo(size.width, size.height);
_layer.path?.lineTo(0.0, size.height);
_layer.path?.close();
if (_layer.color != null) {
_paint.color = _layer.color!;
}
if (_layer.gradient != null) {
final rect = Offset.zero &
Size(size.width, size.height - viewCenterY * heightPercentange!);
_paint.shader = LinearGradient(
begin: gradientBegin ?? Alignment.bottomCenter,
end: gradientEnd ?? Alignment.topCenter,
colors: _layer.gradient!)
.createShader(rect);
}
if (_layer.blur != null) {
_paint.maskFilter = _layer.blur;
}
_paint.style = PaintingStyle.fill;
canvas.drawPath(_layer.path!, _paint);
}
@override
void paint(Canvas canvas, Size size) {
final double viewCenterY = size.height * (heightPercentange! + 0.1);
viewWidth = size.width;
_setPaths(viewCenterY, size, canvas);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
double _getSinY(
double startradius, double waveFrequency, int currentposition) {
if (_tempA == 0) {
_tempA = pi / viewWidth;
}
if (_tempB == 0) {
_tempB = 2 * pi / 360.0;
}
return sin(
_tempA * waveFrequency * (currentposition + 1) + startradius * _tempB);
}
}
/// Meta data of layer
class Layer {
Layer({
this.color,
this.gradient,
this.blur,
this.path,
this.amplitude,
this.phase,
});
final Color? color;
final List<Color>? gradient;
final MaskFilter? blur;
final Path? path;
final double? amplitude;
final double? phase;
} flutter doctor -v (stable and master)[✓] Flutter (Channel stable, 3.3.9, on macOS 13.0 22A380 darwin-x64, locale en-VN)
• Flutter version 3.3.9 on channel stable at /Users/huynq/Documents/GitHub/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision b8f7f1f986 (31 hours ago), 2022-11-23 06:43:51 +0900
• Engine revision 8f2221fbef
• Dart version 2.18.5
• DevTools version 2.15.0
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
• Android SDK at /Users/huynq/Library/Android/sdk
• Platform android-33, build-tools 31.0.0
• ANDROID_HOME = /Users/huynq/Library/Android/sdk
• Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 14.0.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 14A400
• CocoaPods version 1.11.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2021.3)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
[✓] IntelliJ IDEA Community Edition (version 2022.2.2)
• IntelliJ at /Applications/IntelliJ IDEA CE.app
• Flutter plugin version 70.0.5
• Dart plugin version 222.4167.21
[✓] IntelliJ IDEA Community Edition (version 2022.1.1)
• IntelliJ at /Users/huynq/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5591.52/IntelliJ IDEA CE.app
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
[✓] VS Code (version 1.73.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.52.0
[✓] Connected device (3 available)
• SM T225 (mobile) • R9JT3004VRJ • android-arm64 • Android 12 (API 31)
• macOS (desktop) • macos • darwin-x64 • macOS 13.0 22A380 darwin-x64
• Chrome (web) • chrome • web-javascript • Google Chrome 107.0.5304.110
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!
[!] Flutter (Channel master, 3.6.0-7.0.pre.52, on macOS 13.0 22A380 darwin-x64, locale en-VN)
• Flutter version 3.6.0-7.0.pre.52 on channel master at /Users/huynq/Documents/GitHub/flutter_master
! Warning: `flutter` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path.
! Warning: `dart` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path.
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision ff59250dbe (4 hours ago), 2022-11-24 18:08:30 -0500
• Engine revision 7665ae5184
• Dart version 2.19.0 (build 2.19.0-429.0.dev)
• DevTools version 2.19.0
• If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades.
[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
• Android SDK at /Users/huynq/Library/Android/sdk
• Platform android-33, build-tools 31.0.0
• ANDROID_HOME = /Users/huynq/Library/Android/sdk
• Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 14.0.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 14A400
• CocoaPods version 1.11.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2021.3)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
[✓] IntelliJ IDEA Community Edition (version 2022.2.2)
• IntelliJ at /Applications/IntelliJ IDEA CE.app
• Flutter plugin version 70.0.5
• Dart plugin version 222.4167.21
[✓] IntelliJ IDEA Community Edition (version 2022.1.1)
• IntelliJ at /Users/huynq/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5591.52/IntelliJ IDEA CE.app
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
[✓] VS Code (version 1.73.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.52.0
[✓] Connected device (3 available)
• Pixel 3a (mobile) • 964AY0WL20 • android-arm64 • Android 12 (API 32)
• macOS (desktop) • macos • darwin-x64 • macOS 13.0 22A380 darwin-x64
• Chrome (web) • chrome • web-javascript • Google Chrome 107.0.5304.110
[✓] HTTP Host Availability
• All required HTTP hosts are available
! Doctor found issues in 1 category.
|
Thanks @huycozy for the check, verification and update of this old issue to a version that shows it with latest Flutter versions. |
Hi, I've encountered a similar problem with the blur mask filter. maybe flutter/engine#45166 can solve this issue. |
To produce the effect on the red waves (that actually slowly continuously animate like waves when run) a MaskFilter.blur(BlurStyle.outer, 10) is used, I suspect it is the culprit that looks bad on DomCanvas, but until I isolate it and demonstrate it in a stand-alone example I’m not 100% sure.
Expected (Canvaskit rendering):

Actual (Dom rendering):

The text was updated successfully, but these errors were encountered: