| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "config.h" |
| #include "common/rect.h" |
| #include "common/surface.h" |
| |
| #include <cairo/cairo.h> |
| #include <guacamole/client.h> |
| #include <guacamole/layer.h> |
| #include <guacamole/protocol.h> |
| #include <guacamole/socket.h> |
| #include <guacamole/timestamp.h> |
| #include <guacamole/user.h> |
| |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| /** |
| * The width of an update which should be considered negible and thus |
| * trivial overhead compared ot the cost of two updates. |
| */ |
| #define GUAC_SURFACE_NEGLIGIBLE_WIDTH 64 |
| |
| /** |
| * The height of an update which should be considered negible and thus |
| * trivial overhead compared ot the cost of two updates. |
| */ |
| #define GUAC_SURFACE_NEGLIGIBLE_HEIGHT 64 |
| |
| /** |
| * The proportional increase in cost contributed by transfer and processing of |
| * image data, compared to processing an equivalent amount of client-side |
| * data. |
| */ |
| #define GUAC_SURFACE_DATA_FACTOR 16 |
| |
| /** |
| * The base cost of every update. Each update should be considered to have |
| * this starting cost, plus any additional cost estimated from its |
| * content. |
| */ |
| #define GUAC_SURFACE_BASE_COST 4096 |
| |
| /** |
| * An increase in cost is negligible if it is less than |
| * 1/GUAC_SURFACE_NEGLIGIBLE_INCREASE of the old cost. |
| */ |
| #define GUAC_SURFACE_NEGLIGIBLE_INCREASE 4 |
| |
| /** |
| * If combining an update because it appears to be follow a fill pattern, |
| * the combined cost must not exceed |
| * GUAC_SURFACE_FILL_PATTERN_FACTOR * (total uncombined cost). |
| */ |
| #define GUAC_SURFACE_FILL_PATTERN_FACTOR 3 |
| |
| /* Define cairo_format_stride_for_width() if missing */ |
| #ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH |
| #define cairo_format_stride_for_width(format, width) (width*4) |
| #endif |
| |
| /** |
| * The framerate which, if exceeded, indicates that JPEG is preferred. |
| */ |
| #define GUAC_COMMON_SURFACE_JPEG_FRAMERATE 3 |
| |
| /** |
| * Minimum JPEG bitmap size (area). If the bitmap is smaller than this threshold, |
| * it should be compressed as a PNG image to avoid the JPEG compression tax. |
| */ |
| #define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096 |
| |
| /** |
| * The JPEG compression min block size. This defines the optimal rectangle block |
| * size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to |
| * reduce the occurrence of ringing artifacts further. |
| */ |
| #define GUAC_SURFACE_JPEG_BLOCK_SIZE 16 |
| |
| /** |
| * The WebP compression min block size. This defines the optimal rectangle block |
| * size factor for WebP compression. WebP does utilize variable block size, but |
| * ensuring a block size factor reduces any noise on the image edges. |
| */ |
| #define GUAC_SURFACE_WEBP_BLOCK_SIZE 8 |
| |
| void guac_common_surface_move(guac_common_surface* surface, int x, int y) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| surface->x = x; |
| surface->y = y; |
| surface->location_dirty = 1; |
| |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_stack(guac_common_surface* surface, int z) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| surface->z = z; |
| surface->location_dirty = 1; |
| |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_set_parent(guac_common_surface* surface, |
| const guac_layer* parent) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| surface->parent = parent; |
| surface->location_dirty = 1; |
| |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_set_opacity(guac_common_surface* surface, |
| int opacity) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| surface->opacity = opacity; |
| surface->opacity_dirty = 1; |
| |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| /** |
| * Updates the coordinates of the given rectangle to be within the bounds of |
| * the given surface. |
| * |
| * @param surface The surface to use for clipping. |
| * @param rect The rectangle to clip. |
| * @param sx The X coordinate of the source rectangle, if any. |
| * @param sy The Y coordinate of the source rectangle, if any. |
| */ |
| static void __guac_common_bound_rect(guac_common_surface* surface, |
| guac_common_rect* rect, int* sx, int* sy) { |
| |
| guac_common_rect bounds_rect = { |
| .x = 0, |
| .y = 0, |
| .width = surface->width, |
| .height = surface->height |
| }; |
| |
| int orig_x = rect->x; |
| int orig_y = rect->y; |
| |
| guac_common_rect_constrain(rect, &bounds_rect); |
| |
| /* Update source X/Y if given */ |
| if (sx != NULL) *sx += rect->x - orig_x; |
| if (sy != NULL) *sy += rect->y - orig_y; |
| |
| } |
| |
| /** |
| * Updates the coordinates of the given rectangle to be within the clipping |
| * rectangle of the given surface, which must always be within the bounding |
| * rectangle of the given surface. |
| * |
| * @param surface The surface to use for clipping. |
| * @param rect The rectangle to clip. |
| * @param sx The X coordinate of the source rectangle, if any. |
| * @param sy The Y coordinate of the source rectangle, if any. |
| */ |
| static void __guac_common_clip_rect(guac_common_surface* surface, |
| guac_common_rect* rect, int* sx, int* sy) { |
| |
| int orig_x = rect->x; |
| int orig_y = rect->y; |
| |
| /* Just bound within surface if no clipping rectangle applied */ |
| if (!surface->clipped) { |
| __guac_common_bound_rect(surface, rect, sx, sy); |
| return; |
| } |
| |
| guac_common_rect_constrain(rect, &surface->clip_rect); |
| |
| /* Update source X/Y if given */ |
| if (sx != NULL) *sx += rect->x - orig_x; |
| if (sy != NULL) *sy += rect->y - orig_y; |
| |
| } |
| |
| /** |
| * 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 image data, or would be best |
| * kept independent of the current rectangle. |
| * |
| * @param surface |
| * The surface being updated. |
| * |
| * @param rect |
| * The bounding rectangle of the update being made to the surface. |
| * |
| * @param rect_only |
| * Non-zero if this update, by its nature, contains only metainformation |
| * about the update's bounding rectangle, zero if the update also contains |
| * image data. |
| * |
| * @return |
| * Non-zero if the update should be combined with any existing update, zero |
| * otherwise. |
| */ |
| static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) { |
| |
| /* Always favor combining updates if surface is currently a purely |
| * server-side scratch area */ |
| if (!surface->realized) |
| return 1; |
| |
| if (surface->dirty) { |
| |
| int combined_cost, dirty_cost, update_cost; |
| |
| /* Simulate combination */ |
| guac_common_rect combined = surface->dirty_rect; |
| guac_common_rect_extend(&combined, rect); |
| |
| /* Combine if result is still small */ |
| if (combined.width <= GUAC_SURFACE_NEGLIGIBLE_WIDTH && combined.height <= GUAC_SURFACE_NEGLIGIBLE_HEIGHT) |
| return 1; |
| |
| /* Estimate costs of the existing update, new update, and both combined */ |
| combined_cost = GUAC_SURFACE_BASE_COST + combined.width * combined.height; |
| dirty_cost = GUAC_SURFACE_BASE_COST + surface->dirty_rect.width * surface->dirty_rect.height; |
| update_cost = GUAC_SURFACE_BASE_COST + rect->width * rect->height; |
| |
| /* Reduce cost if no image data */ |
| if (rect_only) |
| update_cost /= GUAC_SURFACE_DATA_FACTOR; |
| |
| /* Combine if cost estimate shows benefit */ |
| if (combined_cost <= update_cost + dirty_cost) |
| return 1; |
| |
| /* Combine if increase in cost is negligible */ |
| if (combined_cost - dirty_cost <= dirty_cost / GUAC_SURFACE_NEGLIGIBLE_INCREASE) |
| return 1; |
| |
| if (combined_cost - update_cost <= update_cost / GUAC_SURFACE_NEGLIGIBLE_INCREASE) |
| return 1; |
| |
| /* Combine if we anticipate further updates, as this update follows a common fill pattern */ |
| if (rect->x == surface->dirty_rect.x && rect->y == surface->dirty_rect.y + surface->dirty_rect.height) { |
| if (combined_cost <= (dirty_cost + update_cost) * GUAC_SURFACE_FILL_PATTERN_FACTOR) |
| return 1; |
| } |
| |
| } |
| |
| /* Otherwise, do not combine */ |
| return 0; |
| |
| } |
| |
| /** |
| * Expands the dirty rect of the given surface to contain the rect described by the given |
| * coordinates. |
| * |
| * @param surface The surface to mark as dirty. |
| * @param rect The rectangle of the update which is dirtying the surface. |
| */ |
| static void __guac_common_mark_dirty(guac_common_surface* surface, const guac_common_rect* rect) { |
| |
| /* Ignore empty rects */ |
| if (rect->width <= 0 || rect->height <= 0) |
| return; |
| |
| /* If already dirty, update existing rect */ |
| if (surface->dirty) |
| guac_common_rect_extend(&surface->dirty_rect, rect); |
| |
| /* Otherwise init dirty rect */ |
| else { |
| surface->dirty_rect = *rect; |
| surface->dirty = 1; |
| } |
| |
| } |
| |
| /** |
| * Calculate the current average framerate for a given area on the surface. |
| * |
| * @param surface |
| * The surface on which the framerate will be calculated. |
| * |
| * @param rect |
| * The rect containing the area for which the average framerate will be |
| * calculated. |
| * |
| * @return |
| * The average framerate of the given area, in frames per second. |
| */ |
| static unsigned int __guac_common_surface_calculate_framerate( |
| guac_common_surface* surface, const guac_common_rect* rect) { |
| |
| int x, y; |
| |
| /* Calculate heat map dimensions */ |
| int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); |
| |
| /* Calculate minimum X/Y coordinates intersecting given rect */ |
| int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| int min_y = rect->y / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| |
| /* Calculate maximum X/Y coordinates intersecting given rect */ |
| int max_x = min_x + (rect->width - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| int max_y = min_y + (rect->height - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| |
| unsigned int sum_framerate = 0; |
| unsigned int count = 0; |
| |
| /* Get start of buffer at given coordinates */ |
| const guac_common_surface_heat_cell* heat_row = |
| surface->heat_map + min_y * heat_width + min_x; |
| |
| /* Iterate over all the heat map cells for the area |
| * and calculate the average framerate */ |
| for (y = min_y; y < max_y; y++) { |
| |
| /* Get current row of heat map */ |
| const guac_common_surface_heat_cell* heat_cell = heat_row; |
| |
| /* For each cell in subset of row */ |
| for (x = min_x; x < max_x; x++) { |
| |
| /* Calculate indicies for latest and oldest history entries */ |
| int oldest_entry = heat_cell->oldest_entry; |
| int latest_entry = oldest_entry - 1; |
| if (latest_entry < 0) |
| latest_entry = GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE - 1; |
| |
| /* Calculate elapsed time covering entire history for this cell */ |
| int elapsed_time = heat_cell->history[latest_entry] |
| - heat_cell->history[oldest_entry]; |
| |
| /* Calculate and add framerate */ |
| if (elapsed_time) |
| sum_framerate += GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE |
| * 1000 / elapsed_time; |
| |
| /* Next heat map cell */ |
| heat_cell++; |
| count++; |
| |
| } |
| |
| /* Next heat map row */ |
| heat_row += heat_width; |
| |
| } |
| |
| /* Calculate the average framerate over entire rect */ |
| if (count) |
| return sum_framerate / count; |
| |
| return 0; |
| |
| } |
| |
| /** |
| * Guesses whether a rectangle within a particular surface would be better |
| * compressed as PNG or using a lossy format like JPEG. Positive values |
| * indicate PNG is likely to be superior, while negative values indicate the |
| * opposite. |
| * |
| * @param surface |
| * The surface containing the image data to check. |
| * |
| * @param rect |
| * The rect to check within the given surface. |
| * |
| * @return |
| * Positive values if PNG compression is likely to perform better than |
| * lossy alternatives, or negative values if PNG is likely to perform |
| * worse. |
| */ |
| static int __guac_common_surface_png_optimality(guac_common_surface* surface, |
| const guac_common_rect* rect) { |
| |
| int x, y; |
| |
| int num_same = 0; |
| int num_different = 1; |
| |
| /* Get image/buffer metrics */ |
| int width = rect->width; |
| int height = rect->height; |
| int stride = surface->stride; |
| |
| /* Get buffer from surface */ |
| unsigned char* buffer = surface->buffer + rect->y * stride + rect->x * 4; |
| |
| /* Image must be at least 1x1 */ |
| if (width < 1 || height < 1) |
| return 0; |
| |
| /* For each row */ |
| for (y = 0; y < height; y++) { |
| |
| uint32_t* row = (uint32_t*) buffer; |
| uint32_t last_pixel = *(row++) | 0xFF000000; |
| |
| /* For each pixel in current row */ |
| for (x = 1; x < width; x++) { |
| |
| /* Get next pixel */ |
| uint32_t current_pixel = *(row++) | 0xFF000000; |
| |
| /* Update same/different counts according to pixel value */ |
| if (current_pixel == last_pixel) |
| num_same++; |
| else |
| num_different++; |
| |
| last_pixel = current_pixel; |
| |
| } |
| |
| /* Advance to next row */ |
| buffer += stride; |
| |
| } |
| |
| /* Return rough approximation of optimality for PNG compression */ |
| return 0x100 * num_same / num_different - 0x400; |
| |
| } |
| |
| /** |
| * Returns whether the given rectangle would be optimally encoded as JPEG |
| * rather than PNG. |
| * |
| * @param surface |
| * The surface to be queried. |
| * |
| * @param rect |
| * The rectangle to check. |
| * |
| * @return |
| * Non-zero if the rectangle would be optimally encoded as JPEG, zero |
| * otherwise. |
| */ |
| static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface, |
| const guac_common_rect* rect) { |
| |
| /* Calculate the average framerate for the given rect */ |
| int framerate = __guac_common_surface_calculate_framerate(surface, rect); |
| |
| int rect_size = rect->width * rect->height; |
| |
| /* JPEG is preferred if: |
| * - frame rate is high enough |
| * - image size is large enough |
| * - PNG is not more optimal based on image contents */ |
| return framerate >= GUAC_COMMON_SURFACE_JPEG_FRAMERATE |
| && rect_size > GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE |
| && __guac_common_surface_png_optimality(surface, rect) < 0; |
| |
| } |
| |
| /** |
| * Returns whether the given rectangle would be optimally encoded as WebP |
| * rather than PNG. |
| * |
| * @param surface |
| * The surface to be queried. |
| * |
| * @param rect |
| * The rectangle to check. |
| * |
| * @return |
| * Non-zero if the rectangle would be optimally encoded as WebP, zero |
| * otherwise. |
| */ |
| static int __guac_common_surface_should_use_webp(guac_common_surface* surface, |
| const guac_common_rect* rect) { |
| |
| /* Do not use WebP if not supported */ |
| if (!guac_client_supports_webp(surface->client)) |
| return 0; |
| |
| /* Calculate the average framerate for the given rect */ |
| int framerate = __guac_common_surface_calculate_framerate(surface, rect); |
| |
| /* WebP is preferred if: |
| * - frame rate is high enough |
| * - PNG is not more optimal based on image contents */ |
| return framerate >= GUAC_COMMON_SURFACE_JPEG_FRAMERATE |
| && __guac_common_surface_png_optimality(surface, rect) < 0; |
| |
| } |
| |
| /** |
| * Updates the heat map cells which intersect the given rectangle using the |
| * given timestamp. This timestamp, along with timestamps from past updates, |
| * is used to calculate the framerate of each heat cell. |
| * |
| * @param surface |
| * The surface containing the heat map cells to be updated. |
| * |
| * @param rect |
| * The rectangle containing the heat map cells to be updated. |
| * |
| * @param time |
| * The timestamp to use when updating the heat map cells which intersect |
| * the given rectangle. |
| */ |
| static void __guac_common_surface_touch_rect(guac_common_surface* surface, |
| guac_common_rect* rect, guac_timestamp time) { |
| |
| int x, y; |
| |
| /* Calculate heat map dimensions */ |
| int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(surface->width); |
| |
| /* Calculate minimum X/Y coordinates intersecting given rect */ |
| int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| int min_y = rect->y / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| |
| /* Calculate maximum X/Y coordinates intersecting given rect */ |
| int max_x = min_x + (rect->width - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| int max_y = min_y + (rect->height - 1) / GUAC_COMMON_SURFACE_HEAT_CELL_SIZE; |
| |
| /* Get start of buffer at given coordinates */ |
| guac_common_surface_heat_cell* heat_row = |
| surface->heat_map + min_y * heat_width + min_x; |
| |
| /* Update all heat map cells which intersect with rectangle */ |
| for (y = min_y; y <= max_y; y++) { |
| |
| /* Get current row of heat map */ |
| guac_common_surface_heat_cell* heat_cell = heat_row; |
| |
| /* For each cell in subset of row */ |
| for (x = min_x; x <= max_x; x++) { |
| |
| /* Replace oldest entry with new timestamp */ |
| heat_cell->history[heat_cell->oldest_entry] = time; |
| |
| /* Update to next oldest entry */ |
| heat_cell->oldest_entry++; |
| if (heat_cell->oldest_entry >= |
| GUAC_COMMON_SURFACE_HEAT_CELL_HISTORY_SIZE) |
| heat_cell->oldest_entry = 0; |
| |
| /* Advance to next heat map cell */ |
| heat_cell++; |
| |
| } |
| |
| /* Next heat map row */ |
| heat_row += heat_width; |
| |
| } |
| |
| } |
| |
| /** |
| * Flushes the bitmap update currently described by the dirty rectangle within the |
| * given surface to that surface's bitmap queue. There MUST be space within the |
| * queue. |
| * |
| * @param surface The surface to flush. |
| */ |
| static void __guac_common_surface_flush_to_queue(guac_common_surface* surface) { |
| |
| guac_common_surface_bitmap_rect* rect; |
| |
| /* Do not flush if not dirty */ |
| if (!surface->dirty) |
| return; |
| |
| /* Add new rect to queue */ |
| rect = &(surface->bitmap_queue[surface->bitmap_queue_length++]); |
| rect->rect = surface->dirty_rect; |
| rect->flushed = 0; |
| |
| /* Surface now flushed */ |
| surface->dirty = 0; |
| |
| } |
| |
| /** |
| * Flushes the given surface, drawing any pending operations on the remote |
| * display. Surface properties are not flushed. |
| * |
| * @param surface |
| * The surface to flush. |
| */ |
| static void __guac_common_surface_flush(guac_common_surface* surface); |
| |
| /** |
| * Schedules a deferred flush of the given surface. This will not immediately |
| * flush the surface to the client. Instead, the result of the flush is |
| * added to a queue which is reinspected and combined (if possible) with other |
| * deferred flushes during the call to guac_common_surface_flush(). |
| * |
| * @param surface The surface to flush. |
| */ |
| static void __guac_common_surface_flush_deferred(guac_common_surface* surface) { |
| |
| /* Do not flush if not dirty */ |
| if (!surface->dirty) |
| return; |
| |
| /* Flush if queue size has reached maximum (space is reserved for the final |
| * dirty rect, as __guac_common_surface_flush() MAY add an additional rect |
| * to the queue */ |
| if (surface->bitmap_queue_length == GUAC_COMMON_SURFACE_QUEUE_SIZE-1) |
| __guac_common_surface_flush(surface); |
| |
| /* Append dirty rect to queue */ |
| __guac_common_surface_flush_to_queue(surface); |
| |
| } |
| |
| /** |
| * Transfers a single uint32_t using the given transfer function. |
| * |
| * @param op The transfer function to use. |
| * @param src The source of the uint32_t value. |
| * @param dst THe destination which will hold the result of the transfer. |
| * @return Non-zero if the destination value was changed, zero otherwise. |
| */ |
| static int __guac_common_surface_transfer_int(guac_transfer_function op, uint32_t* src, uint32_t* dst) { |
| |
| uint32_t orig = *dst; |
| |
| switch (op) { |
| |
| case GUAC_TRANSFER_BINARY_BLACK: |
| *dst = 0xFF000000; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_WHITE: |
| *dst = 0xFFFFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_SRC: |
| *dst = *src; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_DEST: |
| /* NOP */ |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NSRC: |
| *dst = *src ^ 0x00FFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NDEST: |
| *dst = *dst ^ 0x00FFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_AND: |
| *dst = ((*dst) & (0xFF000000 | *src)); |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NAND: |
| *dst = ((*dst) & (0xFF000000 | *src)) ^ 0x00FFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_OR: |
| *dst = ((*dst) | (0x00FFFFFF & *src)); |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NOR: |
| *dst = ((*dst) | (0x00FFFFFF & *src)) ^ 0x00FFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_XOR: |
| *dst = ((*dst) ^ (0x00FFFFFF & *src)); |
| break; |
| |
| case GUAC_TRANSFER_BINARY_XNOR: |
| *dst = ((*dst) ^ (0x00FFFFFF & *src)) ^ 0x00FFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NSRC_AND: |
| *dst = ((*dst) & (0xFF000000 | (*src ^ 0x00FFFFFF))); |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NSRC_NAND: |
| *dst = ((*dst) & (0xFF000000 | (*src ^ 0x00FFFFFF))) ^ 0x00FFFFFF; |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NSRC_OR: |
| *dst = ((*dst) | (0x00FFFFFF & (*src ^ 0x00FFFFFF))); |
| break; |
| |
| case GUAC_TRANSFER_BINARY_NSRC_NOR: |
| *dst = ((*dst) | (0x00FFFFFF & (*src ^ 0x00FFFFFF))) ^ 0x00FFFFFF; |
| break; |
| |
| } |
| |
| return *dst != orig; |
| |
| } |
| |
| /** |
| * 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 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_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 = (alpha << 24) | (red << 16) | (green << 8) | blue; |
| |
| int min_x = rect->width - 1; |
| int min_y = rect->height - 1; |
| int max_x = 0; |
| int max_y = 0; |
| |
| dst_stride = dst->stride; |
| dst_buffer = dst->buffer + (dst_stride * rect->y) + (4 * rect->x); |
| |
| /* For each row */ |
| for (y=0; y < rect->height; y++) { |
| |
| uint32_t* dst_current = (uint32_t*) dst_buffer; |
| |
| /* Set row */ |
| for (x=0; x < rect->width; x++) { |
| |
| uint32_t old_color = *dst_current; |
| |
| if (old_color != color) { |
| if (x < min_x) min_x = x; |
| if (y < min_y) min_y = y; |
| if (x > max_x) max_x = x; |
| if (y > max_y) max_y = y; |
| *dst_current = color; |
| } |
| |
| dst_current++; |
| } |
| |
| /* Next row */ |
| dst_buffer += dst_stride; |
| |
| } |
| |
| /* Restrict destination rect to only updated pixels */ |
| if (max_x >= min_x && max_y >= min_y) { |
| rect->x += min_x; |
| rect->y += min_y; |
| rect->width = max_x - min_x + 1; |
| rect->height = max_y - min_y + 1; |
| } |
| else { |
| rect->width = 0; |
| rect->height = 0; |
| } |
| |
| } |
| |
| /** |
| * Applies the Porter-Duff "over" composite operator, blending the two given |
| * color components using the given alpha value. |
| * |
| * @param dst |
| * The destination color component. |
| * |
| * @param src |
| * The source color component. |
| * |
| * @param alpha |
| * The alpha value which applies to the blending operation. |
| * |
| * @return |
| * The result of applying the Porter-Duff "over" composite operator to the |
| * given source and destination components. |
| */ |
| static int guac_common_surface_blend_component(int dst, int src, int alpha) { |
| |
| int blended = src + dst * (0xFF - alpha); |
| |
| /* Do not exceed maximum component value */ |
| if (blended > 0xFF) |
| return 0xFF; |
| |
| return blended; |
| |
| } |
| |
| /** |
| * Applies the Porter-Duff "over" composite operator, blending each component |
| * of the two given ARGB colors. |
| * |
| * @param dst |
| * The destination ARGB color. |
| * |
| * @param src |
| * The source ARGB color. |
| * |
| * @return |
| * The result of applying the Porter-Duff "over" composite operator to the |
| * given source and destination colors. |
| */ |
| static uint32_t guac_common_surface_argb_blend(uint32_t dst, uint32_t src) { |
| |
| /* Separate destination ARGB color into its components */ |
| int dst_a = (dst >> 24) & 0xFF; |
| int dst_r = (dst >> 16) & 0xFF; |
| int dst_g = (dst >> 8) & 0xFF; |
| int dst_b = dst & 0xFF; |
| |
| /* Separate source ARGB color into its components */ |
| int src_a = (src >> 24) & 0xFF; |
| int src_r = (src >> 16) & 0xFF; |
| int src_g = (src >> 8) & 0xFF; |
| int src_b = src & 0xFF; |
| |
| /* If source is fully opaque (or destination is fully transparent), the |
| * blended result is the source */ |
| if (src_a == 0xFF || dst_a == 0x00) |
| return src; |
| |
| /* If source is fully transparent, the blended result is the destination */ |
| if (src_a == 0x00) |
| return dst; |
| |
| /* Otherwise, blend each ARGB component, assuming pre-multiplied alpha */ |
| int r = guac_common_surface_blend_component(dst_r, src_r, src_a); |
| int g = guac_common_surface_blend_component(dst_g, src_g, src_a); |
| int b = guac_common_surface_blend_component(dst_b, src_b, src_a); |
| int a = guac_common_surface_blend_component(dst_a, src_a, src_a); |
| |
| /* Recombine blended components */ |
| return (a << 24) | (r << 16) | (g << 8) | b; |
| |
| } |
| |
| /** |
| * Copies data from the given buffer to the surface at the given coordinates. |
| * The dimensions and location of the destination rectangle will be altered |
| * to remove as many unchanged pixels as possible. |
| * |
| * @param src_buffer The buffer to copy. |
| * @param src_stride The number of bytes in each row of the source buffer. |
| * @param sx The X coordinate of the source rectangle. |
| * @param sy The Y coordinate of the source rectangle. |
| * @param dst The destination surface. |
| * @param rect The destination rectangle. |
| * @param opaque Non-zero if the source surface is opaque (its alpha channel |
| * should be ignored), zero otherwise. |
| */ |
| static void __guac_common_surface_put(unsigned char* src_buffer, int src_stride, |
| int* sx, int* sy, |
| guac_common_surface* dst, guac_common_rect* rect, |
| int opaque) { |
| |
| unsigned char* dst_buffer = dst->buffer; |
| int dst_stride = dst->stride; |
| |
| int x, y; |
| |
| int min_x = rect->width; |
| int min_y = rect->height; |
| int max_x = 0; |
| int max_y = 0; |
| |
| int orig_x = rect->x; |
| int orig_y = rect->y; |
| |
| src_buffer += src_stride * (*sy) + 4 * (*sx); |
| dst_buffer += (dst_stride * rect->y) + (4 * rect->x); |
| |
| /* For each row */ |
| for (y=0; y < rect->height; y++) { |
| |
| uint32_t* src_current = (uint32_t*) src_buffer; |
| uint32_t* dst_current = (uint32_t*) dst_buffer; |
| |
| /* Copy row */ |
| for (x=0; x < rect->width; x++) { |
| |
| uint32_t color; |
| |
| /* Get source and destination color values */ |
| uint32_t src_color = *src_current; |
| uint32_t dst_color = *dst_current; |
| |
| /* Ignore alpha channel if opaque */ |
| if (opaque) |
| color = src_color | 0xFF000000; |
| |
| /* Otherwise, perform alpha blending operation */ |
| else |
| color = guac_common_surface_argb_blend(dst_color, src_color); |
| |
| /* If the destination color is changing, update rectangle bounds |
| * and store the new color */ |
| if (dst_color != color) { |
| if (x < min_x) min_x = x; |
| if (y < min_y) min_y = y; |
| if (x > max_x) max_x = x; |
| if (y > max_y) max_y = y; |
| *dst_current = color; |
| } |
| |
| /* Advance to next pixel */ |
| src_current++; |
| dst_current++; |
| |
| } |
| |
| /* Next row */ |
| src_buffer += src_stride; |
| dst_buffer += dst_stride; |
| |
| } |
| |
| /* Restrict destination rect to only updated pixels */ |
| if (max_x >= min_x && max_y >= min_y) { |
| rect->x += min_x; |
| rect->y += min_y; |
| rect->width = max_x - min_x + 1; |
| rect->height = max_y - min_y + 1; |
| } |
| else { |
| rect->width = 0; |
| rect->height = 0; |
| } |
| |
| /* Update source X/Y */ |
| *sx += rect->x - orig_x; |
| *sy += rect->y - orig_y; |
| |
| } |
| |
| /** |
| * Fills the given surface with color, using the given buffer as a mask. Color |
| * will be added to the given surface iff the corresponding pixel within the |
| * buffer is opaque. |
| * |
| * @param src_buffer The buffer to use as a mask. |
| * @param src_stride The number of bytes in each row of the source buffer. |
| * @param sx The X coordinate of the source rectangle. |
| * @param sy The Y coordinate of the source rectangle. |
| * @param dst The destination surface. |
| * @param rect The destination rectangle. |
| * @param red The red component of the color of the fill. |
| * @param green The green component of the color of the fill. |
| * @param blue The blue component of the color of the fill. |
| */ |
| static void __guac_common_surface_fill_mask(unsigned char* src_buffer, int src_stride, |
| int sx, int sy, |
| guac_common_surface* dst, guac_common_rect* rect, |
| int red, int green, int blue) { |
| |
| unsigned char* dst_buffer = dst->buffer; |
| int dst_stride = dst->stride; |
| |
| uint32_t color = 0xFF000000 | (red << 16) | (green << 8) | blue; |
| int x, y; |
| |
| src_buffer += src_stride*sy + 4*sx; |
| dst_buffer += (dst_stride * rect->y) + (4 * rect->x); |
| |
| /* For each row */ |
| for (y=0; y < rect->height; y++) { |
| |
| uint32_t* src_current = (uint32_t*) src_buffer; |
| uint32_t* dst_current = (uint32_t*) dst_buffer; |
| |
| /* Stencil row */ |
| for (x=0; x < rect->width; x++) { |
| |
| /* Fill with color if opaque */ |
| if (*src_current & 0xFF000000) |
| *dst_current = color; |
| |
| src_current++; |
| dst_current++; |
| } |
| |
| /* Next row */ |
| src_buffer += src_stride; |
| dst_buffer += dst_stride; |
| |
| } |
| |
| } |
| |
| /** |
| * Copies data from the given surface to the given destination surface using |
| * the specified transfer function. |
| * |
| * @param src_buffer The buffer to copy. |
| * @param src_stride The number of bytes in each row of the source buffer. |
| * @param sx The X coordinate of the source rectangle. |
| * @param sy The Y coordinate of the source rectangle. |
| * @param op The transfer function to use. |
| * @param dst The destination surface. |
| * @param rect The destination rectangle. |
| */ |
| static void __guac_common_surface_transfer(guac_common_surface* src, int* sx, int* sy, |
| guac_transfer_function op, |
| guac_common_surface* dst, guac_common_rect* rect) { |
| |
| unsigned char* src_buffer = src->buffer; |
| unsigned char* dst_buffer = dst->buffer; |
| |
| int x, y; |
| int src_stride, dst_stride; |
| int step = 1; |
| |
| int min_x = rect->width - 1; |
| int min_y = rect->height - 1; |
| int max_x = 0; |
| int max_y = 0; |
| |
| int orig_x = rect->x; |
| int orig_y = rect->y; |
| |
| /* Copy forwards only if destination is in a different surface or is before source */ |
| if (src != dst || rect->y < *sy || (rect->y == *sy && rect->x < *sx)) { |
| src_buffer += src->stride * (*sy) + 4 * (*sx); |
| dst_buffer += (dst->stride * rect->y) + (4 * rect->x); |
| src_stride = src->stride; |
| dst_stride = dst->stride; |
| step = 1; |
| } |
| |
| /* Otherwise, copy backwards */ |
| else { |
| src_buffer += src->stride * (*sy + rect->height - 1) + 4 * (*sx + rect->width - 1); |
| dst_buffer += dst->stride * (rect->y + rect->height - 1) + 4 * (rect->x + rect->width - 1); |
| src_stride = -src->stride; |
| dst_stride = -dst->stride; |
| step = -1; |
| } |
| |
| /* For each row */ |
| for (y=0; y < rect->height; y++) { |
| |
| uint32_t* src_current = (uint32_t*) src_buffer; |
| uint32_t* dst_current = (uint32_t*) dst_buffer; |
| |
| /* Transfer each pixel in row */ |
| for (x=0; x < rect->width; x++) { |
| |
| if (__guac_common_surface_transfer_int(op, src_current, dst_current)) { |
| if (x < min_x) min_x = x; |
| if (y < min_y) min_y = y; |
| if (x > max_x) max_x = x; |
| if (y > max_y) max_y = y; |
| } |
| |
| src_current += step; |
| dst_current += step; |
| } |
| |
| /* Next row */ |
| src_buffer += src_stride; |
| dst_buffer += dst_stride; |
| |
| } |
| |
| /* Translate X coordinate space of moving backwards */ |
| if (step < 0) { |
| int old_max_x = max_x; |
| max_x = rect->width - 1 - min_x; |
| min_x = rect->width - 1 - old_max_x; |
| } |
| |
| /* Translate Y coordinate space of moving backwards */ |
| if (dst_stride < 0) { |
| int old_max_y = max_y; |
| max_y = rect->height - 1 - min_y; |
| min_y = rect->height - 1 - old_max_y; |
| } |
| |
| /* Restrict destination rect to only updated pixels */ |
| if (max_x >= min_x && max_y >= min_y) { |
| rect->x += min_x; |
| rect->y += min_y; |
| rect->width = max_x - min_x + 1; |
| rect->height = max_y - min_y + 1; |
| } |
| else { |
| rect->width = 0; |
| rect->height = 0; |
| } |
| |
| /* Update source X/Y */ |
| *sx += rect->x - orig_x; |
| *sy += rect->y - orig_y; |
| |
| } |
| |
| guac_common_surface* guac_common_surface_alloc(guac_client* client, |
| guac_socket* socket, const guac_layer* layer, int w, int h) { |
| |
| /* Calculate heat map dimensions */ |
| int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); |
| int heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); |
| |
| /* Init surface */ |
| guac_common_surface* surface = calloc(1, sizeof(guac_common_surface)); |
| surface->client = client; |
| surface->socket = socket; |
| surface->layer = layer; |
| surface->parent = GUAC_DEFAULT_LAYER; |
| surface->opacity = 0xFF; |
| surface->width = w; |
| surface->height = h; |
| |
| pthread_mutex_init(&surface->_lock, NULL); |
| |
| /* Create corresponding Cairo surface */ |
| surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w); |
| surface->buffer = calloc(h, surface->stride); |
| |
| /* Create corresponding heat map */ |
| surface->heat_map = calloc(heat_width * heat_height, |
| sizeof(guac_common_surface_heat_cell)); |
| |
| /* Reset clipping rect */ |
| guac_common_surface_reset_clip(surface); |
| |
| /* Layers must initially exist */ |
| if (layer->index >= 0) { |
| guac_protocol_send_size(socket, layer, w, h); |
| surface->realized = 1; |
| } |
| |
| /* Defer creation of buffers */ |
| else |
| surface->realized = 0; |
| |
| return surface; |
| } |
| |
| void guac_common_surface_free(guac_common_surface* surface) { |
| |
| /* Only dispose of surface if it exists */ |
| if (surface->realized) |
| guac_protocol_send_dispose(surface->socket, surface->layer); |
| |
| pthread_mutex_destroy(&surface->_lock); |
| |
| free(surface->heat_map); |
| free(surface->buffer); |
| free(surface); |
| |
| } |
| |
| void guac_common_surface_resize(guac_common_surface* surface, int w, int h) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| /* Ignore if resize will have no effect */ |
| if (w == surface->width && h == surface->height) |
| goto complete; |
| |
| guac_socket* socket = surface->socket; |
| const guac_layer* layer = surface->layer; |
| |
| unsigned char* old_buffer; |
| int old_stride; |
| guac_common_rect old_rect; |
| |
| int sx = 0; |
| int sy = 0; |
| |
| /* Calculate heat map dimensions */ |
| int heat_width = GUAC_COMMON_SURFACE_HEAT_DIMENSION(w); |
| int heat_height = GUAC_COMMON_SURFACE_HEAT_DIMENSION(h); |
| |
| /* Copy old surface data */ |
| old_buffer = surface->buffer; |
| old_stride = surface->stride; |
| guac_common_rect_init(&old_rect, 0, 0, surface->width, surface->height); |
| |
| /* Re-initialize at new size */ |
| surface->width = w; |
| surface->height = h; |
| 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); |
| |
| /* Copy relevant old data */ |
| __guac_common_bound_rect(surface, &old_rect, NULL, NULL); |
| __guac_common_surface_put(old_buffer, old_stride, &sx, &sy, surface, &old_rect, 1); |
| |
| /* Free old data */ |
| free(old_buffer); |
| |
| /* Allocate completely new heat map (can safely discard old stats) */ |
| free(surface->heat_map); |
| surface->heat_map = calloc(heat_width * heat_height, |
| sizeof(guac_common_surface_heat_cell)); |
| |
| /* Resize dirty rect to fit new surface dimensions */ |
| if (surface->dirty) { |
| __guac_common_bound_rect(surface, &surface->dirty_rect, NULL, NULL); |
| if (surface->dirty_rect.width <= 0 || surface->dirty_rect.height <= 0) |
| surface->dirty = 0; |
| } |
| |
| /* Update Guacamole layer */ |
| if (surface->realized) |
| guac_protocol_send_size(socket, layer, w, h); |
| |
| complete: |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_surface_t* src) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| unsigned char* buffer = cairo_image_surface_get_data(src); |
| cairo_format_t format = cairo_image_surface_get_format(src); |
| int stride = cairo_image_surface_get_stride(src); |
| int w = cairo_image_surface_get_width(src); |
| int h = cairo_image_surface_get_height(src); |
| |
| int sx = 0; |
| int sy = 0; |
| |
| guac_common_rect rect; |
| guac_common_rect_init(&rect, x, y, w, h); |
| |
| /* Clip operation */ |
| __guac_common_clip_rect(surface, &rect, &sx, &sy); |
| if (rect.width <= 0 || rect.height <= 0) |
| goto complete; |
| |
| /* Update backing surface */ |
| __guac_common_surface_put(buffer, stride, &sx, &sy, surface, &rect, format != CAIRO_FORMAT_ARGB32); |
| if (rect.width <= 0 || rect.height <= 0) |
| goto complete; |
| |
| /* Update the heat map for the update rectangle. */ |
| guac_timestamp time = guac_timestamp_current(); |
| __guac_common_surface_touch_rect(surface, &rect, time); |
| |
| /* 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); |
| |
| complete: |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_paint(guac_common_surface* surface, int x, int y, |
| cairo_surface_t* src, int red, int green, int blue) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| unsigned char* buffer = cairo_image_surface_get_data(src); |
| int stride = cairo_image_surface_get_stride(src); |
| int w = cairo_image_surface_get_width(src); |
| int h = cairo_image_surface_get_height(src); |
| |
| int sx = 0; |
| int sy = 0; |
| |
| guac_common_rect rect; |
| guac_common_rect_init(&rect, x, y, w, h); |
| |
| /* Clip operation */ |
| __guac_common_clip_rect(surface, &rect, &sx, &sy); |
| if (rect.width <= 0 || rect.height <= 0) |
| goto complete; |
| |
| /* Update backing surface */ |
| __guac_common_surface_fill_mask(buffer, stride, sx, sy, surface, &rect, red, green, blue); |
| |
| /* 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); |
| |
| complete: |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_copy(guac_common_surface* src, int sx, int sy, |
| int w, int h, guac_common_surface* dst, int dx, int dy) { |
| |
| /* Lock both surfaces */ |
| pthread_mutex_lock(&dst->_lock); |
| if (src != dst) |
| pthread_mutex_lock(&src->_lock); |
| |
| guac_socket* socket = dst->socket; |
| const guac_layer* src_layer = src->layer; |
| const guac_layer* dst_layer = dst->layer; |
| |
| guac_common_rect srect; |
| guac_common_rect_init(&srect, sx, sy, w, h); |
| |
| /* Clip operation source rect to bounds */ |
| __guac_common_bound_rect(src, &srect, &dx, &dy); |
| if (srect.width <= 0 || srect.height <= 0) |
| goto complete; |
| |
| guac_common_rect drect; |
| guac_common_rect_init(&drect, dx, dy, |
| srect.width, srect.height); |
| |
| /* Clip operation destination rect */ |
| __guac_common_clip_rect(dst, &drect, &srect.x, &srect.y); |
| if (drect.width <= 0 || drect.height <= 0) |
| goto complete; |
| |
| /* NOTE: Being the last rectangle to be adjusted, only the width/height of |
| * drect is now correct! */ |
| |
| /* Update backing surface first only if drect cannot intersect srect */ |
| if (src != dst) { |
| __guac_common_surface_transfer(src, &srect.x, &srect.y, |
| GUAC_TRANSFER_BINARY_SRC, dst, &drect); |
| if (drect.width <= 0 || drect.height <= 0) |
| goto complete; |
| } |
| |
| /* Defer if combining */ |
| if (__guac_common_should_combine(dst, &drect, 1)) |
| __guac_common_mark_dirty(dst, &drect); |
| |
| /* Otherwise, flush and draw immediately */ |
| else { |
| __guac_common_surface_flush(dst); |
| __guac_common_surface_flush(src); |
| guac_protocol_send_copy(socket, src_layer, srect.x, srect.y, |
| drect.width, drect.height, GUAC_COMP_OVER, dst_layer, |
| drect.x, drect.y); |
| dst->realized = 1; |
| } |
| |
| /* Update backing surface last if drect can intersect srect */ |
| if (src == dst) |
| __guac_common_surface_transfer(src, &srect.x, &srect.y, |
| GUAC_TRANSFER_BINARY_SRC, dst, &drect); |
| |
| complete: |
| |
| /* Unlock both surfaces */ |
| pthread_mutex_unlock(&dst->_lock); |
| if (src != dst) |
| pthread_mutex_unlock(&src->_lock); |
| |
| } |
| |
| void guac_common_surface_transfer(guac_common_surface* src, int sx, int sy, int w, int h, |
| guac_transfer_function op, guac_common_surface* dst, int dx, int dy) { |
| |
| /* Lock both surfaces */ |
| pthread_mutex_lock(&dst->_lock); |
| if (src != dst) |
| pthread_mutex_lock(&src->_lock); |
| |
| guac_socket* socket = dst->socket; |
| const guac_layer* src_layer = src->layer; |
| const guac_layer* dst_layer = dst->layer; |
| |
| guac_common_rect srect; |
| guac_common_rect_init(&srect, sx, sy, w, h); |
| |
| /* Clip operation source rect to bounds */ |
| __guac_common_bound_rect(src, &srect, &dx, &dy); |
| if (srect.width <= 0 || srect.height <= 0) |
| goto complete; |
| |
| guac_common_rect drect; |
| guac_common_rect_init(&drect, dx, dy, |
| srect.width, srect.height); |
| |
| /* Clip operation destination rect */ |
| __guac_common_clip_rect(dst, &drect, &srect.x, &srect.y); |
| if (drect.width <= 0 || drect.height <= 0) |
| goto complete; |
| |
| /* NOTE: Being the last rectangle to be adjusted, only the width/height of |
| * drect is now correct! */ |
| |
| /* Update backing surface first only if drect cannot intersect srect */ |
| if (src != dst) { |
| __guac_common_surface_transfer(src, &srect.x, &srect.y, op, dst, &drect); |
| if (drect.width <= 0 || drect.height <= 0) |
| goto complete; |
| } |
| |
| /* Defer if combining */ |
| if (__guac_common_should_combine(dst, &drect, 1)) |
| __guac_common_mark_dirty(dst, &drect); |
| |
| /* Otherwise, flush and draw immediately */ |
| else { |
| __guac_common_surface_flush(dst); |
| __guac_common_surface_flush(src); |
| guac_protocol_send_transfer(socket, src_layer, srect.x, srect.y, |
| drect.width, drect.height, op, dst_layer, drect.x, drect.y); |
| dst->realized = 1; |
| } |
| |
| /* Update backing surface last if drect can intersect srect */ |
| if (src == dst) |
| __guac_common_surface_transfer(src, &srect.x, &srect.y, op, dst, &drect); |
| |
| complete: |
| |
| /* Unlock both surfaces */ |
| pthread_mutex_unlock(&dst->_lock); |
| if (src != dst) |
| pthread_mutex_unlock(&src->_lock); |
| |
| } |
| |
| 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); |
| |
| guac_socket* socket = surface->socket; |
| const guac_layer* layer = surface->layer; |
| |
| guac_common_rect rect; |
| guac_common_rect_init(&rect, x, y, w, h); |
| |
| /* Clip operation */ |
| __guac_common_clip_rect(surface, &rect, NULL, NULL); |
| if (rect.width <= 0 || rect.height <= 0) |
| goto complete; |
| |
| /* Update backing surface */ |
| __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 */ |
| 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, alpha); |
| surface->realized = 1; |
| } |
| |
| complete: |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_clip(guac_common_surface* surface, int x, int y, int w, int h) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| guac_common_rect clip; |
| |
| /* Init clipping rectangle if clipping not already applied */ |
| if (!surface->clipped) { |
| guac_common_rect_init(&surface->clip_rect, 0, 0, surface->width, surface->height); |
| surface->clipped = 1; |
| } |
| |
| guac_common_rect_init(&clip, x, y, w, h); |
| guac_common_rect_constrain(&surface->clip_rect, &clip); |
| |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_reset_clip(guac_common_surface* surface) { |
| pthread_mutex_lock(&surface->_lock); |
| surface->clipped = 0; |
| pthread_mutex_unlock(&surface->_lock); |
| } |
| |
| /** |
| * Flushes the bitmap update currently described by the dirty rectangle within |
| * the given surface directly via an "img" instruction as PNG data. The |
| * resulting instructions will be sent over the socket associated with the |
| * given 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, |
| int opaque) { |
| |
| if (surface->dirty) { |
| |
| guac_socket* socket = surface->socket; |
| const guac_layer* layer = surface->layer; |
| |
| /* Get Cairo surface for specified rect */ |
| unsigned char* buffer = surface->buffer |
| + surface->dirty_rect.y * surface->stride |
| + surface->dirty_rect.x * 4; |
| |
| 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, |
| layer, surface->dirty_rect.x, surface->dirty_rect.y, rect); |
| |
| cairo_surface_destroy(rect); |
| surface->realized = 1; |
| |
| /* Surface is no longer dirty */ |
| surface->dirty = 0; |
| |
| } |
| |
| } |
| |
| /** |
| * Returns an appropriate quality between 0 and 100 for lossy encoding |
| * depending on the current processing lag calculated for the given client. |
| * |
| * @param client |
| * The client for which the lossy quality is being calculated. |
| * |
| * @return |
| * A value between 0 and 100 inclusive which seems appropriate for the |
| * client based on lag measurements. |
| */ |
| static int guac_common_surface_suggest_quality(guac_client* client) { |
| |
| int lag = guac_client_get_processing_lag(client); |
| |
| /* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */ |
| int quality = 90 - (lag - 20); |
| |
| /* Do not exceed 90 for quality */ |
| if (quality > 90) |
| return 90; |
| |
| /* Do not go below 30 for quality */ |
| if (quality < 30) |
| return 30; |
| |
| return quality; |
| |
| } |
| |
| /** |
| * Flushes the bitmap update currently described by the dirty rectangle within |
| * the given surface directly via an "img" instruction as JPEG data. The |
| * resulting instructions will be sent over the socket associated with the |
| * given surface. |
| * |
| * @param surface |
| * The surface to flush. |
| */ |
| static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) { |
| |
| if (surface->dirty) { |
| |
| guac_socket* socket = surface->socket; |
| const guac_layer* layer = surface->layer; |
| |
| guac_common_rect max; |
| guac_common_rect_init(&max, 0, 0, surface->width, surface->height); |
| |
| /* Expand the dirty rect size to fit in a grid with cells equal to the |
| * minimum JPEG block size */ |
| guac_common_rect_expand_to_grid(GUAC_SURFACE_JPEG_BLOCK_SIZE, |
| &surface->dirty_rect, &max); |
| |
| /* Get Cairo surface for specified rect */ |
| unsigned char* buffer = surface->buffer |
| + 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); |
| |
| /* Send JPEG for rect */ |
| guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer, |
| surface->dirty_rect.x, surface->dirty_rect.y, rect, |
| guac_common_surface_suggest_quality(surface->client)); |
| |
| cairo_surface_destroy(rect); |
| surface->realized = 1; |
| |
| /* Surface is no longer dirty */ |
| surface->dirty = 0; |
| |
| } |
| |
| } |
| |
| /** |
| * Flushes the bitmap update currently described by the dirty rectangle within |
| * the given surface directly via an "img" instruction as WebP data. The |
| * resulting instructions will be sent over the socket associated with the |
| * given 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, |
| int opaque) { |
| |
| if (surface->dirty) { |
| |
| guac_socket* socket = surface->socket; |
| const guac_layer* layer = surface->layer; |
| |
| guac_common_rect max; |
| guac_common_rect_init(&max, 0, 0, surface->width, surface->height); |
| |
| /* Expand the dirty rect size to fit in a grid with cells equal to the |
| * minimum WebP block size */ |
| guac_common_rect_expand_to_grid(GUAC_SURFACE_WEBP_BLOCK_SIZE, |
| &surface->dirty_rect, &max); |
| |
| /* Get Cairo surface for specified rect */ |
| unsigned char* buffer = surface->buffer |
| + surface->dirty_rect.y * surface->stride |
| + surface->dirty_rect.x * 4; |
| |
| 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, |
| surface->dirty_rect.x, surface->dirty_rect.y, rect, |
| guac_common_surface_suggest_quality(surface->client), 0); |
| |
| cairo_surface_destroy(rect); |
| surface->realized = 1; |
| |
| /* Surface is no longer dirty */ |
| surface->dirty = 0; |
| |
| } |
| |
| } |
| |
| /** |
| * Comparator for instances of guac_common_surface_bitmap_rect, the elements |
| * which make up a surface's bitmap buffer. |
| * |
| * @see qsort |
| */ |
| static int __guac_common_surface_bitmap_rect_compare(const void* a, const void* b) { |
| |
| guac_common_surface_bitmap_rect* ra = (guac_common_surface_bitmap_rect*) a; |
| guac_common_surface_bitmap_rect* rb = (guac_common_surface_bitmap_rect*) b; |
| |
| /* Order roughly top to bottom, left to right */ |
| if (ra->rect.y != rb->rect.y) return ra->rect.y - rb->rect.y; |
| if (ra->rect.x != rb->rect.x) return ra->rect.x - rb->rect.x; |
| |
| /* Wider updates should come first (more likely to intersect later) */ |
| if (ra->rect.width != rb->rect.width) return rb->rect.width - ra->rect.width; |
| |
| /* Shorter updates should come first (less likely to increase cost) */ |
| return ra->rect.height - rb->rect.height; |
| |
| } |
| |
| /** |
| * Flushes only the properties of the given surface, such as layer location or |
| * opacity. Image state is not flushed. If the surface represents a buffer or |
| * the default layer, this function has no effect. |
| * |
| * @param surface |
| * The surface to flush. |
| */ |
| static void __guac_common_surface_flush_properties( |
| guac_common_surface* surface) { |
| |
| guac_socket* socket = surface->socket; |
| |
| /* Only applicable to non-default visible layers */ |
| if (surface->layer->index <= 0) |
| return; |
| |
| /* Flush opacity */ |
| if (surface->opacity_dirty) { |
| guac_protocol_send_shade(socket, surface->layer, surface->opacity); |
| surface->opacity_dirty = 0; |
| } |
| |
| /* Flush location and hierarchy */ |
| if (surface->location_dirty) { |
| guac_protocol_send_move(socket, surface->layer, |
| surface->parent, surface->x, surface->y, surface->z); |
| surface->location_dirty = 0; |
| } |
| |
| } |
| |
| static void __guac_common_surface_flush(guac_common_surface* surface) { |
| |
| /* Flush final dirty rectangle to queue. */ |
| __guac_common_surface_flush_to_queue(surface); |
| |
| guac_common_surface_bitmap_rect* current = surface->bitmap_queue; |
| int i, j; |
| int original_queue_length; |
| int flushed = 0; |
| |
| original_queue_length = surface->bitmap_queue_length; |
| |
| /* Sort updates to make combination less costly */ |
| qsort(surface->bitmap_queue, surface->bitmap_queue_length, sizeof(guac_common_surface_bitmap_rect), |
| __guac_common_surface_bitmap_rect_compare); |
| |
| /* Flush all rects in queue */ |
| for (i=0; i < surface->bitmap_queue_length; i++) { |
| |
| /* Get next unflushed candidate */ |
| guac_common_surface_bitmap_rect* candidate = current; |
| if (!candidate->flushed) { |
| |
| int combined = 0; |
| |
| /* Build up rect as much as possible */ |
| for (j=i; j < surface->bitmap_queue_length; j++) { |
| |
| if (!candidate->flushed) { |
| |
| /* Clip candidate within current bounds */ |
| __guac_common_bound_rect(surface, &candidate->rect, NULL, NULL); |
| if (candidate->rect.width <= 0 || candidate->rect.height <= 0) |
| candidate->flushed = 1; |
| |
| /* Combine if reasonable */ |
| else if (__guac_common_should_combine(surface, &candidate->rect, 0) || !surface->dirty) { |
| __guac_common_mark_dirty(surface, &candidate->rect); |
| candidate->flushed = 1; |
| combined++; |
| } |
| |
| } |
| |
| candidate++; |
| |
| } |
| |
| /* Re-add to queue if there's room and this update was modified or we expect others might be */ |
| if ((combined > 1 || i < original_queue_length) |
| && surface->bitmap_queue_length < GUAC_COMMON_SURFACE_QUEUE_SIZE) |
| __guac_common_surface_flush_to_queue(surface); |
| |
| /* Flush as bitmap otherwise */ |
| else if (surface->dirty) { |
| |
| 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, opaque); |
| |
| /* If not WebP, JPEG is the next best (lossy) choice */ |
| 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, opaque); |
| |
| } |
| |
| } |
| |
| current++; |
| |
| } |
| |
| /* Flush complete */ |
| surface->bitmap_queue_length = 0; |
| |
| } |
| |
| void guac_common_surface_flush(guac_common_surface* surface) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| /* Flush any applicable layer properties */ |
| __guac_common_surface_flush_properties(surface); |
| |
| /* Flush surface contents */ |
| __guac_common_surface_flush(surface); |
| |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |
| void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, |
| guac_socket* socket) { |
| |
| pthread_mutex_lock(&surface->_lock); |
| |
| /* Do nothing if not realized */ |
| if (!surface->realized) |
| goto complete; |
| |
| /* Synchronize layer-specific properties if applicable */ |
| if (surface->layer->index > 0) { |
| |
| /* Synchronize opacity */ |
| guac_protocol_send_shade(socket, surface->layer, surface->opacity); |
| |
| /* Synchronize location and hierarchy */ |
| guac_protocol_send_move(socket, surface->layer, |
| surface->parent, surface->x, surface->y, surface->z); |
| |
| } |
| |
| /* Sync size to new socket */ |
| guac_protocol_send_size(socket, surface->layer, |
| surface->width, surface->height); |
| |
| /* Send contents of layer, if non-empty */ |
| if (surface->width > 0 && surface->height > 0) { |
| |
| /* Get entire surface */ |
| cairo_surface_t* rect = cairo_image_surface_create_for_data( |
| surface->buffer, CAIRO_FORMAT_ARGB32, |
| surface->width, surface->height, surface->stride); |
| |
| /* Send PNG for rect */ |
| guac_user_stream_png(user, socket, GUAC_COMP_OVER, surface->layer, |
| 0, 0, rect); |
| cairo_surface_destroy(rect); |
| |
| } |
| |
| complete: |
| pthread_mutex_unlock(&surface->_lock); |
| |
| } |
| |