Skip to content

Commit c04888a

Browse files
committed
GUACAMOLE-377: Ensure all layers supporting transparency are properly cleared before receiving data that also supports transparency (including WebP).
1 parent a58884c commit c04888a

File tree

1 file changed

+63
-127
lines changed

1 file changed

+63
-127
lines changed

src/libguac/display-worker.c

+63-127
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,28 @@
3535
#include <pthread.h>
3636

3737
/**
38-
* Sends the contents of the given dirty rectangle from the given layer using
39-
* lossless PNG compression. The resulting instructions will be sent over the
40-
* client-wide broadcast socket associated with the given layer. The graphical
41-
* contents sent will be pulled from the layer's last_frame buffer. If sending
42-
* the contents of a pending frame, that pending frame must have been copied
43-
* over to the last_frame buffer before calling this function.
38+
* Returns a new Cairo surface representing the contents of the given dirty
39+
* rectangle from the given layer. The returned surface must eventually be
40+
* freed with a call to cairo_surface_destroy(). The graphical contents will be
41+
* referenced from the layer's last_frame buffer. If sending the contents of a
42+
* pending frame, that pending frame must have been copied over to the
43+
* last_frame buffer before calling this function.
4444
*
4545
* @param display_layer
46-
* The layer whose data should be sent to connected users.
46+
* The layer whose data should be referenced by the returned Cairo surface.
4747
*
4848
* @param dirty
49-
* The region of the layer that should be sent.
49+
* The region of the layer that should be referenced by the returned Cairo
50+
* surface.
51+
*
52+
* @return
53+
* A new Cairo surface that points to the given rectangle of image data
54+
* from the last_frame buffer of the given layer. This surface must
55+
* eventually be freed with a call to cairo_surface_destroy().
5056
*/
51-
static void LFR_guac_display_layer_flush_to_png(guac_display_layer* display_layer,
57+
static cairo_surface_t* LFR_guac_display_layer_cairo_rect(guac_display_layer* display_layer,
5258
guac_rect* dirty) {
5359

54-
guac_display* display = display_layer->display;
55-
guac_client* client = display->client;
56-
guac_socket* socket = client->socket;
57-
const guac_layer* layer = display_layer->layer;
58-
5960
/* Get Cairo surface covering dirty rect */
6061
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
6162
cairo_surface_t* rect;
@@ -67,12 +68,39 @@ static void LFR_guac_display_layer_flush_to_png(guac_display_layer* display_laye
6768
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
6869

6970
/* Otherwise ARGB32 is needed, and the destination must be cleared */
70-
else {
71-
71+
else
7272
rect = cairo_image_surface_create_for_data(buffer,
7373
CAIRO_FORMAT_ARGB32, guac_rect_width(dirty),
7474
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
7575

76+
return rect;
77+
78+
}
79+
80+
/**
81+
* Sends instructions over the Guacamole connection to clear the given
82+
* rectangle of the given layer if that layer is non-opaque. This is necessary
83+
* prior to sending image data to layers with alpha transparency, as image data
84+
* from multiple updates will otherwise be composited together.
85+
*
86+
* @param display_layer
87+
* The layer that should possibly be cleared in preparation for a future
88+
* drawing operation.
89+
*
90+
* @param dirty
91+
* The rectangular region of the drawing operation.
92+
*/
93+
static void guac_display_layer_clear_non_opaque(guac_display_layer* display_layer,
94+
guac_rect* dirty) {
95+
96+
guac_display* display = display_layer->display;
97+
const guac_layer* layer = display_layer->layer;
98+
99+
guac_client* client = display->client;
100+
guac_socket* socket = client->socket;
101+
102+
if (!display_layer->opaque) {
103+
76104
/* Clear destination rect first */
77105
pthread_mutex_lock(&display->op_path_lock);
78106
guac_protocol_send_rect(socket, layer,
@@ -84,12 +112,6 @@ static void LFR_guac_display_layer_flush_to_png(guac_display_layer* display_laye
84112

85113
}
86114

87-
/* Send PNG for rect */
88-
guac_client_stream_png(client, socket, GUAC_COMP_OVER,
89-
layer, dirty->left, dirty->top, rect);
90-
91-
cairo_surface_destroy(rect);
92-
93115
}
94116

95117
/**
@@ -122,108 +144,6 @@ static int guac_display_suggest_quality(guac_client* client) {
122144

123145
}
124146

125-
/**
126-
* Sends the contents of the given dirty rectangle from the given layer using
127-
* lossy JPEG compression. The resulting instructions will be sent over the
128-
* client-wide broadcast socket associated with the given layer. The graphical
129-
* contents sent will be pulled from the layer's last_frame buffer. If sending
130-
* the contents of a pending frame, that pending frame must have been copied
131-
* over to the last_frame buffer before calling this function.
132-
*
133-
* @param layer
134-
* The layer whose data should be sent to connected users.
135-
*
136-
* @param dirty
137-
* The region of the layer that should be sent.
138-
*/
139-
static void LFR_guac_display_layer_flush_to_jpeg(guac_display_layer* display_layer,
140-
guac_rect* dirty) {
141-
142-
guac_display* display = display_layer->display;
143-
guac_client* client = display->client;
144-
guac_socket* socket = client->socket;
145-
const guac_layer* layer = display_layer->layer;
146-
147-
guac_rect max = {
148-
.left = 0,
149-
.top = 0,
150-
.right = display_layer->last_frame.width,
151-
.bottom = display_layer->last_frame.height
152-
};
153-
154-
/* Expand the dirty rect size to fit in a grid with cells equal to the
155-
* minimum JPEG block size */
156-
guac_rect_align(dirty, GUAC_SURFACE_JPEG_BLOCK_SIZE);
157-
guac_rect_constrain(dirty, &max);
158-
159-
/* Get Cairo surface covering dirty rect */
160-
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
161-
cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer,
162-
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
163-
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
164-
165-
/* Send JPEG for rect */
166-
guac_client_stream_jpeg(client, socket, GUAC_COMP_OVER, layer,
167-
dirty->left, dirty->top, rect,
168-
guac_display_suggest_quality(client));
169-
170-
cairo_surface_destroy(rect);
171-
172-
}
173-
174-
/**
175-
* Sends the contents of the given dirty rectangle from the given layer using
176-
* WebP compression. Whether that WebP compression is lossless depends on the
177-
* lossless setting of the layer's last frame. The resulting instructions will
178-
* be sent over the client-wide broadcast socket associated with the given
179-
* layer. The graphical contents sent will be pulled from the layer's
180-
* last_frame buffer. If sending the contents of a pending frame, that pending
181-
* frame must have been copied over to the last_frame buffer before calling
182-
* this function.
183-
*
184-
* @param layer
185-
* The layer whose data should be sent to connected users.
186-
*
187-
* @param dirty
188-
* The region of the layer that should be sent.
189-
*/
190-
static void LFR_guac_display_layer_flush_to_webp(guac_display_layer* display_layer,
191-
guac_rect* dirty) {
192-
193-
guac_display* display = display_layer->display;
194-
guac_client* client = display->client;
195-
guac_socket* socket = client->socket;
196-
const guac_layer* layer = display_layer->layer;
197-
198-
guac_rect max = {
199-
.left = 0,
200-
.top = 0,
201-
.right = display_layer->last_frame.width,
202-
.bottom = display_layer->last_frame.height
203-
};
204-
205-
/* Expand the dirty rect size to fit in a grid with cells equal to the
206-
* minimum WebP block size */
207-
guac_rect_align(dirty, GUAC_SURFACE_WEBP_BLOCK_SIZE);
208-
guac_rect_constrain(dirty, &max);
209-
210-
/* Get Cairo surface covering dirty rect */
211-
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
212-
cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer,
213-
display_layer->opaque ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32,
214-
guac_rect_width(dirty), guac_rect_height(dirty),
215-
display_layer->last_frame.buffer_stride);
216-
217-
/* Send WebP for rect */
218-
guac_client_stream_webp(client, socket, GUAC_COMP_OVER, layer,
219-
dirty->left, dirty->top, rect,
220-
guac_display_suggest_quality(client),
221-
display_layer->last_frame.lossless ? 1 : 0);
222-
223-
cairo_surface_destroy(rect);
224-
225-
}
226-
227147
/**
228148
* Guesses whether a rectangle within a particular layer would be better
229149
* compressed as PNG or using a lossy format like JPEG. Positive values
@@ -372,6 +292,7 @@ void* guac_display_worker_thread(void* data) {
372292

373293
guac_display* display = (guac_display*) data;
374294
guac_client* client = display->client;
295+
guac_socket* socket = client->socket;
375296

376297
guac_display_plan_operation op;
377298
while (guac_fifo_dequeue_and_lock(&display->ops, &op)) {
@@ -416,18 +337,33 @@ void* guac_display_worker_thread(void* data) {
416337
* the size of the original image. Compositing via Guacamole
417338
* protocol instructions can reassemble those stages. */
418339

340+
cairo_surface_t* rect = LFR_guac_display_layer_cairo_rect(display_layer, dirty);
341+
const guac_layer* layer = display_layer->layer;
342+
343+
/* Clear relevant rect of destination layer if necessary to
344+
* ensure fresh data is not drawn on top of old data for layers
345+
* with alpha transparency */
346+
guac_display_layer_clear_non_opaque(display_layer, dirty);
347+
419348
/* Prefer WebP when reasonable */
420349
if (LFR_guac_display_layer_should_use_webp(display_layer, dirty, framerate))
421-
LFR_guac_display_layer_flush_to_webp(display_layer, dirty);
350+
guac_client_stream_webp(client, socket, GUAC_COMP_OVER, layer,
351+
dirty->left, dirty->top, rect,
352+
guac_display_suggest_quality(client),
353+
display_layer->last_frame.lossless ? 1 : 0);
422354

423355
/* If not WebP, JPEG is the next best (lossy) choice */
424356
else if (display_layer->opaque && LFR_guac_display_layer_should_use_jpeg(display_layer, dirty, framerate))
425-
LFR_guac_display_layer_flush_to_jpeg(display_layer, dirty);
357+
guac_client_stream_jpeg(client, socket, GUAC_COMP_OVER, layer,
358+
dirty->left, dirty->top, rect,
359+
guac_display_suggest_quality(client));
426360

427361
/* Use PNG if no lossy formats are appropriate */
428362
else
429-
LFR_guac_display_layer_flush_to_png(display_layer, dirty);
363+
guac_client_stream_png(client, socket, GUAC_COMP_OVER,
364+
layer, dirty->left, dirty->top, rect);
430365

366+
cairo_surface_destroy(rect);
431367
break;
432368

433369
case GUAC_DISPLAY_PLAN_OPERATION_COPY:

0 commit comments

Comments
 (0)