-
-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#90: split cairo code so we can add the partial cython workarounds fo…
…r gtk3 git-svn-id: https://xpra.org/svn/Xpra/trunk@7557 3bb7dfac-3a0b-4e04-842a-767bc560f471
- Loading branch information
Showing
9 changed files
with
293 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# This file is part of Xpra. | ||
# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> | ||
# Copyright (C) 2012-2014 Antoine Martin <antoine@devloop.org.uk> | ||
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any | ||
# later version. See the file COPYING for details. | ||
|
||
import cairo | ||
|
||
from xpra.client.gtk_base.cairo_backing_base import CairoBackingBase | ||
from xpra.gtk_common.gtk_util import pixbuf_new_from_data, COLORSPACE_RGB | ||
from xpra.os_util import builtins | ||
_memoryview = builtins.__dict__.get("memoryview") | ||
|
||
from xpra.log import Logger | ||
log = Logger("paint", "cairo") | ||
|
||
|
||
""" | ||
With python2 / gtk2, we can create an ImageSurface using either: | ||
* cairo.ImageSurface.create_for_data | ||
* pixbuf_new_from_data | ||
Failing that, we use the horrible roundtrip via PNG using PIL. | ||
""" | ||
class CairoBacking(CairoBackingBase): | ||
|
||
#with gtk2 we can convert these directly to a cairo image surface: | ||
RGB_MODES = ["ARGB", "XRGB", "RGBA", "RGBX", "RGB"] | ||
|
||
|
||
def __repr__(self): | ||
return "gtk2.CairoBacking(%s)" % self._backing | ||
|
||
|
||
def _do_paint_rgb(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, options): | ||
""" must be called from UI thread """ | ||
log("cairo._do_paint_rgb(%s, %s, %s bytes,%s,%s,%s,%s,%s,%s)", cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, options) | ||
rgb_format = options.strget("rgb_format", "RGB") | ||
if _memoryview and isinstance(img_data, _memoryview): | ||
#Pixbuf cannot use the memoryview directly: | ||
img_data = img_data.tobytes() | ||
|
||
if rgb_format in ("ARGB", "XRGB"): | ||
#the pixel format is also what cairo expects | ||
#maybe we should also check that the stride is acceptable for cairo? | ||
#cairo_stride = cairo.ImageSurface.format_stride_for_width(cairo_format, width) | ||
#log("cairo_stride=%s, stride=%s", cairo_stride, rowstride) | ||
img_surface = cairo.ImageSurface.create_for_data(img_data, cairo_format, width, height, rowstride) | ||
return self.cairo_paint_surface(img_surface, x, y) | ||
|
||
if rgb_format in ("RGBA", "RGBX", "RGB"): | ||
#with GTK2, we can use a pixbuf from RGB(A) pixels | ||
if rgb_format=="RGBA": | ||
#we have to unpremultiply for pixbuf! | ||
img_data = self.unpremultiply(img_data) | ||
pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, has_alpha, 8, width, height, rowstride) | ||
return self.cairo_paint_pixbuf(pixbuf, x, y) | ||
|
||
self.nasty_rgb_via_png_paint(cairo_format, has_alpha, img_data, x, y, width, height, rowstride, rgb_format) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# This file is part of Xpra. | ||
# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> | ||
# Copyright (C) 2012-2014 Antoine Martin <antoine@devloop.org.uk> | ||
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any | ||
# later version. See the file COPYING for details. | ||
|
||
import cairo | ||
from gi.repository import GObject #@UnresolvedImport | ||
from gi.repository import GdkPixbuf #@UnresolvedImport | ||
|
||
from xpra.client.gtk_base.cairo_backing_base import CairoBackingBase | ||
from xpra.os_util import BytesIOClass | ||
from xpra.client.gtk_base.gtk_window_backing_base import GTKWindowBacking | ||
from xpra.client.window_backing_base import fire_paint_callbacks | ||
from xpra.os_util import builtins | ||
_memoryview = builtins.__dict__.get("memoryview") | ||
|
||
from xpra.client.gtk3.cairo_workaround import set_image_surface_data | ||
|
||
from xpra.log import Logger | ||
log = Logger("paint", "cairo") | ||
|
||
|
||
""" | ||
An area we draw onto with cairo | ||
This must be used with gtk3 since gtk3 no longer supports gdk pixmaps | ||
/RANT: ideally we would want to use pycairo's create_for_data method: | ||
#surf = cairo.ImageSurface.create_for_data(data, cairo.FORMAT_RGB24, width, height) | ||
but this is disabled in most cases, or does not accept our rowstride, so we cannot use it. | ||
Instead we have to use PIL to convert via a PNG or Pixbuf! | ||
""" | ||
class CairoBacking(CairoBackingBase): | ||
|
||
RGB_MODES = ["ARGB", "XRGB", "RGBA", "RGBX", "RGB"] | ||
|
||
|
||
def __repr__(self): | ||
return "gtk3.CairoBacking(%s)" % self._backing | ||
|
||
|
||
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): | ||
log("cairo.paint_image(%s, %s bytes,%s,%s,%s,%s,%s,%s) alpha_enabled=%s", coding, len(img_data), x, y, width, height, options, callbacks, self._alpha_enabled) | ||
#catch PNG and jpeg we can handle via cairo or pixbufloader respectively | ||
#(both of which need to run from the UI thread) | ||
if coding.startswith("png") or coding=="jpeg": | ||
def ui_paint_image(): | ||
if not self._backing: | ||
fire_paint_callbacks(callbacks, False) | ||
return | ||
try: | ||
if coding.startswith("png"): | ||
reader = BytesIOClass(img_data) | ||
img = cairo.ImageSurface.create_from_png(reader) | ||
success = self.cairo_paint_surface(img, x, y) | ||
else: | ||
assert coding=="jpeg" | ||
pbl = GdkPixbuf.PixbufLoader() | ||
pbl.write(img_data) | ||
pbl.close() | ||
pixbuf = pbl.get_pixbuf() | ||
del pbl | ||
success = self.cairo_paint_pixbuf(pixbuf, x, y) | ||
except: | ||
log.error("cairo error during paint", exc_info=True) | ||
success = False | ||
fire_paint_callbacks(callbacks, success) | ||
GObject.idle_add(ui_paint_image) | ||
return | ||
#this will end up calling do_paint_rgb24 after converting the pixels to RGB | ||
GTKWindowBacking.paint_image(self, coding, img_data, x, y, width, height, options, callbacks) | ||
|
||
|
||
def _do_paint_rgb(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, options): | ||
""" must be called from UI thread """ | ||
log("cairo._do_paint_rgb(%s, %s, %s bytes,%s,%s,%s,%s,%s,%s)", cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, options) | ||
if _memoryview and isinstance(img_data, _memoryview): | ||
#Pixbuf cannot use the memoryview directly: | ||
img_data = img_data.tobytes() | ||
|
||
rgb_format = options.strget("rgb_format", "RGB") | ||
#this format we can handle with the workaround: | ||
if format==cairo.FORMAT_RGB24 and rgb_format=="RGB": | ||
img_surface = cairo.ImageSurface(cairo_format, width, height) | ||
set_image_surface_data(img_surface, rgb_format, img_data, width, height, rowstride) | ||
return self.cairo_paint_surface(img_surface, x, y) | ||
|
||
self.nasty_rgb_via_png_paint(cairo_format, has_alpha, img_data, x, y, width, height, rowstride, rgb_format) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# This file is part of Xpra. | ||
# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk> | ||
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any | ||
# later version. See the file COPYING for details. | ||
|
||
# What is this workaround you ask? | ||
# Well, pycairo can't handle raw pixel data in RGB format, | ||
# and that's despite the documentation saying otherwise: | ||
# http://cairographics.org/pythoncairopil/ | ||
# Because of this glaring omission, we would have to roundtrip via PNG. | ||
# This workaround populates an image surface with RGB pixels using Cython. | ||
# (not the most efficient implementation, but still 10 times faster than the alternative) | ||
# | ||
#"cairo.ImageSurface.create_for_data" is not implemented in GTK3! | ||
# http://cairographics.org/documentation/pycairo/3/reference/surfaces.html#cairo.ImageSurface.create_for_data | ||
# "Not yet available in Python 3" | ||
# | ||
#It is available in the cffi cairo bindings, which can be used instead of pycairo | ||
# but then we can't use it from the draw callbacks: | ||
# https://mail.gnome.org/archives/python-hackers-list/2011-December/msg00004.html | ||
# "PyGObject just lacks the glue code that allows it to pass the statically-wrapped | ||
# cairo.Pattern to introspected methods" | ||
|
||
import cairo | ||
|
||
|
||
cdef extern from "../../codecs/buffers/buffers.h": | ||
int object_as_buffer(object obj, const void ** buffer, Py_ssize_t * buffer_len) | ||
|
||
cdef extern from "Python.h": | ||
ctypedef object PyObject | ||
object PyBuffer_FromMemory(void *ptr, Py_ssize_t size) | ||
int PyObject_AsReadBuffer(object obj, void ** buffer, Py_ssize_t * buffer_len) except -1 | ||
|
||
#void * PyCObject_Import(char *, char *) except NULL | ||
ctypedef struct PyTypeObject: | ||
pass | ||
ctypedef struct PyObject: | ||
Py_ssize_t ob_refcnt | ||
PyTypeObject *ob_type | ||
void * PyCapsule_Import(const char *name, int no_block) | ||
|
||
cdef extern from "cairo/cairo.h": | ||
ctypedef struct cairo_surface_t: | ||
pass | ||
|
||
#typedef enum _cairo_format { | ||
ctypedef enum cairo_format_t: | ||
CAIRO_FORMAT_INVALID | ||
CAIRO_FORMAT_ARGB32 | ||
CAIRO_FORMAT_RGB24 | ||
CAIRO_FORMAT_A8 | ||
CAIRO_FORMAT_A1 | ||
CAIRO_FORMAT_RGB16_565 | ||
CAIRO_FORMAT_RGB30 | ||
|
||
unsigned char * cairo_image_surface_get_data(cairo_surface_t *surface) | ||
cairo_format_t cairo_image_surface_get_format(cairo_surface_t *surface) | ||
|
||
int cairo_image_surface_get_width (cairo_surface_t *surface) | ||
int cairo_image_surface_get_height (cairo_surface_t *surface) | ||
int cairo_image_surface_get_stride (cairo_surface_t *surface) | ||
|
||
|
||
cdef extern from "pycairo/pycairo.h": | ||
ctypedef struct Pycairo_CAPI_t: | ||
PyTypeObject *ImageSurface_Type | ||
ctypedef struct PycairoSurface: | ||
#PyObject_HEAD | ||
cairo_surface_t *surface | ||
#PyObject *base; /* base object used to create surface, or NULL */ | ||
ctypedef PycairoSurface PycairoImageSurface | ||
|
||
CAIRO_FORMAT = { | ||
CAIRO_FORMAT_INVALID : "Invalid", | ||
CAIRO_FORMAT_ARGB32 : "ARGB32", | ||
CAIRO_FORMAT_RGB24 : "RGB24", | ||
CAIRO_FORMAT_A8 : "A8", | ||
CAIRO_FORMAT_A1 : "A1", | ||
CAIRO_FORMAT_RGB16_565 : "RGB16_565", | ||
CAIRO_FORMAT_RGB30 : "RGB30", | ||
} | ||
|
||
def set_image_surface_data(object image_surface, rgb_format, object pixel_data, int width, int height, int stride): | ||
print("set_image_surface_data%s" % str((image_surface, rgb_format, len(pixel_data), width, height, stride))) | ||
#convert pixel_data to a C buffer: | ||
cdef const unsigned char * cbuf = <unsigned char *> 0 | ||
cdef Py_ssize_t cbuf_len = 0 | ||
assert object_as_buffer(pixel_data, <const void**> &cbuf, &cbuf_len)==0, "cannot convert %s to a readable buffer" % type(pixel_data) | ||
assert cbuf_len>=height*stride, "pixel buffer is too small for %sx%s with stride=%s: only %s bytes, expected %s" % (width, height, stride, cbuf_len, height*stride) | ||
#convert cairo.ImageSurface python object to a cairo_surface_t | ||
if not isinstance(image_surface, cairo.ImageSurface): | ||
raise TypeError("object %r is not a %r" % (image_surface, cairo.ImageSurface)) | ||
cdef cairo_surface_t * surface = (<PycairoImageSurface *> image_surface).surface | ||
cdef unsigned char * data = cairo_image_surface_get_data(surface) | ||
#get surface attributes: | ||
cdef cairo_format_t format = cairo_image_surface_get_format(surface) | ||
cdef int istride = cairo_image_surface_get_stride(surface) | ||
cdef int iwidth = cairo_image_surface_get_width(surface) | ||
cdef int iheight = cairo_image_surface_get_height(surface) | ||
assert iwidth>=width and iheight>=height, "invalid image surface: expected at least %sx%s but got %sx%s" % (width, height, iwidth, iheight) | ||
assert istride>=iwidth*4, "invalid image stride: expected at least %s but got %s" % (iwidth*4, istride) | ||
#just deal with the formats we care about: | ||
if format==CAIRO_FORMAT_RGB24 and rgb_format=="RGB": | ||
for x in range(width): | ||
for y in range(height): | ||
data[x*4 + 0 + y*istride] = cbuf[x*3 + 0 + y*stride] #R | ||
data[x*4 + 1 + y*istride] = cbuf[x*3 + 1 + y*stride] #G | ||
data[x*4 + 2 + y*istride] = cbuf[x*3 + 2 + y*stride] #B | ||
data[x*4 + 3 + y*istride] = 255 #X | ||
return | ||
#note: this one is currently unused because it doesn't work | ||
#and I don't know why | ||
#(we just disable 'rgb32' for gtk3... and fallback to png) | ||
elif format==CAIRO_FORMAT_ARGB32 and rgb_format=="RGBA": | ||
for x in range(width): | ||
for y in range(height): | ||
data[x*4 + 0 + y*istride] = cbuf[x*4 + 3 + y*stride] #A | ||
data[x*4 + 1 + y*istride] = cbuf[x*4 + 0 + y*stride] #R | ||
data[x*4 + 2 + y*istride] = cbuf[x*4 + 1 + y*stride] #G | ||
data[x*4 + 3 + y*istride] = cbuf[x*4 + 2 + y*stride] #B | ||
return | ||
print("ERROR: cairo workaround not implemented for %s / %s" % (rgb_format, CAIRO_FORMAT.get(format, "unknown"))) | ||
|
||
cdef Pycairo_CAPI_t * Pycairo_CAPI | ||
Pycairo_CAPI = <Pycairo_CAPI_t*> PyCapsule_Import("cairo.CAPI", 0); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.