Esempio n. 1
0
class Picture(Scatter):

    img_texture = ObjectProperty()
    alpha = NumericProperty()
    onLoadCallback = None
    filename = None

    vertices = ListProperty([])

    fbo_texture = ObjectProperty(None)
    fbo = None

    border_image = None
    keep_aspect = True

    aspectRatio = 1.0

    # --
    def __init__(self,
                 filename=None,
                 onload=None,
                 maxtexsize=(1024, 1024),
                 **kwargs):

        if maxtexsize == None:
            maxtexsize = (1024, 1024)

        self.canvas = Canvas()

        with self.canvas:
            self.fbo = Fbo(size=maxtexsize)
            self.fbo.add_reload_observer(self.updateFbo)

        self.border_image = CoreImage('data/shadow32.png')
        self.img_texture = Texture.create(size=(16, 16), colorfmt="rgba")

        self.alpha = 0
        self.fbo_texture = self.fbo.texture

        super(Picture, self).__init__(**kwargs)

        self.loadImage(filename, onload)

    # --
    def updateFbo(self):

        with self.fbo:
            ClearColor(0, 0, 0, 0)
            ClearBuffers()
            Color(1, 1, 1, 1)
            BorderImage(texture=self.border_image.texture,
                        border=(36, 36, 36, 36),
                        size=(self.fbo.size[0], self.fbo.size[1]),
                        pos=(0, 0))
            Rectangle(texture=self.img_texture,
                      size=(self.fbo.size[0] - 72, self.fbo.size[1] - 72),
                      pos=(36, 36))

        self.fbo_texture = self.fbo.texture

        pass

    # --
    def on_size(self, instance, value):

        # change the underlying size property
        newAspectRatio = float(value[1]) / float(value[0])

        if self.keep_aspect:
            value[1] = value[0] * self.aspectRatio
            newAspectRatio = self.aspectRatio

        self.size = value

        # setup simple quad mesh to be rendered
        # the quad is used for actual resizing
        self.vertices = []
        self.vertices.extend([0, 0, 0, 0])
        self.vertices.extend([0, self.height, 0, 1])
        self.vertices.extend([self.width, self.height, 1, 1])
        self.vertices.extend([self.width, 0, 1, 0])

        # if the aspect ratio of the underlying FBO is not the same as the aspect
        # ratio of the new size, then change the size of the FBO
        fboAspectRatio = float(self.fbo.size[1]) / float(self.fbo.size[0])

        if abs(fboAspectRatio - newAspectRatio) > 0.1:
            fboSize = (self.fbo.size[0], self.fbo.size[1] * newAspectRatio)
            self.fbo.size = fboSize
            self.updateFbo()

        pass

    # --
    def loadImage(self, filename, onload=None):
        self.filename = filename
        if filename != None:
            self.onLoadCallback = onload

            proxyImage = Loader.image(filename)

            # this is totally stupid behaviour of Kivy
            # the docs suggest to bind proxy image's on_load method to a callback
            # to indicate when image is loaded. However, when image is already
            # in the cache the callback won't be called. My first thought was
            # that it was a threading issue, because the bindind might have happened
            # after the callback was initiated, but it seems that indeed the method is just not called.
            if proxyImage.loaded == False:
                proxyImage.bind(on_load=self._image_loaded)
            else:
                self._image_loaded(proxyImage)

    # --
    # All used memory except of the FBO texture is released. The image
    # can still be used, however, properties could not be changed
    def releaseMemory(self):

        self.img_texture = False
        self.border_image = False
        self.fbo_texture = Texture.create(size=(2, 2), colorfmt="rgba")

        # clear up cache
        Cache.remove('kv.image')
        Cache.remove('kv.texture')
        Cache.remove('kv.loader')

        pass

    # --
    def _image_loaded(self, proxyImage):

        if proxyImage.image.texture:
            self.img_texture = proxyImage.image.texture
            self.aspectRatio = float(proxyImage.image.height) / float(
                proxyImage.image.width)
            self.updateFbo()

            anim = Animation(alpha=1, duration=0.2)
            anim.start(self)

            if self.onLoadCallback != None:
                self.onLoadCallback(self)
class TextureStackBatchWidget(Widget):
    """Widget for efficiently drawing many TextureStacks

    Only add TextureStack or ImageStack widgets to this. Avoid adding
    any that are to be changed frequently.

    """
    critical_props = ['texs', 'offxs', 'offys', 'pos']
    """Properties that, when changed on my children, force a redraw."""

    def __init__(self, **kwargs):
        self._trigger_redraw = Clock.create_trigger(self.redraw)
        self._trigger_rebind_children = Clock.create_trigger(self.rebind_children)
        super(TextureStackBatchWidget, self).__init__(**kwargs)

    def on_parent(self, *args):
        if not self.canvas:
            Clock.schedule_once(self.on_parent, 0)
            return
        if not hasattr(self, '_fbo'):
            with self.canvas:
                self._fbo = Fbo(size=self.size)
                self._fbo.add_reload_observer(self.redraw)
                self._translate = Translate(x=self.x, y=self.y)
                self._rectangle = Rectangle(texture=self._fbo.texture, size=self.size)
        self.rebind_children()

    def rebind_children(self, *args):
        child_by_uid = {}
        binds = {prop: self._trigger_redraw for prop in self.critical_props}
        for child in self.children:
            child_by_uid[child.uid] = child
            child.bind(**binds)
        if hasattr(self, '_old_children'):
            old_children = self._old_children
            for uid in set(old_children).difference(child_by_uid):
                old_children[uid].unbind(**binds)
        self.redraw()
        self._old_children = child_by_uid

    def redraw(self, *args):
        fbo = self._fbo
        fbo.bind()
        fbo.clear()
        fbo.clear_buffer()
        fbo.release()
        for child in self.children:
            assert child.canvas not in fbo.children
            fbo.add(child.canvas)

    def on_pos(self, *args):
        if not hasattr(self, '_translate'):
            return
        self._translate.x, self._translate.y = self.pos

    def on_size(self, *args):
        if not hasattr(self, '_rectangle'):
            return
        self._rectangle.size = self._fbo.size = self.size
        self.redraw()

    def add_widget(self, widget, index=0, canvas=None):
        if not isinstance(widget, TextureStack):
            raise TypeError("TextureStackBatch is only for TextureStack")
        if index == 0 or len(self.children) == 0:
            self.children.insert(0, widget)
        else:
            children = self.children
            if index >= len(children):
                index = len(children)

            children.insert(index, widget)
        widget.parent = self
        if hasattr(self, '_fbo'):
            self.rebind_children()

    def remove_widget(self, widget):
        if widget not in self.children:
            return
        self.children.remove(widget)
        widget.parent = None
        if hasattr(self, '_fbo'):
            self.rebind_children()
Esempio n. 3
0
class Picture(Scatter):

    img_texture = ObjectProperty()
    alpha = NumericProperty()
    onLoadCallback = None
    filename = None

    vertices = ListProperty([])

    fbo_texture = ObjectProperty(None)
    fbo = None

    border_image = None
    keep_aspect = True

    aspectRatio = 1.0

    # --
    def __init__(self, filename=None, onload=None, maxtexsize=(1024, 1024), **kwargs):

        if maxtexsize == None:
            maxtexsize = (1024, 1024)

        self.canvas = Canvas()

        with self.canvas:
            self.fbo = Fbo(size=maxtexsize)
            self.fbo.add_reload_observer(self.updateFbo)

        self.border_image = CoreImage("data/shadow32.png")
        self.img_texture = Texture.create(size=(16, 16), colorfmt="rgba")

        self.alpha = 0
        self.fbo_texture = self.fbo.texture

        super(Picture, self).__init__(**kwargs)

        self.loadImage(filename, onload)

    # --
    def updateFbo(self):

        with self.fbo:
            ClearColor(0, 0, 0, 0)
            ClearBuffers()
            Color(1, 1, 1, 1)
            BorderImage(
                texture=self.border_image.texture,
                border=(36, 36, 36, 36),
                size=(self.fbo.size[0], self.fbo.size[1]),
                pos=(0, 0),
            )
            Rectangle(texture=self.img_texture, size=(self.fbo.size[0] - 72, self.fbo.size[1] - 72), pos=(36, 36))

        self.fbo_texture = self.fbo.texture

        pass

    # --
    def on_size(self, instance, value):

        # change the underlying size property
        newAspectRatio = float(value[1]) / float(value[0])

        if self.keep_aspect:
            value[1] = value[0] * self.aspectRatio
            newAspectRatio = self.aspectRatio

        self.size = value

        # setup simple quad mesh to be rendered
        # the quad is used for actual resizing
        self.vertices = []
        self.vertices.extend([0, 0, 0, 0])
        self.vertices.extend([0, self.height, 0, 1])
        self.vertices.extend([self.width, self.height, 1, 1])
        self.vertices.extend([self.width, 0, 1, 0])

        # if the aspect ratio of the underlying FBO is not the same as the aspect
        # ratio of the new size, then change the size of the FBO
        fboAspectRatio = float(self.fbo.size[1]) / float(self.fbo.size[0])

        if abs(fboAspectRatio - newAspectRatio) > 0.1:
            fboSize = (self.fbo.size[0], self.fbo.size[1] * newAspectRatio)
            self.fbo.size = fboSize
            self.updateFbo()

        pass

    # --
    def loadImage(self, filename, onload=None):
        self.filename = filename
        if filename != None:
            self.onLoadCallback = onload

            proxyImage = Loader.image(filename)

            # this is totally stupid behaviour of Kivy
            # the docs suggest to bind proxy image's on_load method to a callback
            # to indicate when image is loaded. However, when image is already
            # in the cache the callback won't be called. My first thought was
            # that it was a threading issue, because the bindind might have happened
            # after the callback was initiated, but it seems that indeed the method is just not called.
            if proxyImage.loaded == False:
                proxyImage.bind(on_load=self._image_loaded)
            else:
                self._image_loaded(proxyImage)

    # --
    # All used memory except of the FBO texture is released. The image
    # can still be used, however, properties could not be changed
    def releaseMemory(self):

        self.img_texture = False
        self.border_image = False
        self.fbo_texture = Texture.create(size=(2, 2), colorfmt="rgba")

        # clear up cache
        Cache.remove("kv.image")
        Cache.remove("kv.texture")
        Cache.remove("kv.loader")

        pass

    # --
    def _image_loaded(self, proxyImage):

        if proxyImage.image.texture:
            self.img_texture = proxyImage.image.texture
            self.aspectRatio = float(proxyImage.image.height) / float(proxyImage.image.width)
            self.updateFbo()

            anim = Animation(alpha=1, duration=0.2)
            anim.start(self)

            if self.onLoadCallback != None:
                self.onLoadCallback(self)