diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index e3eab309d..a50b8de8c 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -42,15 +42,17 @@ Blitting custom data -------------------- You can create your own data and blit it to the texture using -:meth:`Texture.blit_data`:: +:meth:`Texture.blit_buffer`. + +For example, to blit immutable bytes data:: # create a 64x64 texture, defaults to rgb / ubyte texture = Texture.create(size=(64, 64)) - # create 64x64 rgb tab, and fill with value from 0 to 255 + # create 64x64 rgb tab, and fill with values from 0 to 255 # we'll have a gradient from black to white size = 64 * 64 * 3 - buf = [int(x * 255 / size) for x in xrange(size)] + buf = [int(x * 255 / size) for x in range(size)] # then, convert the array to a ubyte string buf = b''.join(map(chr, buf)) @@ -63,6 +65,34 @@ You can create your own data and blit it to the texture using with self.canvas: Rectangle(texture=texture, pos=self.pos, size=(64, 64)) +Since 1.8.1, you can blit data stored in a instance that implements the python +buffer interface, or a memoryview thereof, such as numpy arrays, python +`array.array`, a `bytearray`, or a cython array. This is beneficial if you +expect to blit similar data, with perhaps a few changes in the data. + +When using a bytes representation of the data, for every change you have to +regenerate the bytes instance, from perhaps a list, which is very inefficient. +When using a buffer object, you can simply edit parts of the original data. +Similarly, unless starting with a bytes object, converting to bytes requires a +full copy, however, when using a buffer instance, no memory is copied, except +to upload it to the GPU. + +Continuing with the example above:: + + from array import array + + size = 64 * 64 * 3 + buf = [int(x * 255 / size) for x in range(size)] + # initialize the array with the buffer values + arr = array('B', buf) + # now blit the array + texture.blit_buffer(arr, colorfmt='rgb', bufferfmt='ubyte') + + # now change some elements in the original array + arr[24] = arr[50] = 99 + # blit again the buffer + texture.blit_buffer(arr, colorfmt='rgb', bufferfmt='ubyte') + BGR/BGRA support ---------------- @@ -194,7 +224,6 @@ include "common.pxi" include "opengl_utils_def.pxi" include "img_tools.pxi" -from array import array from kivy.weakmethod import WeakMethod from kivy.graphics.context cimport get_context @@ -802,8 +831,15 @@ cdef class Texture: called in order to update the texture. :Parameters: - `pbuffer` : bytes - Image data. + `pbuffer` : bytes, or a class that implements the buffer interface\ + (including memoryview). + A buffer containing the image data. It can be either a bytes + object or a instance of a class that implements the python + buffer interface, e.g. `array.array`, `bytearray`, numpy arrays + etc. If it's not a bytes object, the underlying buffer must + be contiguous, have only one dimension and must not be + readonly, even though the data is not modified, due to a cython + limitation. See module description for usage details. `size` : tuple, defaults to texture size Size of the image (width, height) `colorfmt` : str, defaults to 'rgb' @@ -820,9 +856,12 @@ cdef class Texture: Indicate if we need to regenerate the mipmap from level 0. .. versionadded:: 1.0.7 - added `mipmap_level` and `mipmap_generation` + .. versionchanged:: 1.8.1 + `pbuffer` can now be any class instance that implements the python + buffer interface and / or memoryviews thereof. + ''' cdef GLuint target = self._target if colorfmt is None: @@ -839,20 +878,27 @@ cdef class Texture: # time. self.bind() - # need conversion ? - cdef bytes data - data = pbuffer - data, colorfmt = _convert_buffer(data, colorfmt) + # need conversion, do check here because it seems to be faster ? + if not gl_has_texture_native_format(colorfmt): + pbuffer, colorfmt = convert_to_gl_format(pbuffer, colorfmt) + cdef char [:] view + cdef char *cdata + cdef long datasize + if isinstance(pbuffer, bytes): # if it's bytes, just use memory + cdata = pbuffer # explicit bytes + datasize = len(pbuffer) + else: # if it's a memoryview or buffer type, use start of memory + view = pbuffer + cdata = &view[0] + datasize = view.nbytes # prepare nogil cdef int iglfmt = _color_fmt_to_gl(self._icolorfmt) cdef int glfmt = _color_fmt_to_gl(colorfmt) - cdef long datasize = len(pbuffer) cdef int x = pos[0] cdef int y = pos[1] cdef int w = size[0] cdef int h = size[1] - cdef char *cdata = data cdef int glbufferfmt = bufferfmt cdef int is_allocated = self._is_allocated cdef int is_compressed = _is_compressed_fmt(colorfmt) diff --git a/setup.py b/setup.py index 507f8d429..ad24dee47 100644 --- a/setup.py +++ b/setup.py @@ -428,7 +428,8 @@ graphics_dependencies = { 'texture.pxd': ['c_opengl.pxd'], 'texture.pyx': [ 'config.pxi', 'common.pxi', 'opengl_utils_def.pxi', 'context.pxd', - 'c_opengl.pxd', 'c_opengl_debug.pxd', 'opengl_utils.pxd'], + 'c_opengl.pxd', 'c_opengl_debug.pxd', 'opengl_utils.pxd', + 'img_tools.pxi'], 'vbo.pxd': ['buffer.pxd', 'c_opengl.pxd', 'vertex.pxd'], 'vbo.pyx': [ 'config.pxi', 'common.pxi', 'c_opengl_debug.pxd', 'context.pxd',