Skip to content

Instantly share code, notes, and snippets.

@rydmike
Last active April 5, 2023 10:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rydmike/78cdff7f63515a9ea18f172b8640de55 to your computer and use it in GitHub Desktop.
Save rydmike/78cdff7f63515a9ea18f172b8640de55 to your computer and use it in GitHub Desktop.
Flutter: MaskFilter.blur Outer Blur issue on DomCanvas
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(
overflow: Overflow.clip,
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment