kuuko pushed a commit to branch master. http://git.enlightenment.org/bindings/python/python-efl.git/commit/?id=b05187b52e8d825fafa514afa7ad22afd7482ae8
commit b05187b52e8d825fafa514afa7ad22afd7482ae8 Author: Kai Huuhko <kai.huu...@gmail.com> Date: Thu Nov 7 11:30:02 2013 +0200 Evas.Image: Update to new Python buffer API, fix doc issues. Needs testing. --- doc/evas/class-object-image.rst | 6 + efl/evas/efl.evas_object_image.pxi | 240 ++++++++++++++++++++++++------------- 2 files changed, 163 insertions(+), 83 deletions(-) diff --git a/doc/evas/class-object-image.rst b/doc/evas/class-object-image.rst index 47e7aaa..b30cbd5 100644 --- a/doc/evas/class-object-image.rst +++ b/doc/evas/class-object-image.rst @@ -2,3 +2,9 @@ ============================= .. autoclass:: efl.evas.Image + + +:class:`efl.evas.FilledImage` Class +=================================== + +.. autoclass:: efl.evas.FilledImage diff --git a/efl/evas/efl.evas_object_image.pxi b/efl/evas/efl.evas_object_image.pxi index 684a3d0..f698db9 100644 --- a/efl/evas/efl.evas_object_image.pxi +++ b/efl/evas/efl.evas_object_image.pxi @@ -15,16 +15,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. - -# TODO: remove me after usage is update to new buffer api cdef extern from "Python.h": - int PyObject_AsReadBuffer(obj, void **buffer, Py_ssize_t *buffer_len) except -1 + PyObject * PyMemoryView_FromBuffer(Py_buffer *info) + +from cpython.buffer cimport Py_buffer, PyObject_CheckBuffer, \ + PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE +from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free -# TODO needed? neem like the frong place to define fill/rotation stuff... -# def image_mask_fill(Image source, Image mask, Image surface, int x_mask, int y_mask, int x_surface, int y_surface): -# evas_object_image_mask_fill(source.obj, mask.obj, surface.obj, -# x_mask, y_mask, x_surface, y_surface) cdef int _data_size_get(Evas_Object *obj): cdef int stride, h, bpp, cspace, have_alpha @@ -52,38 +50,38 @@ cdef class Image(Object): .. rubric:: Introduction - Image will consider the object's :py:func:`geometry<geometry_set()>` - as the area to paint with tiles as described by :py:func:`fill_set()` and the + Image will consider the object's :py:attr:`geometry` + as the area to paint with tiles as described by :py:attr:`fill` and the real pixels (image data) will be stored as described by - :py:func:`image_size<image_size_set()>`. This can be tricky to understand at + :py:attr:`image_size`. This can be tricky to understand at first, but gives flexibility to do everything. If an image is loaded from file, it will have - :py:func:`image_size<image_size_set()>` set to its original size, unless some - other size was set with :py:func:`load_size_set()`, :py:func:`load_dpi_set()` or - :py:func:`load_scale_down_set()`. + :py:attr:`image_size` set to its original size, unless some + other size was set with :py:attr:`load_size`, :py:attr:`load_dpi` or + :py:attr:`load_scale_down`. - Pixels will be scaled to match size specified by :py:func:`fill_set()` + Pixels will be scaled to match size specified by :py:attr:`fill` using either sampled or smooth methods, these can be specified with - :py:func:`smooth_scale_set()`. The scale will consider borders as specified by - :py:func:`border_set()` and :py:func:`border_center_fill_set()`, while the former specify + :py:attr:`smooth_scale`. The scale will consider borders as specified by + :py:attr:`border` and :py:attr:`border_center_fill`, while the former specify the border dimensions (top and bottom will scale horizontally, while right and left will do vertically, corners are kept unscaled), the latter says whenever the center of the image will be used (useful to create frames). - Contents will be tiled using :py::`fill_set()` information in order to paint - :py:func:`geometry<Object.geometry_set()>`, so if you want an image to be drawn - just once, you should match every :py:func:`geometry_set(x, y, w, h)` by a call - to :py:func:`fill_set(0, 0, w, h)`. :py:class:`FilledImage` does that for you. + Contents will be tiled using :py:attr:`fill` information in order to paint + :py:attr:`geometry<Object.geometry>`, so if you want an image to be drawn + just once, you should match every :py:attr:`geometry` = **x, y, w, h** by a call + to :py:attr:`fill` = **0, 0, w, h**. :py:class:`FilledImage` does that for you. .. rubric:: Pixel data and buffer API Images implement the Python Buffer API, so it's possible to use it where buffers are expected (ie: file.write()). Available data will - depend on :py:func:`alpha<alpha_set()>`, :py:func:`colorspace<colorspace_set()>` and - :py:func:`image_size<image_size_set()>`, lines should be considered multiple - of :py:func:`stride<stride_get()>`, with the following considerations about + depend on :py:attr:`alpha`, :py:attr:`colorspace` and + :py:attr:`image_size`, lines should be considered multiple + of :py:attr:`stride`, with the following considerations about colorspace: - **EVAS_COLORSPACE_ARGB8888:** This pixel format is a linear block of @@ -101,7 +99,8 @@ cdef class Image(Object): So 50% transparent blue will be: 0x80000080. This will not be "dark" - just 50% transparent. Values are 0 == black, 255 == solid or full red, green or blue. - - **EVAS_COLORSPACE_RGB565_A5P:** In the process of being implemented in + + .. **EVAS_COLORSPACE_RGB565_A5P:** In the process of being implemented in 1 engine only. This may change. This is a pointer to image data for 16-bit half-word pixel data in 16bpp RGB 565 format (5 bits red, 6 bits green, 5 bits blue), with the high-byte containing red and the @@ -473,23 +472,125 @@ cdef class Image(Object): :type buf: data """ - cdef const_void *p_data - cdef Py_ssize_t size, expected_size + cdef: + Py_ssize_t expected_size + Py_buffer view if buf is None: evas_object_image_data_set(self.obj, NULL) return - # TODO: update to new buffer api - PyObject_AsReadBuffer(buf, &p_data, &size) - if p_data != NULL: - expected_size = _data_size_get(self.obj) - if size < expected_size: - raise ValueError(("buffer size (%d) is smalled than expected " - "(%d)!") % (size, expected_size)) - evas_object_image_data_set(self.obj,<void *> p_data) + if not PyObject_CheckBuffer(buf): + raise TypeError("The provided object does not support buffer interface.") + + PyObject_GetBuffer(buf, &view, PyBUF_SIMPLE) + + expected_size = _data_size_get(self.obj) + if view.itemsize < expected_size: + raise ValueError( + "buffer size (%d) is smaller than expected (%d)!" % ( + view.itemsize, expected_size + ) + ) + + evas_object_image_data_set(self.obj, <void *>view.buf) + + PyBuffer_Release(&view) + + def image_data_memoryview_get(self, bint for_writing=False, bint simple=True): + """image_data_memoryview_get(bool for_writing) -> MemoryView + + Get a MemoryView object to the raw image data of the given image object. + + :param bool for_writing: Whether the data being retrieved will be + modified or not. + :param bool simple: Whether the MemoryView is 1D or 2D + :return MemoryView: The raw image data. + + This method returns a MemoryView object to an image object's internal pixel + buffer, for reading only or read/write. If you request it for + writing, the image will be marked dirty so that it gets redrawn at + the next update. + + Each time you call this method on an image object, its data + buffer will have an internal reference counter + incremented. Decrement it back by using + :py:func:`image_data_set`. + + This is best suited for when you want to modify an existing image, + without changing its dimensions. + + .. note:: + The contents' format returned by it depend on the color + space of the given image object. + + .. note:: + You may want to use :py:func:`image_data_update_add` to + inform data changes, if you did any. + + """ + cdef int stride, h, bpp, cspace, have_alpha, img_size + + stride = evas_object_image_stride_get(self.obj) + evas_object_image_size_get(self.obj, NULL, &h) + cspace = evas_object_image_colorspace_get(self.obj) + have_alpha = evas_object_image_alpha_get(self.obj) + + bpp = 0 + if cspace == EVAS_COLORSPACE_ARGB8888: + bpp = 4 + format = "L" + elif cspace == EVAS_COLORSPACE_RGB565_A5P: + if have_alpha == 0: + bpp = 2 + format = "H" + else: + pass #bpp = 3 + # XXX: There's no type that has three bytes. + # Is the format string actually used? + if bpp == 0: + raise ValueError("Unsupported colorspace") + + img_size = stride * h * bpp + + cdef Py_buffer *img_buf = <Py_buffer *>PyMem_Malloc(sizeof(Py_buffer)) + if img_buf == NULL: + raise MemoryError + + cdef: + Py_ssize_t simple_shape[1] + Py_ssize_t shape[2] + Py_ssize_t strides[2] + Py_ssize_t suboffsets[2] + + if simple: + simple_shape[0] = img_size + else: + shape[0] = stride / bpp + shape[1] = h + strides[0] = stride + strides[1] = h * bpp + suboffsets[0] = -1 + suboffsets[1] = -1 + + img_buf.buf = evas_object_image_data_get(self.obj, for_writing) + img_buf.len = img_size + img_buf.readonly = not for_writing + img_buf.format = format + if simple: + img_buf.ndim = 1 + img_buf.shape = simple_shape + img_buf.strides = NULL + img_buf.suboffsets = NULL + else: + img_buf.ndim = 2 + img_buf.shape = shape + img_buf.strides = strides + img_buf.suboffsets = suboffsets + img_buf.itemsize = bpp + + return <object>PyMemoryView_FromBuffer(img_buf) - # TODO: def image_data_get(self): # TODO: # def image_data_convert(self, to_cspace): @@ -814,13 +915,16 @@ cdef class Image(Object): - **EVAS_COLORSPACE_ARGB8888** ARGB 32 bits per pixel, high-byte is Alpha, accessed 1 32bit word at a time. - - **EVAS_COLORSPACE_YCBCR422P601_PL** YCbCr 4:2:2 Planar, ITU.BT-601 + + .. **EVAS_COLORSPACE_YCBCR422P601_PL** YCbCr 4:2:2 Planar, ITU.BT-601 specifications. The data poitned to is just an array of row pointer, pointing to the Y rows, then the Cb, then Cr rows. - - **EVAS_COLORSPACE_YCBCR422P709_PL** YCbCr 4:2:2 Planar, ITU.BT-709 + + .. **EVAS_COLORSPACE_YCBCR422P709_PL** YCbCr 4:2:2 Planar, ITU.BT-709 specifications. The data poitned to is just an array of row pointer, pointing to the Y rows, then the Cb, then Cr rows. - - **EVAS_COLORSPACE_RGB565_A5P** 16bit rgb565 + Alpha plane at end - + + .. **EVAS_COLORSPACE_RGB565_A5P** 16bit rgb565 + Alpha plane at end - 5 bits of the 8 being used per alpha byte. :type: Evas_Colorspace @@ -952,7 +1056,7 @@ cdef class Image(Object): def alpha_mask_set(self, bint ismask): evas_object_image_alpha_mask_set(self.obj, ismask) - property image_source: + property source: """The source object on an image object to used as a **proxy**. If an image object is set to behave as a **proxy**, it will mirror @@ -1193,50 +1297,20 @@ cdef class Image(Object): evas_object_image_animated_frame_set(self.obj, frame_num) - - def __getsegcount__(self, Py_ssize_t *p_len): - if p_len == NULL: - return 1 - - p_len[0] = _data_size_get(self.obj) - return 1 - - def __getreadbuffer__(self, int segment, void **ptr): - ptr[0] = evas_object_image_data_get(self.obj, 0) - if ptr[0] == NULL: - raise SystemError("image has no allocated buffer.") - # XXX: keep Evas pixels_checked_out counter to 0 and allow - # XXX: image to reload and unload its data. - # XXX: may cause problems if buffer is used after these - # XXX: functions are called, but buffers aren't expected to - # XXX: live much. - evas_object_image_data_set(self.obj, ptr[0]) - return _data_size_get(self.obj) - - def __getwritebuffer__(self, int segment, void **ptr): - ptr[0] = evas_object_image_data_get(self.obj, 1) - if ptr[0] == NULL: - raise SystemError("image has no allocated buffer.") - # XXX: keep Evas pixels_checked_out counter to 0 and allow - # XXX: image to reload and unload its data. - # XXX: may cause problems if buffer is used after these - # XXX: functions are called, but buffers aren't expected to - # XXX: live much. - evas_object_image_data_set(self.obj, ptr[0]) - return _data_size_get(self.obj) - - def __getcharbuffer__(self, int segment, char **ptr): - ptr[0] = <char *>evas_object_image_data_get(self.obj, 0) - if ptr[0] == NULL: + def __getbuffer__(self, Py_buffer *view, int flags): + view.buf = evas_object_image_data_get(self.obj, not view.readonly) + if view.buf == NULL: raise SystemError("image has no allocated buffer.") - # XXX: keep Evas pixels_checked_out counter to 0 and allow - # XXX: image to reload and unload its data. - # XXX: may cause problems if buffer is used after these - # XXX: functions are called, but buffers aren't expected to - # XXX: live much. - evas_object_image_data_set(self.obj, ptr[0]) - return _data_size_get(self.obj) + view.len = _data_size_get(self.obj) + view.format = "L" + view.ndim = 1 + view.itemsize = 4 + # TODO: Check flags, provide multidim if possible, handle other + # colorspaces + def __releasebuffer__(self, Py_buffer *view): + evas_object_image_data_set(self.obj, view.buf) + # TODO: Free possibly allocated memory here (shape, strides, suboffsets) def on_image_preloaded_add(self, func, *a, **k): @@ -1273,8 +1347,8 @@ cdef class FilledImage(Image): Image that automatically resize it's contents to fit object size. - This :py::`Image` subclass already calls :py::`Image.fill_set()` on resize so - it will match and so be scaled to fill the whole area. + This :py:class:`Image` subclass already calls :py:func:`Image.fill_set` + on resize so it will match and so be scaled to fill the whole area. :param canvas: The evas canvas for this object :type canvas: :py:class:`Canvas` --