【问题标题】:Achieve optimal gaussian blur effect in kivy在kivy中实现最佳的高斯模糊效果
【发布时间】:2021-10-02 06:04:54
【问题描述】:

我正在使用 kivy & kivymd,我正在尝试以最佳方式将高斯模糊应用于作为背景图像的 FitImage

当然,我尝试了 EffectWidget,但是,考虑到我为它设置动画并且我希望用户能够调整窗口大小和根据文档,每次小部件发生更改时,效果小部件都会重新创建 fbo。

我使用的代码:

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()

所以我认为实现我想要的唯一合适的方法是更改​​ glsl 代码

ew.Horizo​​ntalBlurEffect的glsl代码:

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);
}}
'''

根据文档,AdvancedEffectBase 可以帮助解决这些问题,但是,问题是我不知道如何更改这些 glsl 代码以实现我想要的。

我尝试使用其他人的 glsl 代码来应用像这样的高斯模糊效果:

Shadertoy's code for gaussian blur effect

Mattdesl's code from github

还有其他人……

我应该如何实现我想要的?

更新:@Tshirtman 的答案似乎是迄今为止最好的答案,但是,我个人遇到了问题。

我在单独的屏幕中使用模糊图像,图像似乎没有跟随屏幕管理器的过渡动画,而是在其他小部件慢慢到位时显示出来。这是可以修复的吗?另外,有没有办法提高模糊的分辨率?有点像低分辨率。

我的代码:

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()

【问题讨论】:

    标签: python python-3.x kivy kivymd gaussianblur


    【解决方案1】:

    如果你不想使用 FBO,我认为你不应该使用 EffectWidget,而是直接在 RenderContext 上创建你的着色器,并将你想用作背景的纹理绑定到它,这样着色器就可以查找其中的像素

    存储库中有一个多纹理示例,我们可以将其用作起点

    https://github.com/kivy/kivy/blob/c28c47b39ae57c97c70cc0398d74774d73a6894b/examples/canvas/multitexture.py

    我会这样想(未经测试)。

    from kivy.clock import Clock
    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.lang import Builder
    from kivy.core.window import Window
    from kivy.graphics import RenderContext
    
    
    fs_multitexture = '''
    $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);
    }
    '''
    
    
    kv = """
    <BackgroundBluredImage>:
        canvas:
            Color:
            Rectangle:
                # the shader will apply where the widget draws, so we need a rectangle of the appropriate size/pos
                pos: self.pos
                size: self.size
                # binding the texture to it so we can look it up in the shader and do our thing
                source: "Coast.jpg"
    
    FloatLayout:
        BackgroundBluredImage:
    """
    
    
    class BackgroundBluredImage(Widget):
        def __init__(self, **kwargs):
            self.canvas = RenderContext()
            self.canvas.shader.fs = fs_multitexture
            super().__init__(**kwargs)
            # We'll update our glsl variables in a clock to run every frame, but you could bind it to relevant events only, like size and pos
            Clock.schedule_interval(self.update_glsl, 0)
    
        def update_glsl(self, *largs):
            # This is needed for the default vertex shader.
            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))
    
    
    class BluredBackgroundApp(App):
        def build(self):
            return Builder.load_string(kv)
    
    
    if __name__ == '__main__':
        BluredBackgroundApp().run()
    

    【讨论】:

    • 我不相信你的代码是正确的。可以更新一下吗?
    • 好的,确实有一些问题,现在已修复,我做了一个非常基本的模糊着色器,请随时改进。
    • 您的答案在调整小部件大小时似乎工作正常,但不适用于动画时。我在不同的屏幕中使用这个模糊的图像,并且小部件不像其他元素那样跟随动画。我更新了问题以获取更多信息
    猜你喜欢
    • 1970-01-01
    • 2018-05-01
    • 2010-09-11
    • 1970-01-01
    • 2016-07-08
    • 2010-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多