diff --git a/src/common/common/surface.h b/src/common/common/surface.h index f92c0685..964f8d3c 100644 --- a/src/common/common/surface.h +++ b/src/common/common/surface.h @@ -336,20 +336,43 @@ void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int guac_transfer_function op, guac_common_surface* dst, int dx, int dy); /** - * Draws a solid color rectangle at the given coordinates on the given surface. + * Assigns the given value to all pixels within a rectangle of the given + * surface. The color of all pixels within the rectangle, including the alpha + * component, is entirely replaced. * - * @param surface The surface to draw upon. - * @param x The X coordinate of the upper-left corner of the rectangle. - * @param y The Y coordinate of the upper-left corner of the rectangle. - * @param w The width of the rectangle. - * @param h The height of the rectangle. - * @param red The red component of the color of the rectangle. - * @param green The green component of the color of the rectangle. - * @param blue The blue component of the color of the rectangle. + * @param surface + * The surface to draw upon. + * + * @param x + * The X coordinate of the upper-left corner of the rectangle. + * + * @param y + * The Y coordinate of the upper-left corner of the rectangle. + * + * @param w + * The width of the rectangle. + * + * @param h + * The height of the rectangle. + * + * @param red + * The red component of the color value to assign to all pixels within the + * rectangle. + * + * @param green + * The green component of the color value to assign to all pixels within + * the rectangle. + * + * @param blue + * The blue component of the color value to assign to all pixels within the + * rectangle. + * + * @param alpha + * The alpha component of the color value to assign to all pixels within + * the rectangle. */ -void guac_common_surface_rect(guac_common_surface* surface, - int x, int y, int w, int h, - int red, int green, int blue); +void guac_common_surface_set(guac_common_surface* surface, int x, int y, + int w, int h, int red, int green, int blue, int alpha); /** * Given the coordinates and dimensions of a rectangle, clips all future diff --git a/src/common/surface.c b/src/common/surface.c index a4fd4c62..9aa60cad 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -224,6 +224,54 @@ static void __guac_common_clip_rect(guac_common_surface* surface, } +/** + * Returns whether a rectangle within the given surface contains only fully + * opaque pixels. + * + * @param surface + * The surface to check. + * + * @param rect + * The rectangle to check. + * + * @return + * Non-zero if the rectangle contains only fully opaque pixels, zero + * otherwise. + */ +static int __guac_common_surface_is_opaque(guac_common_surface* surface, + guac_common_rect* rect) { + + int x, y; + + int stride = surface ->stride; + unsigned char* buffer = + surface->buffer + (stride * rect->y) + (4 * rect->x); + + /* For each row */ + for (y = 0; y < rect->height; y++) { + + /* Search for a non-opaque pixel */ + uint32_t* current = (uint32_t*) buffer; + for (x=0; x < rect->width; x++) { + + /* Rectangle is non-opaque if a single non-opaque pixel is found */ + uint32_t color = *(current++); + if ((color & 0xFF000000) != 0xFF000000) + return 0; + + } + + /* Next row */ + buffer += stride; + + } + + /* Rectangle is opaque */ + return 1; + +} + + /** * Returns whether the given rectangle should be combined into the existing * dirty rectangle, to be eventually flushed as a "png" instruction. @@ -730,24 +778,41 @@ static int __guac_common_surface_transfer_int(guac_transfer_function op, uint32_ } /** - * Draws a rectangle of solid color within the backing surface of the - * given destination surface. + * Assigns the given value to all pixels within a rectangle of the backing + * surface of the given destination surface. The color of all pixels within the + * rectangle, including the alpha component, is entirely replaced. * - * @param dst The destination surface. - * @param rect The rectangle to draw. - * @param red The red component of the color of the rectangle. - * @param green The green component of the color of the rectangle. - * @param blue The blue component of the color of the rectangle. + * @param dst + * The destination surface. + * + * @param rect + * The rectangle to draw. + * + * @param red + * The red component of the color value to assign to all pixels within the + * rectangle. + * + * @param green + * The green component of the color value to assign to all pixels within + * the rectangle. + * + * @param blue + * The blue component of the color value to assign to all pixels within the + * rectangle. + * + * @param alpha + * The alpha component of the color value to assign to all pixels within + * the rectangle. */ -static void __guac_common_surface_rect(guac_common_surface* dst, guac_common_rect* rect, - int red, int green, int blue) { +static void __guac_common_surface_set(guac_common_surface* dst, + guac_common_rect* rect, int red, int green, int blue, int alpha) { int x, y; int dst_stride; unsigned char* dst_buffer; - uint32_t color = 0xFF000000 | (red << 16) | (green << 8) | blue; + uint32_t color = (alpha << 24) | (red << 16) | (green << 8) | blue; int min_x = rect->width - 1; int min_y = rect->height - 1; @@ -1063,7 +1128,7 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client, pthread_mutex_init(&surface->_lock, NULL); /* Create corresponding Cairo surface */ - surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); + surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); surface->buffer = calloc(h, surface->stride); /* Create corresponding heat map */ @@ -1126,7 +1191,7 @@ void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { /* Re-initialize at new size */ surface->width = w; surface->height = h; - surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); + surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); surface->buffer = calloc(h, surface->stride); __guac_common_bound_rect(surface, &surface->clip_rect, NULL, NULL); @@ -1368,8 +1433,8 @@ complete: } -void guac_common_surface_rect(guac_common_surface* surface, - int x, int y, int w, int h, int red, int green, int blue) { +void guac_common_surface_set(guac_common_surface* surface, + int x, int y, int w, int h, int red, int green, int blue, int alpha) { pthread_mutex_lock(&surface->_lock); @@ -1385,19 +1450,31 @@ void guac_common_surface_rect(guac_common_surface* surface, goto complete; /* Update backing surface */ - __guac_common_surface_rect(surface, &rect, red, green, blue); + __guac_common_surface_set(surface, &rect, red, green, blue, alpha); if (rect.width <= 0 || rect.height <= 0) goto complete; + /* Handle as normal draw if non-opaque */ + if (alpha != 0xFF) { + + /* Flush if not combining */ + if (!__guac_common_should_combine(surface, &rect, 0)) + __guac_common_surface_flush_deferred(surface); + + /* Always defer draws */ + __guac_common_mark_dirty(surface, &rect); + + } + /* Defer if combining */ - if (__guac_common_should_combine(surface, &rect, 1)) + else if (__guac_common_should_combine(surface, &rect, 1)) __guac_common_mark_dirty(surface, &rect); /* Otherwise, flush and draw immediately */ else { __guac_common_surface_flush(surface); guac_protocol_send_rect(socket, layer, rect.x, rect.y, rect.width, rect.height); - guac_protocol_send_cfill(socket, GUAC_COMP_OVER, layer, red, green, blue, 0xFF); + guac_protocol_send_cfill(socket, GUAC_COMP_OVER, layer, red, green, blue, alpha); surface->realized = 1; } @@ -1439,8 +1516,12 @@ void guac_common_surface_reset_clip(guac_common_surface* surface) { * * @param surface * The surface to flush. + * + * @param opaque + * Whether the rectangle being flushed contains only fully-opaque pixels. */ -static void __guac_common_surface_flush_to_png(guac_common_surface* surface) { +static void __guac_common_surface_flush_to_png(guac_common_surface* surface, + int opaque) { if (surface->dirty) { @@ -1452,9 +1533,29 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface) { + surface->dirty_rect.y * surface->stride + surface->dirty_rect.x * 4; - cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, - CAIRO_FORMAT_RGB24, surface->dirty_rect.width, - surface->dirty_rect.height, surface->stride); + cairo_surface_t* rect; + + /* Use RGB24 if the image is fully opaque */ + if (opaque) + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); + + /* Otherwise ARGB32 is needed */ + else { + + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_ARGB32, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); + + /* Clear destination rect first */ + guac_protocol_send_rect(socket, layer, + surface->dirty_rect.x, surface->dirty_rect.y, + surface->dirty_rect.width, surface->dirty_rect.height); + guac_protocol_send_cfill(socket, GUAC_COMP_ROUT, layer, + 0x00, 0x00, 0x00, 0xFF); + + } /* Send PNG for rect */ guac_client_stream_png(surface->client, socket, GUAC_COMP_OVER, @@ -1526,8 +1627,12 @@ static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) { * * @param surface * The surface to flush. + * + * @param opaque + * Whether the rectangle being flushed contains only fully-opaque pixels. */ -static void __guac_common_surface_flush_to_webp(guac_common_surface* surface) { +static void __guac_common_surface_flush_to_webp(guac_common_surface* surface, + int opaque) { if (surface->dirty) { @@ -1547,9 +1652,19 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface) { + surface->dirty_rect.y * surface->stride + surface->dirty_rect.x * 4; - cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer, - CAIRO_FORMAT_RGB24, surface->dirty_rect.width, - surface->dirty_rect.height, surface->stride); + cairo_surface_t* rect; + + /* Use RGB24 if the image is fully opaque */ + if (opaque) + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_RGB24, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); + + /* Otherwise ARGB32 is needed */ + else + rect = cairo_image_surface_create_for_data(buffer, + CAIRO_FORMAT_ARGB32, surface->dirty_rect.width, + surface->dirty_rect.height, surface->stride); /* Send WebP for rect */ guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer, @@ -1679,19 +1794,22 @@ static void __guac_common_surface_flush(guac_common_surface* surface) { flushed++; + int opaque = __guac_common_surface_is_opaque(surface, + &surface->dirty_rect); + /* Prefer WebP when reasonable */ if (__guac_common_surface_should_use_webp(surface, &surface->dirty_rect)) - __guac_common_surface_flush_to_webp(surface); + __guac_common_surface_flush_to_webp(surface, opaque); /* If not WebP, JPEG is the next best (lossy) choice */ - else if (__guac_common_surface_should_use_jpeg(surface, - &surface->dirty_rect)) + else if (opaque && __guac_common_surface_should_use_jpeg( + surface, &surface->dirty_rect)) __guac_common_surface_flush_to_jpeg(surface); /* Use PNG if no lossy formats are appropriate */ else - __guac_common_surface_flush_to_png(surface); + __guac_common_surface_flush_to_png(surface, opaque); } @@ -1750,7 +1868,7 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, /* Get entire surface */ cairo_surface_t* rect = cairo_image_surface_create_for_data( - surface->buffer, CAIRO_FORMAT_RGB24, + surface->buffer, CAIRO_FORMAT_ARGB32, surface->width, surface->height, surface->stride); /* Send PNG for rect */ diff --git a/src/protocols/rdp/rdp_gdi.c b/src/protocols/rdp/rdp_gdi.c index 1805319e..59fe64cd 100644 --- a/src/protocols/rdp/rdp_gdi.c +++ b/src/protocols/rdp/rdp_gdi.c @@ -113,7 +113,8 @@ void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) { case 0: /* Send black rectangle */ - guac_common_surface_rect(current_surface, x, y, w, h, 0, 0, 0); + guac_common_surface_set(current_surface, x, y, w, h, + 0x00, 0x00, 0x00, 0xFF); break; /* DSTINVERT */ @@ -128,7 +129,8 @@ void guac_rdp_gdi_dstblt(rdpContext* context, DSTBLT_ORDER* dstblt) { /* Whiteness */ case 0xFF: - guac_common_surface_rect(current_surface, x, y, w, h, 0xFF, 0xFF, 0xFF); + guac_common_surface_set(current_surface, x, y, w, h, + 0xFF, 0xFF, 0xFF, 0xFF); break; /* Unsupported ROP3 */ @@ -175,7 +177,8 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) { /* If blackness, send black rectangle */ case 0x00: - guac_common_surface_rect(current_surface, x, y, w, h, 0, 0, 0); + guac_common_surface_set(current_surface, x, y, w, h, + 0x00, 0x00, 0x00, 0xFF); break; /* If NOP, do nothing */ @@ -185,15 +188,17 @@ void guac_rdp_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) { /* If operation is just a copy, send foreground only */ case 0xCC: case 0xF0: - guac_common_surface_rect(current_surface, x, y, w, h, + guac_common_surface_set(current_surface, x, y, w, h, (patblt->foreColor >> 16) & 0xFF, (patblt->foreColor >> 8 ) & 0xFF, - (patblt->foreColor ) & 0xFF); + (patblt->foreColor ) & 0xFF, + 0xFF); break; /* If whiteness, send white rectangle */ case 0xFF: - guac_common_surface_rect(current_surface, x, y, w, h, 0xFF, 0xFF, 0xFF); + guac_common_surface_set(current_surface, x, y, w, h, + 0xFF, 0xFF, 0xFF, 0xFF); break; /* Otherwise, invert entire rect */ @@ -250,7 +255,8 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { /* If blackness, send black rectangle */ case 0x00: - guac_common_surface_rect(current_surface, x, y, w, h, 0, 0, 0); + guac_common_surface_set(current_surface, x, y, w, h, + 0x00, 0x00, 0x00, 0xFF); break; /* If NOP, do nothing */ @@ -294,7 +300,8 @@ void guac_rdp_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) { /* If whiteness, send white rectangle */ case 0xFF: - guac_common_surface_rect(current_surface, x, y, w, h, 0xFF, 0xFF, 0xFF); + guac_common_surface_set(current_surface, x, y, w, h, + 0xFF, 0xFF, 0xFF, 0xFF); break; /* Otherwise, use transfer */ @@ -330,10 +337,11 @@ void guac_rdp_gdi_opaquerect(rdpContext* context, OPAQUE_RECT_ORDER* opaque_rect int w = opaque_rect->nWidth; int h = opaque_rect->nHeight; - guac_common_surface_rect(current_surface, x, y, w, h, + guac_common_surface_set(current_surface, x, y, w, h, (color >> 16) & 0xFF, (color >> 8 ) & 0xFF, - (color ) & 0xFF); + (color ) & 0xFF, + 0xFF); } diff --git a/src/protocols/rdp/rdp_glyph.c b/src/protocols/rdp/rdp_glyph.c index 68ce7a26..8ba1087b 100644 --- a/src/protocols/rdp/rdp_glyph.c +++ b/src/protocols/rdp/rdp_glyph.c @@ -136,10 +136,12 @@ void guac_rdp_glyph_begindraw(rdpContext* context, /* Convert background color */ bgcolor = guac_rdp_convert_color(context, bgcolor); - guac_common_surface_rect(rdp_client->current_surface, x, y, width, height, - (bgcolor & 0xFF0000) >> 16, - (bgcolor & 0x00FF00) >> 8, - bgcolor & 0x0000FF); + guac_common_surface_set(rdp_client->current_surface, + x, y, width, height, + (bgcolor & 0xFF0000) >> 16, + (bgcolor & 0x00FF00) >> 8, + (bgcolor & 0x0000FF), + 0xFF); } diff --git a/src/terminal/display.c b/src/terminal/display.c index da1a8d98..295bdb57 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -773,13 +773,14 @@ void __guac_terminal_display_flush_clear(guac_terminal_display* display) { } /* Send rect */ - guac_common_surface_rect( + guac_common_surface_set( display->display_surface, col * display->char_width, row * display->char_height, rect_width * display->char_width, rect_height * display->char_height, - guac_color->red, guac_color->green, guac_color->blue); + guac_color->red, guac_color->green, guac_color->blue, + 0xFF); } /* end if clear operation */