Evas.Image: Update to new Python buffer API, fix doc issues.

Needs testing.
This commit is contained in:
Kai Huuhko 2013-11-07 11:30:02 +02:00
parent 05cd30a2e3
commit b05187b52e
2 changed files with 163 additions and 83 deletions

View File

@ -2,3 +2,9 @@
=============================
.. autoclass:: efl.evas.Image
:class:`efl.evas.FilledImage` Class
===================================
.. autoclass:: efl.evas.FilledImage

View File

@ -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:
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)
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:
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`