I am using kivy & kivymd and I am trying to achieve an optimal way to apply a gaussian blur to a FitImage that stands as a background image.
Naturally, I tried the EffectWidget, however, this is not optimal considering the fact that I animate it and I want the user to be able to resize the window and according to the docs, the effectwidget recreates the fbo every time there is a change to the widget.
The code I used:
from kivy.lang import Builder
from kivymd.app import MDApp
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.kv = Builder.load_string('''
#:kivy 2.0.0
#:import ew kivy.uix.effectwidget
EffectWidget:
effects: ew.HorizontalBlurEffect(size=12.0), ew.VerticalBlurEffect(size=12.0)
FitImage:
source: "images/song_img.jpg"
# the rest of my code...
''')
def build(self):
return self.kv
if __name__ == '__main__':
MainApp().run()
So I assume the only appropriate way for me to achieve what I want is to alter the glsl code.
The glsl code for ew.HorizontalBlurEffect:
effect_blur_h = '''
vec4 effect(vec4 color, sampler2D texture, vec2 tex_coords, vec2 coords)
{{
float dt = ({} / 4.0) * 1.0 / resolution.x;
vec4 sum = vec4(0.0);
sum += texture2D(texture, vec2(tex_coords.x - 4.0*dt, tex_coords.y))
* 0.05;
sum += texture2D(texture, vec2(tex_coords.x - 3.0*dt, tex_coords.y))
* 0.09;
sum += texture2D(texture, vec2(tex_coords.x - 2.0*dt, tex_coords.y))
* 0.12;
sum += texture2D(texture, vec2(tex_coords.x - dt, tex_coords.y))
* 0.15;
sum += texture2D(texture, vec2(tex_coords.x, tex_coords.y))
* 0.16;
sum += texture2D(texture, vec2(tex_coords.x + dt, tex_coords.y))
* 0.15;
sum += texture2D(texture, vec2(tex_coords.x + 2.0*dt, tex_coords.y))
* 0.12;
sum += texture2D(texture, vec2(tex_coords.x + 3.0*dt, tex_coords.y))
* 0.09;
sum += texture2D(texture, vec2(tex_coords.x + 4.0*dt, tex_coords.y))
* 0.05;
return vec4(sum.xyz, color.w);
}}
'''
According to the docs, the AdvancedEffectBase could help with such things, however, the problem is that I have no idea how to change these glsl codes in a way to achieve what I want.
I tried using others' glsl codes for applying a gaussian blur effect like these:
Shadertoy's code for gaussian blur effect
and others...
How am I supposed to achieve what I want?
UPDATE: @Tshirtman's answer seems to be the best one so far, however, I personally ran into a problem with it.
I use the blurred image in a separate screen and the image does not seem to follow the transition animation of the screenmanager, instead it just shows up while the other widgets slowly come into place. Is this fixable? Also, is there a way to up the resolution of the blur? It kind of seems low res.
My Code:
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.graphics import RenderContext
from kivymd.utils.fitimage import FitImage
from kivymd.app import MDApp
class BlurredBackgroundImage(FitImage):
def __init__(self, **kwargs):
fs = '''
$HEADER$
uniform vec2 resolution;
void main(void) {
int radius = 4;
vec2 d = float(radius) / resolution;
for (int dx = -radius; dx < radius; dx++)
for (int dy = -radius; dy < radius; dy++)
gl_FragColor += texture2D(texture0, tex_coord0 + vec2(float(dx), float(dy)) * d);
gl_FragColor /= float( 4 * radius * radius);
}
'''
self.canvas = RenderContext()
self.canvas.shader.fs = fs
super(BlurredBackgroundImage, self).__init__(**kwargs)
def on_size(self, *args):
self.canvas['projection_mat'] = Window.render_context['projection_mat']
self.canvas['modelview_mat'] = Window.render_context['modelview_mat']
self.canvas['resolution'] = list(map(float, self.size))
print("size changed")
# tried updating the shader whenever the position changes but still no improvements
'''def on_pos(self, *args):
self.canvas['projection_mat'] = Window.render_context['projection_mat']
self.canvas['modelview_mat'] = Window.render_context['modelview_mat']
self.canvas['resolution'] = list(map(float, self.size))
print("pos changed")'''
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.kv = Builder.load_string('''
ScreenManager:
Screen:
name: "main-menu"
Button:
text: "Go to next Page!"
on_release:
root.transition.direction = "down"
root.current = "song-view"
Screen:
name: "song-view"
RelativeLayout:
BlurredBackgroundImage:
source: "images/song_img.jpg"
Button:
text: "Return to main menu!"
size_hint: .25, .25
pos_hint: {"center_x": .5, "center_y": .5}
on_release:
root.transition.direction = "up"
root.current = "main-menu"
''')
def build(self):
return self.kv
if __name__ == '__main__':
MainApp().run()