diff --git a/src/shell-screenshot.c b/src/shell-screenshot.c index 99083ffb2..09f73edae 100644 --- a/src/shell-screenshot.c +++ b/src/shell-screenshot.c @@ -104,6 +104,182 @@ on_screenshot_written (GObject *source, g_clear_pointer (&priv->datetime, g_date_time_unref); } +static cairo_format_t +util_cairo_format_for_content (cairo_content_t content) +{ + switch (content) + { + case CAIRO_CONTENT_COLOR: + return CAIRO_FORMAT_RGB24; + case CAIRO_CONTENT_ALPHA: + return CAIRO_FORMAT_A8; + case CAIRO_CONTENT_COLOR_ALPHA: + default: + return CAIRO_FORMAT_ARGB32; + } +} + +static cairo_surface_t * +util_cairo_surface_coerce_to_image (cairo_surface_t *surface, + cairo_content_t content, + int src_x, + int src_y, + int width, + int height) +{ + cairo_surface_t *copy; + cairo_t *cr; + + copy = cairo_image_surface_create (util_cairo_format_for_content (content), + width, + height); + + cr = cairo_create (copy); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface (cr, surface, -src_x, -src_y); + cairo_paint (cr); + cairo_destroy (cr); + + return copy; +} + +static void +convert_alpha (guchar *dest_data, + int dest_stride, + guchar *src_data, + int src_stride, + int src_x, + int src_y, + int width, + int height) +{ + int x, y; + + src_data += src_stride * src_y + src_x * 4; + + for (y = 0; y < height; y++) + { + uint32_t *src = (guint32 *) src_data; + + for (x = 0; x < width; x++) + { + unsigned int alpha = src[x] >> 24; + + if (alpha == 0) + { + dest_data[x * 4 + 0] = 0; + dest_data[x * 4 + 1] = 0; + dest_data[x * 4 + 2] = 0; + } + else + { + dest_data[x * 4 + 0] = (((src[x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; + dest_data[x * 4 + 1] = (((src[x] & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; + dest_data[x * 4 + 2] = (((src[x] & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; + } + dest_data[x * 4 + 3] = alpha; + } + + src_data += src_stride; + dest_data += dest_stride; + } +} + +static void +convert_no_alpha (guchar *dest_data, + int dest_stride, + guchar *src_data, + int src_stride, + int src_x, + int src_y, + int width, + int height) +{ + int x, y; + + src_data += src_stride * src_y + src_x * 4; + + for (y = 0; y < height; y++) + { + uint32_t *src = (uint32_t *) src_data; + + for (x = 0; x < width; x++) + { + dest_data[x * 3 + 0] = src[x] >> 16; + dest_data[x * 3 + 1] = src[x] >> 8; + dest_data[x * 3 + 2] = src[x]; + } + + src_data += src_stride; + dest_data += dest_stride; + } +} + +static GdkPixbuf * +util_pixbuf_from_surface (cairo_surface_t *surface, + gint src_x, + gint src_y, + gint width, + gint height) +{ + cairo_content_t content; + GdkPixbuf *dest; + + /* General sanity checks */ + g_return_val_if_fail (surface != NULL, NULL); + g_return_val_if_fail (width > 0 && height > 0, NULL); + + content = cairo_surface_get_content (surface) | CAIRO_CONTENT_COLOR; + dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + !!(content & CAIRO_CONTENT_ALPHA), + 8, + width, height); + + if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE && + cairo_image_surface_get_format (surface) == util_cairo_format_for_content (content)) + { + surface = cairo_surface_reference (surface); + } + else + { + surface = util_cairo_surface_coerce_to_image (surface, content, + src_x, src_y, + width, height); + src_x = 0; + src_y = 0; + } + cairo_surface_flush (surface); + if (cairo_surface_status (surface) || dest == NULL) + { + cairo_surface_destroy (surface); + g_clear_object (&dest); + return NULL; + } + + if (gdk_pixbuf_get_has_alpha (dest)) + { + convert_alpha (gdk_pixbuf_get_pixels (dest), + gdk_pixbuf_get_rowstride (dest), + cairo_image_surface_get_data (surface), + cairo_image_surface_get_stride (surface), + src_x, src_y, + width, height); + } + else + { + convert_no_alpha (gdk_pixbuf_get_pixels (dest), + gdk_pixbuf_get_rowstride (dest), + cairo_image_surface_get_data (surface), + cairo_image_surface_get_stride (surface), + src_x, src_y, + width, height); + } + + cairo_surface_destroy (surface); + + return dest; +} + static void write_screenshot_thread (GTask *result, gpointer object, @@ -123,10 +299,10 @@ write_screenshot_thread (GTask *result, stream = g_object_ref (priv->stream); - pixbuf = gdk_pixbuf_get_from_surface (priv->image, - 0, 0, - cairo_image_surface_get_width (priv->image), - cairo_image_surface_get_height (priv->image)); + pixbuf = util_pixbuf_from_surface (priv->image, + 0, 0, + cairo_image_surface_get_width (priv->image), + cairo_image_surface_get_height (priv->image)); creation_time = g_date_time_format (priv->datetime, "%c"); if (!creation_time) @@ -1167,11 +1343,11 @@ shell_screenshot_composite_to_stream (CoglTexture *texture, cairo_surface_destroy (cursor_surface); } - // Save to an image. - pixbuf = gdk_pixbuf_get_from_surface (surface, - 0, 0, - cairo_image_surface_get_width (surface), - cairo_image_surface_get_height (surface)); + /* Save to an image. */ + pixbuf = util_pixbuf_from_surface (surface, + 0, 0, + cairo_image_surface_get_width (surface), + cairo_image_surface_get_height (surface)); cairo_surface_destroy (surface); date_time = g_date_time_new_now_local ();