From 1d104c1771b8d6573ace9c39e3ef730e797ace68 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 7 May 2014 14:37:46 +0000 Subject: [PATCH] fix cairo backing for gtk3 - LONG, see comments: * try cairo.ImageSurface.create_for_data (GTK2 only) * try pixbuf (GTK2 only) * fallback to PNG via PIL... (GTK3) Also import cairo via compat wrapper so we can potentially interpose something else (even though we cannot use cairocffi with the do_paint callbacks..) git-svn-id: https://xpra.org/svn/Xpra/trunk@6394 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/client/gl/gl_window_backing.py | 10 +-- src/xpra/client/gtk2/client_window.py | 6 +- src/xpra/client/gtk2/gtk2_window_base.py | 2 - src/xpra/client/gtk2/pixmap_backing.py | 2 + src/xpra/client/gtk_base/cairo_backing.py | 69 +++++++++++++++---- .../client/gtk_base/gtk_client_window_base.py | 4 +- .../gtk_base/gtk_window_backing_base.py | 4 +- src/xpra/client/window_backing_base.py | 8 +-- src/xpra/gtk_common/gobject_compat.py | 8 +++ src/xpra/gtk_common/gtk_spinner.py | 6 +- 10 files changed, 85 insertions(+), 34 deletions(-) diff --git a/src/xpra/client/gl/gl_window_backing.py b/src/xpra/client/gl/gl_window_backing.py index 64f6f6b666..701e975fe4 100644 --- a/src/xpra/client/gl/gl_window_backing.py +++ b/src/xpra/client/gl/gl_window_backing.py @@ -434,13 +434,13 @@ def gl_expose_event(self, glarea, event): finally: drawable.gl_end() - def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, callbacks): - return self._do_paint_rgb(32, img_data, x, y, width, height, rowstride, options, callbacks) + def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options): + return self._do_paint_rgb(32, img_data, x, y, width, height, rowstride, options) - def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): - return self._do_paint_rgb(24, img_data, x, y, width, height, rowstride, options, callbacks) + def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options): + return self._do_paint_rgb(24, img_data, x, y, width, height, rowstride, options) - def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options, callbacks): + def _do_paint_rgb(self, bpp, img_data, x, y, width, height, rowstride, options): log("%s._do_paint_rgb(%s, %s bytes, x=%d, y=%d, width=%d, height=%d, rowstride=%d)", self, bpp, len(img_data), x, y, width, height, rowstride) drawable = self.gl_init() if not drawable: diff --git a/src/xpra/client/gtk2/client_window.py b/src/xpra/client/gtk2/client_window.py index e660c5bc53..fe93d4238f 100644 --- a/src/xpra/client/gtk2/client_window.py +++ b/src/xpra/client/gtk2/client_window.py @@ -44,9 +44,9 @@ def get_full_csc_modes(self): #plain GTK2 window needs to use CSC modules to paint video #so calculate the server CSC modes the server is allowed to use #based on the client CSC modes we can convert to RGB(A): - target_rgb_modes = ["RGB", "RGBX"] - if HAS_ALPHA: - target_rgb_modes.append("RGBA") + target_rgb_modes = BACKING_CLASS.RGB_MODES + if not HAS_ALPHA: + target_rgb_modes = [x for x in target_rgb_modes if x.find("A")<0] ClientWindow.full_csc_modes = getVideoHelper().get_server_full_csc_modes_for_rgb(*target_rgb_modes) log("full csc modes (%s)=%s", target_rgb_modes, ClientWindow.full_csc_modes) return ClientWindow.full_csc_modes diff --git a/src/xpra/client/gtk2/gtk2_window_base.py b/src/xpra/client/gtk2/gtk2_window_base.py index 1db8c131a7..cd3d94d0c5 100644 --- a/src/xpra/client/gtk2/gtk2_window_base.py +++ b/src/xpra/client/gtk2/gtk2_window_base.py @@ -81,8 +81,6 @@ class GTK2WindowBase(GTKClientWindowBase): WINDOW_STATE_MAXIMIZED = gdk.WINDOW_STATE_MAXIMIZED WINDOW_STATE_ICONIFIED = gdk.WINDOW_STATE_ICONIFIED - #must be overriden by subclasses - BACKING_CLASS = None def init_window(self, metadata): if self._override_redirect: diff --git a/src/xpra/client/gtk2/pixmap_backing.py b/src/xpra/client/gtk2/pixmap_backing.py index 20e5641e24..572abbf8e9 100644 --- a/src/xpra/client/gtk2/pixmap_backing.py +++ b/src/xpra/client/gtk2/pixmap_backing.py @@ -19,6 +19,8 @@ """ class PixmapBacking(GTK2WindowBacking): + RGB_MODES = ["RGB", "RGBX", "RGBA"] + def __repr__(self): return "PixmapBacking(%s)" % self._backing diff --git a/src/xpra/client/gtk_base/cairo_backing.py b/src/xpra/client/gtk_base/cairo_backing.py index 5f74d546e6..fbf845ea71 100644 --- a/src/xpra/client/gtk_base/cairo_backing.py +++ b/src/xpra/client/gtk_base/cairo_backing.py @@ -4,17 +4,17 @@ # 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.gtk_common.gobject_compat import import_gdk, import_gobject, import_pixbufloader +from xpra.gtk_common.gobject_compat import import_gdk, import_gobject, import_pixbufloader, import_cairo, is_gtk3 gdk = import_gdk() gobject = import_gobject() +cairo = import_cairo() PixbufLoader = import_pixbufloader() from xpra.os_util import BytesIOClass from xpra.gtk_common.gtk_util import pixbuf_new_from_data, cairo_set_source_pixbuf, gdk_cairo_context, COLORSPACE_RGB from xpra.client.gtk_base.gtk_window_backing_base import GTKWindowBacking from xpra.client.window_backing_base import fire_paint_callbacks +from xpra.codecs.loader import get_codec from xpra.os_util import builtins _memoryview = builtins.__dict__.get("memoryview") @@ -34,6 +34,8 @@ """ class CairoBacking(GTKWindowBacking): + RGB_MODES = ["ARGB", "XRGB", "RGBA", "RGBX", "RGB"] + def __init__(self, wid, w, h, has_alpha): GTKWindowBacking.__init__(self, wid) @@ -131,25 +133,66 @@ def cairo_paint_surface(self, img_surface, x, y): return True - def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): + def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options): log("_do_paint_rgb24") - return self._do_paint_rgb(False, img_data, x, y, width, height, rowstride, options, callbacks) + return self._do_paint_rgb(cairo.FORMAT_RGB24, False, img_data, x, y, width, height, rowstride, options) - def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, callbacks): + def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options): log("_do_paint_rgb32") - return self._do_paint_rgb(True, img_data, x, y, width, height, rowstride, options, callbacks) + return self._do_paint_rgb(cairo.FORMAT_ARGB32, True, img_data, x, y, width, height, rowstride, options) - def _do_paint_rgb(self, has_alpha, img_data, x, y, width, height, rowstride, 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 bytes,%s,%s,%s,%s,%s,%s,%s)", has_alpha, len(img_data), x, y, width, height, rowstride, options, callbacks) + log("cairo._do_paint_rgb(%s, %s bytes,%s,%s,%s,%s,%s,%s)", has_alpha, len(img_data), x, y, width, height, rowstride, options) rgb_format = options.strget("rgb_format", "RGB") - if rgb_format in ("RGBA", ): - img_data = self.unpremultiply(img_data) if _memoryview and isinstance(img_data, _memoryview): #Pixbuf cannot use the memoryview directly: img_data = img_data.tobytes() - pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, has_alpha, 8, width, height, rowstride) - return self.cairo_paint_pixbuf(pixbuf, x, y) + #"cairo.ImageSurface.create_for_data" is not implemented in GTK3! ARGH! + # 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" + + if not is_gtk3() and 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 not is_gtk3() and rgb_format in ("RGBA", "RGBX"): + #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) + + #PIL fallback + PIL = get_codec("PIL") + if has_alpha: + oformat = "RGBA" + else: + oformat = "RGB" + img = PIL.Image.frombuffer(oformat, (width,height), img_data, "raw", rgb_format, 0, 1) + #This is insane, the code below should work, but it doesn't: + # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) + # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, width*4) + # success = self.cairo_paint_pixbuf(pixbuf, x, y) + #So we still rountrip via PNG: + png = BytesIOClass() + img.save(png, format="PNG") + reader = BytesIOClass(png.getvalue()) + png.close() + img = cairo.ImageSurface.create_from_png(reader) + return self.cairo_paint_surface(img, x, y) + def cairo_draw(self, context): log("cairo_draw(%s) backing=%s", context, self._backing) diff --git a/src/xpra/client/gtk_base/gtk_client_window_base.py b/src/xpra/client/gtk_base/gtk_client_window_base.py index f6ab4a15c8..5df7fd15f8 100644 --- a/src/xpra/client/gtk_base/gtk_client_window_base.py +++ b/src/xpra/client/gtk_base/gtk_client_window_base.py @@ -12,13 +12,13 @@ keylog = Logger("window", "keyboard") from xpra.util import AdHocStruct, nn -from xpra.gtk_common.gobject_compat import import_gtk, import_gdk +from xpra.gtk_common.gobject_compat import import_gtk, import_gdk, import_cairo from xpra.client.client_window_base import ClientWindowBase gtk = import_gtk() gdk = import_gdk() +cairo = import_cairo() import os -import cairo import time import math diff --git a/src/xpra/client/gtk_base/gtk_window_backing_base.py b/src/xpra/client/gtk_base/gtk_window_backing_base.py index 77c341e9cd..31ed17fca8 100644 --- a/src/xpra/client/gtk_base/gtk_window_backing_base.py +++ b/src/xpra/client/gtk_base/gtk_window_backing_base.py @@ -5,8 +5,9 @@ # later version. See the file COPYING for details. #pygtk3 vs pygtk2 (sigh) -from xpra.gtk_common.gobject_compat import import_gobject +from xpra.gtk_common.gobject_compat import import_gobject, import_cairo gobject = import_gobject() +cairo = import_cairo() from xpra.client.window_backing_base import WindowBackingBase from xpra.log import Logger @@ -29,7 +30,6 @@ def cairo_draw(self, context): def cairo_draw_from_drawable(self, context, drawable): if drawable is None: return - import cairo try: context.set_source_pixmap(drawable, 0, 0) context.set_operator(cairo.OPERATOR_SOURCE) diff --git a/src/xpra/client/window_backing_base.py b/src/xpra/client/window_backing_base.py index ed197f7452..e7559a6b1d 100644 --- a/src/xpra/client/window_backing_base.py +++ b/src/xpra/client/window_backing_base.py @@ -207,7 +207,7 @@ def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, call the actual paint code is in _do_paint_rgb24 """ try: - success = (self._backing is not None) and self._do_paint_rgb24(img_data, x, y, width, height, rowstride, options, callbacks) + success = (self._backing is not None) and self._do_paint_rgb24(img_data, x, y, width, height, rowstride, options) fire_paint_callbacks(callbacks, success) except KeyboardInterrupt: raise @@ -215,7 +215,7 @@ def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, call log.error("do_paint_rgb24 error", exc_info=True) fire_paint_callbacks(callbacks, False) - def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): + def _do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options): raise Exception("override me!") @@ -233,7 +233,7 @@ def do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, call the actual paint code is in _do_paint_rgb32 """ try: - success = (self._backing is not None) and self._do_paint_rgb32(img_data, x, y, width, height, rowstride, options, callbacks) + success = (self._backing is not None) and self._do_paint_rgb32(img_data, x, y, width, height, rowstride, options) fire_paint_callbacks(callbacks, success) except KeyboardInterrupt: raise @@ -241,7 +241,7 @@ def do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, call log.error("do_paint_rgb32 error", exc_info=True) fire_paint_callbacks(callbacks, False) - def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options, callbacks): + def _do_paint_rgb32(self, img_data, x, y, width, height, rowstride, options): raise Exception("override me!") diff --git a/src/xpra/gtk_common/gobject_compat.py b/src/xpra/gtk_common/gobject_compat.py index 2eb08cd752..9b50fba9fd 100644 --- a/src/xpra/gtk_common/gobject_compat.py +++ b/src/xpra/gtk_common/gobject_compat.py @@ -99,3 +99,11 @@ def import_pango3(): return Pango def import_pango(): return _try_import(import_pango3, import_pango2) + +def import_cairo(): + #we cannot use cairocffi with the do_paint callbacks.. + #import cairocffi #@UnresolvedImport + #cairocffi.install_as_pycairo() + #cairo = cairocffi + import cairo + return cairo diff --git a/src/xpra/gtk_common/gtk_spinner.py b/src/xpra/gtk_common/gtk_spinner.py index 6e7410cf38..40cbe03e69 100755 --- a/src/xpra/gtk_common/gtk_spinner.py +++ b/src/xpra/gtk_common/gtk_spinner.py @@ -7,10 +7,10 @@ # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. -from xpra.gtk_common.gobject_compat import import_gtk, import_gobject -gtk = import_gtk() +from xpra.gtk_common.gobject_compat import import_gtk, import_gobject, import_cairo +gtk = import_gtk() gobject = import_gobject() -import cairo +cairo = import_cairo() import math