Trouble blitting to a texture inside an MDBoxLayout

176 views
Skip to first unread message

Ryan Anderson

unread,
Jul 6, 2021, 12:20:47 PM7/6/21
to Kivy users support
I'm working on an application that requires displaying images stored and manipulated in a numpy array.  I've written a simple widget to blit these images to a texture for display in Kivy(MD).  It works fine, until I try to use it with KivyMD containers.  Here's a simplified, reproducible example:

from kivy.uix.widget import Widget
from kivy.graphics.texture import Texture
from kivy.graphics.vertex_instructions import Rectangle
from kivymd.app import MDApp
from kivy.lang import Builder
import numpy


class TestImageWidget(Widget):
    def __init__(self, **kwargs):
    super(TestImageWidget, self).__init__(**kwargs)

    # create a simple test image for reproducibility, just a pair of colored diagonal lines on a black background
    image_size = 100
    test_layer = 255 * numpy.eye(image_size, dtype=numpy.uint8)
    test_image = numpy.dstack([test_layer,
                               numpy.fliplr(test_layer),
                               numpy.zeros_like(test_layer)])

    # this is simplified to provide a self contained reproducible example
    self.texture = Texture.create(size=(image_size, image_size), colorfmt='rgb', mipmap=False, bufferfmt='ubyte')
    self.texture.mag_filter = 'nearest'
    self.texture.min_filter = 'nearest'
    self.texture.blit_buffer(test_image.tobytes(order='C'), colorfmt='rgb', bufferfmt='ubyte')

with self.canvas:
    self.image_rect = Rectangle(texture=self.texture, size=self.size, pos=self.pos)

def on_pos(self, *args):
    self.image_rect.pos = self.pos

def on_size(self, *args):
    self.image_rect.size = self.size


KV = '''
BoxLayout:
    TestImageWidget:
'''

if __name__ == '__main__':
    class TestApp(MDApp):
        def build(self):
            return Builder.load_string(KV)

    TestApp().run()

This code works fine, and produces an image like this:
kivy_debug.png

I want to use KivyMD widgets in my App, however, and the image is not visible inside kivy MD Widgets.  If replace the BoxLayout with an MDBoxLayout, for example, I get this:

KV = '''
MDBoxLayout:
    TestImageWidget:
'''

kivymd_debug.png

In particular, I'd like to put one of these image widgets inside an MDTabs Widget, though the MDBoxLayout is a simpler example to provide here, and it behaves the same way. After digging through the KivyMD code for a bit, I believe that the problem exists with every widget that inherits from kivymd.uix.behaviours.backgroundcolor_behavior.BackgroundColorBehavior.  As such, I expected that setting md_bg_color to something like 0, 0, 0, 0 might eliminate the problem, but it doesn't appear to have any effect.  

Can anybody help me understand why my texture is invisible when it is placed inside a widget with BackgroundColorBehavior, and how to correct it?

Thank you!

Ryan Anderson

Ryan Anderson

unread,
Jul 6, 2021, 12:22:07 PM7/6/21
to Kivy users support
Small error in the code above:  the "with self.canvas" block should be indented one more level.

Robert

unread,
Jul 6, 2021, 12:33:56 PM7/6/21
to Kivy users support
Wild guess, perhaps self.texture is a property in those KivyMD widgets?
So change 'self.texture' to 'self.somethingelse'

Ryan Anderson

unread,
Jul 6, 2021, 12:41:47 PM7/6/21
to Kivy users support
Good thought, but that wasn't it.

Elliot Garbus

unread,
Jul 6, 2021, 3:10:53 PM7/6/21
to kivy-...@googlegroups.com

By deriving the TestImageWidget class from Image, rather than Widget, it works.  I moved some of the widget definition to kv.

 

from kivy.graphics.texture import Texture
from kivy.uix.image import Image
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.properties import ObjectProperty
import numpy


class TestImageWidget(Image):
    texture_x = ObjectProperty

   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)

       
# create a simple test image for reproducibility, just a pair of colored diagonal lines on a black background
       
image_size = 100
       
test_layer = 255 * numpy.eye(image_size, dtype=numpy.uint8)

        test_image = numpy.dstack([test_layer,
                                   numpy.fliplr(test_layer),
                                   numpy.zeros_like(test_layer)])

       
# this is simplified to provide a self contained reproducible example
       
self.texture_x = Texture.create(size=(image_size, image_size), colorfmt='rgb', mipmap=False, bufferfmt='ubyte')
       
self.texture_x.mag_filter = 'nearest'
       
self.texture_x.min_filter = 'nearest'
        
