Created
October 21, 2023 23:18
-
-
Save Rahiche/11285b14a9b953dee9e1d7ee3a6396be to your computer and use it in GitHub Desktop.
iOS 17 Faded Gradual Blur
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ui'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_shaders/flutter_shaders.dart'; | |
void main() { | |
runApp(BlurGallery()); | |
} | |
class BlurGallery extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData.dark( | |
useMaterial3: true, | |
), | |
home: Builder(builder: (context) { | |
return Scaffold( | |
appBar: AppBar(title: Text('Blur Effect Demo')), | |
body: GridView.count( | |
crossAxisCount: 2, | |
children: <Widget>[ | |
GestureDetector( | |
onTap: () { | |
Navigator.push( | |
context, | |
MaterialPageRoute( | |
builder: (context) => GradualBackgroundBlur()), | |
); | |
}, | |
child: buildContent("Gradual Background Blur"), | |
), | |
], | |
), | |
); | |
}), | |
); | |
} | |
Widget buildContent(String text) { | |
return Card( | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Center(child: Text(text)), | |
), | |
); | |
} | |
} | |
class GradualBackgroundBlur extends StatefulWidget { | |
@override | |
_GradualBackgroundBlurState createState() => _GradualBackgroundBlurState(); | |
} | |
class _GradualBackgroundBlurState extends State<GradualBackgroundBlur> { | |
bool isClipped = true; | |
ScrollController _scrollController = ScrollController(); | |
double blurSigma = 1; | |
@override | |
void initState() { | |
super.initState(); | |
_scrollController.addListener(_onScroll); | |
} | |
void _onScroll() { | |
const maxBlur = 8.0; | |
const scrollThreshold = 200.0; | |
if (_scrollController.offset < scrollThreshold) { | |
// Calculate blurSigma based on scroll position | |
setState(() { | |
blurSigma = maxBlur * (_scrollController.offset / scrollThreshold); | |
if (blurSigma == maxBlur) {} | |
}); | |
print("blurSigma ${blurSigma}"); | |
} | |
} | |
@override | |
void dispose() { | |
_scrollController.removeListener(_onScroll); // Remove listener | |
_scrollController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final child = Scaffold( | |
body: Stack( | |
children: [ | |
Stack( | |
children: [ | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: ListView.builder( | |
controller: _scrollController, | |
itemBuilder: (BuildContext context, int index) { | |
if (index == 0) { | |
return SizedBox( | |
height: 100, | |
); | |
} | |
if (index == 1) { | |
return Container( | |
child: Image.asset( | |
'assets/rg.jpg', | |
fit: BoxFit.cover, | |
), | |
); | |
} | |
return ListTile( | |
title: Text(index.toString()), | |
); | |
}, | |
), | |
), | |
], | |
), | |
if (isClipped) | |
Align( | |
alignment: Alignment.topCenter, | |
child: ClipRect( | |
child: Container( | |
height: 120, | |
width: double.infinity, | |
child: BackdropFilter( | |
filter: | |
ImageFilter.blur(sigmaX: blurSigma, sigmaY: blurSigma), | |
child: Container( | |
color: Colors.transparent, | |
), | |
), | |
), | |
), | |
), | |
Align( | |
alignment: Alignment.bottomCenter, | |
child: Padding( | |
padding: const EdgeInsets.all(36.0), | |
child: Switch.adaptive( | |
value: isClipped, | |
onChanged: (value) { | |
setState(() { | |
isClipped = value; | |
}); | |
}, | |
), | |
), | |
), | |
], | |
), | |
); | |
if (isClipped) { | |
return child; | |
} else { | |
return TiltShift( | |
child: child, | |
progress: blurSigma, | |
); | |
} | |
} | |
} | |
class TiltShift extends StatelessWidget { | |
const TiltShift({Key? key, required this.child, required this.progress}) | |
: super(key: key); | |
final Widget child; | |
final double progress; | |
@override | |
Widget build(BuildContext context) { | |
return ShaderBuilder( | |
(context, shader, _) { | |
return AnimatedSampler( | |
(image, size, canvas) { | |
shader.setFloat(0, size.width); | |
shader.setFloat(1, size.height); | |
shader.setFloat(2, progress); | |
shader.setImageSampler(0, image); | |
canvas.drawRect( | |
Rect.fromLTWH(0, 0, size.width, size.height), | |
Paint()..shader = shader, | |
); | |
}, | |
child: child, | |
); | |
}, | |
assetKey: 'shaders/tilt_shift_gaussian.frag', | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 460 core | |
precision mediump float; | |
#include <flutter/runtime_effect.glsl> | |
uniform vec2 uViewSize; | |
uniform float sigma; | |
uniform sampler2D uTexture; | |
out vec4 FragColor; | |
float normpdf(in float x, in float sigma) | |
{ | |
return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma; | |
} | |
void main() { | |
vec2 fragCoord = FlutterFragCoord().xy; | |
vec2 uv = fragCoord / uViewSize; | |
vec4 color = vec4(texture(uTexture, uv).rgb, 1.0); | |
if(fragCoord.y < uViewSize.y * 0.2) { // Compute the effect for the top 30% of the image. | |
// How much the shift is visible. | |
const float shiftPower = 4.0; | |
//declare stuff | |
//The bigger the value the slower the effect | |
const int mSize = 35; | |
const int kSize = (mSize-1)/2; | |
float kernel[mSize]; | |
vec3 final_colour = vec3(0.0); | |
//create the 1-D kernel | |
float Z = 0.00; | |
for (int j = 0; j <= kSize; ++j) | |
kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma ); | |
//get the normalization factor (as the gaussian has been clamped) | |
for (int j = 0; j < mSize; ++j) | |
Z += kernel[j]; | |
//read out the texels | |
for (int i=-kSize; i <= kSize; ++i) | |
{ | |
for (int j=-kSize; j <= kSize; ++j) | |
final_colour += kernel[kSize+j]*kernel[kSize+i]*texture(uTexture, (fragCoord.xy+vec2(float(i),float(j))) / uViewSize).rgb; | |
} | |
// Blend factor for the effect | |
float val = clamp(shiftPower * abs(uViewSize.y * 0.2 - fragCoord.y) / (uViewSize.y * 0.3), 0.0, 1.0); | |
FragColor = vec4(final_colour/(Z*Z), 1.0) * val + color * (1.0 - val); | |
} else { | |
FragColor = color; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment