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