import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_shaders/flutter_shaders.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Simple Shader Demo',
theme: ThemeData(colorSchemeSeed: Colors.blue),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool useImageFilter = false;
double rotation = 0.0;
ui.Image? image;
@override
void initState() {
super.initState();
_createImage();
}
Future<void> _createImage() async {
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
final paint = Paint();
// Draw a checkers pattern
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
paint.color = (i + j) % 2 == 0 ? Colors.red : Colors.blue;
canvas.drawRect(Rect.fromLTWH(i * 50.0, j * 50.0, 50.0, 50.0), paint);
}
}
final picture = recorder.endRecording();
final img = await picture.toImage(100, 100);
setState(() {
image = img;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Shader Demo'),
actions: [
Row(
children: [
const Text('Use Paint.imageFilter'),
],
),
],
),
body: image == null
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('Filter:'),
Switch(
value: useImageFilter,
onChanged: (v) =>
setState(() => useImageFilter = v),
),
],
),
Expanded(
child: Row(
children: [
const Text('Rot:'),
Expanded(
child: Slider(
value: rotation,
min: 0.0,
max: 6.28, // 2*PI approx
onChanged: (v) => setState(() => rotation = v),
),
),
],
),
),
],
),
),
Expanded(
child: ShaderBuilder(
assetKey: 'shaders/gradient.frag',
(context, shader, child) => CustomPaint(
size: MediaQuery.of(context).size,
painter: ShaderPainter(
shader: shader,
useImageFilter: useImageFilter,
image: image!,
rotation: rotation,
),
),
child: const Center(child: CircularProgressIndicator()),
),
),
],
),
);
}
}
class ShaderPainter extends CustomPainter {
ShaderPainter({
required this.shader,
required this.useImageFilter,
required this.image,
required this.rotation,
});
final ui.FragmentShader shader;
final bool useImageFilter;
final ui.Image image;
final double rotation;
@override
void paint(Canvas canvas, Size size) {
shader.setFloat(0, 100); // resolution.x
shader.setFloat(1, 100); // resolution.y
final paint = Paint();
canvas.translate(50, 50);
canvas.rotate(rotation);
canvas.translate(-50, -50);
if (useImageFilter) {
// Pass the shader as an ImageFilter.
// The content drawn (the image) becomes the input texture for the shader.
paint.imageFilter = ui.ImageFilter.shader(shader);
canvas.drawImage(image, Offset.zero, paint);
} else {
// Bind the image explicitly to the shader's sampler 0
shader.setImageSampler(0, image);
paint.shader = shader;
// Draw a rect filled with the shader (which samples the image)
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);
}
}
@override
bool shouldRepaint(covariant ShaderPainter oldDelegate) {
return oldDelegate.shader != shader ||
oldDelegate.useImageFilter != useImageFilter ||
oldDelegate.image != image ||
oldDelegate.rotation != rotation;
}
}
#version 460 core
#include <flutter/runtime_effect.glsl>
precision mediump float;
uniform vec2 resolution;
out vec4 fragColor;
vec3 flutterBlue = vec3(5, 83, 177) / 255;
vec3 flutterNavy = vec3(4, 43, 89) / 255;
vec3 flutterSky = vec3(2, 125, 253) / 255;
void main() {
vec2 st = FlutterFragCoord().xy / resolution.xy;
vec3 color = vec3(0.0);
vec3 percent = vec3((st.x + st.y) / 2);
color =
mix(mix(flutterSky, flutterBlue, percent * 2),
mix(flutterBlue, flutterNavy, percent * 2 - 1), step(0.5, percent));
fragColor = vec4(color, 1);
}
reproduction
Details
gradient
simple
observed (stable)
f6ff152
imagefilterbefore.mov
observed (main)
acdb284
imagefilterafter.mov
expected
The imagefilter effect should rotate like it does on stable, but the rect should be sized to 100,100 at all times, like after when there is no rotation.