self.texture_x.blit_buffer(test_image.tobytes(order='C'), colorfmt='rgb', bufferfmt='ubyte')



KV =
'''
<TestImageWidget>:
    texture: self.texture_x
    allow_stretch: True
    keep_ratio: False

MDBoxLayout:
    TestImageWidget:
'''

 

I want to use KivyMD widgets in my App, however, and the image is not visible inside kivy MD Widgets.  If replace the BoxLayout with an MDBoxLayout, for example, I get this:

 

KV = '''
MDBoxLayout:
    TestImageWidget:
'''

 

 

In particular, I'd like to put one of these image widgets inside an MDTabs Widget, though the MDBoxLayout is a simpler example to provide here, and it behaves the same way. After digging through the KivyMD code for a bit, I believe that the problem exists with every widget that inherits from kivymd.uix.behaviours.backgroundcolor_behavior.BackgroundColorBehavior.  As such, I expected that setting md_bg_color to something like 0, 0, 0, 0 might eliminate the problem, but it doesn't appear to have any effect.  

 

Can anybody help me understand why my texture is invisible when it is placed inside a widget with BackgroundColorBehavior, and how to correct it?

 

Thank you!

 

Ryan Anderson

 

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/fd01f3f1-212c-40d0-9e49-1d02a0bf960an%40googlegroups.com.

 

Ryan Anderson

unread,
Jul 6, 2021, 3:26:06 PM7/6/21
to Kivy users support
Thank you!  It indeed works, and there is other nice functionality added by deriving from Image as well.   

I would love to understand, however, why the KivyMD BackgroundColorBehavior impacts the Widget-derived version, but not the Image derived version.  What is Image doing to seperate the texture from the effects of the parent Layout?

Elliot Garbus

unread,
Jul 6, 2021, 3:58:53 PM7/6/21
to kivy-...@googlegroups.com

Good question…

For your amusement here was my thinking… I have seen lots of kivyMD apps with photos, and Image has a texture… so I thought it was worth a try.

 

You asked why… damn good question.  So I at the source for image, nothing obvious there; and the kv source for Image in kivy/data/style.kv

 

<Image,AsyncImage>:
    canvas:
        Color:
            rgba: self.color
        Rectangle:
            texture: self.texture
            size: self.norm_image_size
            pos: self.center_x - self.norm_image_size[0] / 2., self.center_y - self.norm_image_size[1] / 2.

 

I noticed Color was set, so I tried that.  That works with Widget.  See below.

 

 

 

from kivy.graphics.texture import Texture
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.properties import ObjectProperty
import numpy


class TestImageWidget(Widget):
    texture_x = ObjectProperty

   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)

       
# create a simple test image for reproducibility, just a pair of colored diagonal lines on a black background
       
image_size = 100
       
test_layer = 255 * numpy.eye(image_size, dtype=numpy.uint8)

        test_image = numpy.dstack([test_layer,
                                   numpy.fliplr(test_layer),
                                   numpy.zeros_like(test_layer)])

       
# this is simplified to provide a self contained reproducible example
       
self.texture_x = Texture.create(size=(image_size, image_size), colorfmt='rgb', mipmap=False, bufferfmt='ubyte')
       
self.texture_x.mag_filter = 'nearest'
       
self.texture_x.min_filter = 'nearest'
       
self.texture_x.blit_buffer(test_image.tobytes(order='C'), colorfmt='rgb', bufferfmt='ubyte')



KV =
'''
<TestImageWidget>:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            texture: self.texture_x
            size: self.size
            pos: self.pos

MDBoxLayout:
    TestImageWidget:
'''

Ryan Anderson

unread,
Jul 6, 2021, 4:16:45 PM7/6/21
to Kivy users support
Adding a Color(1, 1, 1, 1) call (from kivy.graphics.context_instructions) before the Rectangle() call in my original code works as well, with no KV code at all.  KivyMD's BackgroundColorBehavior class's Color() calls must impacting the state of the drawing canvas at the time that the texture is rendered in child widgets??   I'm not sure if that is intended behavior, but it sure confused me!  I've definitely found the state of the canvas to be one of the more difficult parts of this framework to wrap my head around.  

Anyway, thank you for your help. 

Elliot Garbus

unread,
Jul 6, 2021, 4:31:18 PM7/6/21
to kivy-...@googlegroups.com

I’m glad to hear that worked. 

Canvas is a thin abstraction over OpenGL – this could be a typical OpenGL behavior – I don’t know.    I suspect your assumption about Color changing the state is correct. 

 

A number of the Devs are on the discord channel if you are curious enough – they might have an answer.

Reply all
Reply to author
Forward
0 new messages