From 364d2842ab3756ce8f46c8b3ec2865c21ee8fbf6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 09:35:23 -0700 Subject: [PATCH 1/9] GUAC-240: Move PNG encoder to own file, writing directly to an img stream. --- src/libguac/Makefile.am | 2 + src/libguac/encode-png.c | 402 +++++++++++++++++++++++++++++++ src/libguac/encode-png.h | 53 ++++ src/libguac/guacamole/protocol.h | 34 +++ src/libguac/protocol.c | 27 +++ 5 files changed, 518 insertions(+) create mode 100644 src/libguac/encode-png.c create mode 100644 src/libguac/encode-png.h diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index e53e71a1..c059c822 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -64,6 +64,7 @@ libguacinc_HEADERS = \ noinst_HEADERS = \ client-handlers.h \ + encode-png.h \ palette.h \ wav_encoder.h @@ -71,6 +72,7 @@ libguac_la_SOURCES = \ audio.c \ client.c \ client-handlers.c \ + encode-png.c \ error.c \ hash.c \ instruction.c \ diff --git a/src/libguac/encode-png.c b/src/libguac/encode-png.c new file mode 100644 index 00000000..d3632f28 --- /dev/null +++ b/src/libguac/encode-png.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "encode-png.h" +#include "error.h" +#include "palette.h" +#include "protocol.h" +#include "stream.h" + +#include +#include + +#ifdef HAVE_PNGSTRUCT_H +#include +#endif + +#include +#include +#include +#include +#include + +/** + * Data describing the current write state of PNG data. + */ +typedef struct guac_png_write_state { + + /** + * The socket over which all PNG blobs will be written. + */ + guac_socket* socket; + + /** + * The Guacamole stream to associate with each PNG blob. + */ + guac_stream* stream; + + /** + * Buffer of pending PNG data. + */ + char buffer[6048]; + + /** + * The number of bytes currently stored in the buffer. + */ + int buffer_size; + +} guac_png_write_state; + +/** + * Writes the contents of the PNG write state as a blob to its associated + * socket. + * + * @param write_state + * The write state to flush. + */ +static void guac_png_flush_data(guac_png_write_state* write_state) { + + /* Send blob */ + guac_protocol_send_blob(write_state->socket, write_state->stream, + write_state->buffer, write_state->buffer_size); + + /* Clear buffer */ + write_state->buffer_size = 0; + +} + +/** + * Appends the given PNG data to the internal buffer of the given write state, + * automatically flushing the write state as necessary. + * socket. + * + * @param write_state + * The write state to append the given data to. + * + * @param data + * The PNG data to append. + * + * @param length + * The size of the given PNG data, in bytes. + */ +static void guac_png_write_data(guac_png_write_state* write_state, + const void* data, int length) { + + const unsigned char* current = data; + + /* Append all data given */ + while (length > 0) { + + /* Calculate space remaining */ + int remaining = sizeof(write_state->buffer) - write_state->buffer_size; + + /* If no space remains, flush buffer to make room */ + if (remaining == 0) { + guac_png_flush_data(write_state); + remaining = sizeof(write_state->buffer); + } + + /* Calculate size of next block of data to append */ + int block_size = remaining; + if (block_size > length) + block_size = length; + + /* Append block */ + memcpy(write_state->buffer + write_state->buffer_size, + current, block_size); + + /* Next block */ + current += block_size; + write_state->buffer_size += block_size; + length -= block_size; + + } + +} + +/** + * Writes the given buffer of PNG data to the buffer of the given write state, + * flushing that buffer to blob instructions if necessary. This handler is + * called by Cairo when writing PNG data via + * cairo_surface_write_to_png_stream(). + * + * @param closure + * Pointer to arbitrary data passed to cairo_surface_write_to_png_stream(). + * In the case of this handler, this data will be the guac_png_write_state. + * + * @param data + * The buffer of PNG data to write. + * + * @param length + * The size of the given buffer, in bytes. + * + * @return + * A Cairo status code indicating whether the write operation succeeded. + * In the case of this handler, this will always be CAIRO_STATUS_SUCCESS. + */ +static cairo_status_t guac_png_cairo_write_handler(void* closure, + const unsigned char* data, unsigned int length) { + + guac_png_write_state* write_state = (guac_png_write_state*) closure; + + /* Append data to buffer, writing as necessary */ + guac_png_write_data(write_state, data, length); + + return CAIRO_STATUS_SUCCESS; + +} + +/** + * Implementation of guac_png_write() which uses Cairo's own PNG encoder to + * write PNG data, rather than using libpng directly. + * + * @param socket + * The socket to send PNG blobs over. + * + * @param stream + * The stream to associate with each blob. + * + * @param surface + * The Cairo surface to write to the given stream and socket as PNG blobs. + * + * @return + * Zero if the encoding operation is successful, non-zero otherwise. + */ +static int guac_png_cairo_write(guac_socket* socket, guac_stream* stream, + cairo_surface_t* surface) { + + guac_png_write_state write_state; + + /* Init write state */ + write_state.socket = socket; + write_state.stream = stream; + write_state.buffer_size = 0; + + /* Write surface as PNG */ + if (cairo_surface_write_to_png_stream(surface, + guac_png_cairo_write_handler, + &write_state) != CAIRO_STATUS_SUCCESS) { + guac_error = GUAC_STATUS_INTERNAL_ERROR; + guac_error_message = "Cairo PNG backend failed"; + return -1; + } + + /* Flush remaining PNG data */ + guac_png_flush_data(&write_state); + return 0; + +} + +/** + * Writes the given buffer of PNG data to the buffer of the given write state, + * flushing that buffer to blob instructions if necessary. This handler is + * called by libpng when writing PNG data via png_write_png(). + * + * @param png + * The PNG compression state structure associated with the write operation. + * The pointer to arbitrary data will have been set to the + * guac_png_write_state by png_set_write_fn(), and will be accessible via + * png->io_ptr or png_get_io_ptr(png), depending on the version of libpng. + * + * @param data + * The buffer of PNG data to write. + * + * @param length + * The size of the given buffer, in bytes. + */ +static void guac_png_write_handler(png_structp png, png_bytep data, + png_size_t length) { + + /* Get png buffer structure */ + guac_png_write_state* write_state; +#ifdef HAVE_PNG_GET_IO_PTR + write_state = (guac_png_write_state*) png_get_io_ptr(png); +#else + write_state = (guac_png_write_state*) png->io_ptr; +#endif + + /* Append data to buffer, writing as necessary */ + guac_png_write_data(write_state, data, length); + +} + +/** + * Flushes any PNG data within the buffer of the given write state as a blob + * instruction. If no data is within the buffer, this function has no effect. + * This handler is called by libpng when it has finished writing PNG data via + * png_write_png(). + * + * @param png + * The PNG compression state structure associated with the write operation. + * The pointer to arbitrary data will have been set to the + * guac_png_write_state by png_set_write_fn(), and will be accessible via + * png->io_ptr or png_get_io_ptr(png), depending on the version of libpng. + */ +static void guac_png_flush_handler(png_structp png) { + + /* Get png buffer structure */ + guac_png_write_state* write_state; +#ifdef HAVE_PNG_GET_IO_PTR + write_state = (guac_png_write_state*) png_get_io_ptr(png); +#else + write_state = (guac_png_write_state*) png->io_ptr; +#endif + + /* Flush buffer */ + guac_png_flush_data(write_state); + +} + +int guac_png_write(guac_socket* socket, guac_stream* stream, + cairo_surface_t* surface) { + + png_structp png; + png_infop png_info; + png_byte** png_rows; + int bpp; + + int x, y; + + guac_png_write_state write_state; + + /* Get image surface properties and data */ + cairo_format_t format = cairo_image_surface_get_format(surface); + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + int stride = cairo_image_surface_get_stride(surface); + unsigned char* data = cairo_image_surface_get_data(surface); + + /* If not RGB24, use Cairo PNG writer */ + if (format != CAIRO_FORMAT_RGB24 || data == NULL) + return guac_png_cairo_write(socket, stream, surface); + + /* Flush pending operations to surface */ + cairo_surface_flush(surface); + + /* Attempt to build palette */ + guac_palette* palette = guac_palette_alloc(surface); + + /* If not possible, resort to Cairo PNG writer */ + if (palette == NULL) + return guac_png_cairo_write(socket, stream, surface); + + /* Calculate BPP from palette size */ + if (palette->size <= 2) bpp = 1; + else if (palette->size <= 4) bpp = 2; + else if (palette->size <= 16) bpp = 4; + else bpp = 8; + + /* Set up PNG writer */ + png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) { + guac_error = GUAC_STATUS_INTERNAL_ERROR; + guac_error_message = "libpng failed to create write structure"; + return -1; + } + + png_info = png_create_info_struct(png); + if (!png_info) { + png_destroy_write_struct(&png, NULL); + guac_error = GUAC_STATUS_INTERNAL_ERROR; + guac_error_message = "libpng failed to create info structure"; + return -1; + } + + /* Set error handler */ + if (setjmp(png_jmpbuf(png))) { + png_destroy_write_struct(&png, &png_info); + guac_error = GUAC_STATUS_IO_ERROR; + guac_error_message = "libpng output error"; + return -1; + } + + /* Init write state */ + write_state.socket = socket; + write_state.stream = stream; + write_state.buffer_size = 0; + + /* Set up writer */ + png_set_write_fn(png, &write_state, + guac_png_write_handler, + guac_png_flush_handler); + + /* Copy data from surface into PNG data */ + png_rows = (png_byte**) malloc(sizeof(png_byte*) * height); + for (y=0; ycolors, palette->size); + + /* Write image */ + png_set_rows(png, png_info, png_rows); + png_write_png(png, png_info, PNG_TRANSFORM_PACKING, NULL); + + /* Finish write */ + png_destroy_write_struct(&png, &png_info); + + /* Free palette */ + guac_palette_free(palette); + + /* Free PNG data */ + for (y=0; y + +/** + * Encodes the given surface as a PNG, and sends the resulting data over the + * given stream and socket as blobs. + * + * @param socket + * The socket to send PNG blobs over. + * + * @param stream + * The stream to associate with each blob. + * + * @param surface + * The Cairo surface to write to the given stream and socket as PNG blobs. + * + * @return + * Zero if the encoding operation is successful, non-zero otherwise. + */ +int guac_png_write(guac_socket* socket, guac_stream* stream, + cairo_surface_t* surface); + +#endif + diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 16ea70a3..7256e7c4 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -647,6 +647,40 @@ int guac_protocol_send_jpeg(guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface, int quality); +/** + * Sends an img instruction over the given guac_socket connection. + * + * If an error occurs sending the instruction, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param socket + * The guac_socket connection to use when sending the img instruction. + * + * @param stream + * The stream over which the image data will be sent. + * + * @param mode + * The composite mode to use when drawing the image over the destination + * layer. + * + * @param layer + * The destination layer. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * within the destination layer, in pixels. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * within the destination layer, in pixels. + * + * @return + * Zero if the instruction was successfully sent, non-zero on error. + */ +int guac_protocol_send_img(guac_socket* socket, const guac_stream* stream, + guac_composite_mode mode, const guac_layer* layer, + const char* mimetype, int x, int y); + /** * Sends a pop instruction over the given guac_socket connection. * diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 4d13b51d..62cdf03c 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -1400,6 +1400,33 @@ int guac_protocol_send_png(guac_socket* socket, guac_composite_mode mode, } +int guac_protocol_send_img(guac_socket* socket, const guac_stream* stream, + guac_composite_mode mode, const guac_layer* layer, + const char* mimetype, int x, int y) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "3.img,") + || __guac_socket_write_length_int(socket, stream->index) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, mode) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, layer->index) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, mimetype) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, x) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, y) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + int guac_protocol_send_pop(guac_socket* socket, const guac_layer* layer) { int ret_val; From d07d8ba2d29f6b807fe5394c51ac4bec20960b43 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 15:18:12 -0700 Subject: [PATCH 2/9] GUAC-240: Associate Guacamole client with surface. --- src/common/guac_surface.c | 7 +++++-- src/common/guac_surface.h | 31 +++++++++++++++++++++++++------ src/protocols/rdp/client.c | 5 +++-- src/protocols/rdp/rdp_bitmap.c | 3 ++- src/protocols/vnc/client.c | 5 +++-- src/terminal/display.c | 4 ++-- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/common/guac_surface.c b/src/common/guac_surface.c index d2c6a435..3ef36666 100644 --- a/src/common/guac_surface.c +++ b/src/common/guac_surface.c @@ -25,6 +25,7 @@ #include "guac_surface.h" #include +#include #include #include #include @@ -660,12 +661,14 @@ static void __guac_common_surface_transfer(guac_common_surface* src, int* sx, in } -guac_common_surface* guac_common_surface_alloc(guac_socket* socket, const guac_layer* layer, int w, int h) { +guac_common_surface* guac_common_surface_alloc(guac_client* client, + guac_socket* socket, const guac_layer* layer, int w, int h) { /* Init surface */ guac_common_surface* surface = malloc(sizeof(guac_common_surface)); - surface->layer = layer; + surface->client = client; surface->socket = socket; + surface->layer = layer; surface->width = w; surface->height = h; surface->dirty = 0; diff --git a/src/common/guac_surface.h b/src/common/guac_surface.h index b5ba89f2..cacfc2d9 100644 --- a/src/common/guac_surface.h +++ b/src/common/guac_surface.h @@ -27,6 +27,7 @@ #include "guac_rect.h" #include +#include #include #include #include @@ -65,6 +66,11 @@ typedef struct guac_common_surface { */ const guac_layer* layer; + /** + * The client associated with this surface. + */ + guac_client* client; + /** * The socket to send instructions on when flushing. */ @@ -131,13 +137,26 @@ typedef struct guac_common_surface { /** * Allocates a new guac_common_surface, assigning it to the given layer. * - * @param socket The socket to send instructions on when flushing. - * @param layer The layer to associate with the new surface. - * @param w The width of the surface. - * @param h The height of the surface. - * @return A newly-allocated guac_common_surface. + * @param client + * The client associated with the given layer. + * + * @param socket + * The socket to send instructions on when flushing. + * + * @param layer + * The layer to associate with the new surface. + * + * @param w + * The width of the surface. + * + * @param h + * The height of the surface. + * + * @return + * A newly-allocated guac_common_surface. */ -guac_common_surface* guac_common_surface_alloc(guac_socket* socket, const guac_layer* layer, int w, int h); +guac_common_surface* guac_common_surface_alloc(guac_client* client, + guac_socket* socket, const guac_layer* layer, int w, int h); /** * Frees the given guac_common_surface. Beware that this will NOT free any diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index ab2c104f..ccd6776d 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -933,8 +933,9 @@ int guac_client_init(guac_client* client, int argc, char** argv) { #endif /* Create default surface */ - guac_client_data->default_surface = guac_common_surface_alloc(client->socket, GUAC_DEFAULT_LAYER, - settings->width, settings->height); + guac_client_data->default_surface = guac_common_surface_alloc(client, + client->socket, GUAC_DEFAULT_LAYER, + settings->width, settings->height); guac_client_data->current_surface = guac_client_data->default_surface; /* Send connection name */ diff --git a/src/protocols/rdp/rdp_bitmap.c b/src/protocols/rdp/rdp_bitmap.c index a3211bf4..8de2fcc1 100644 --- a/src/protocols/rdp/rdp_bitmap.c +++ b/src/protocols/rdp/rdp_bitmap.c @@ -50,7 +50,8 @@ void guac_rdp_cache_bitmap(rdpContext* context, rdpBitmap* bitmap) { /* Allocate surface */ guac_layer* buffer = guac_client_alloc_buffer(client); - guac_common_surface* surface = guac_common_surface_alloc(socket, buffer, bitmap->width, bitmap->height); + guac_common_surface* surface = guac_common_surface_alloc(client, socket, + buffer, bitmap->width, bitmap->height); /* Cache image data if present */ if (bitmap->data != NULL) { diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index e40a62dc..201cdcd8 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -549,8 +549,9 @@ int guac_client_init(guac_client* client, int argc, char** argv) { guac_protocol_send_name(client->socket, rfb_client->desktopName); /* Create default surface */ - guac_client_data->default_surface = guac_common_surface_alloc(client->socket, GUAC_DEFAULT_LAYER, - rfb_client->width, rfb_client->height); + guac_client_data->default_surface = guac_common_surface_alloc(client, + client->socket, GUAC_DEFAULT_LAYER, + rfb_client->width, rfb_client->height); return 0; } diff --git a/src/terminal/display.c b/src/terminal/display.c index c90c2882..5a3b40f5 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -276,8 +276,8 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, /* Create default surface */ display->display_layer = guac_client_alloc_layer(client); display->select_layer = guac_client_alloc_layer(client); - display->display_surface = guac_common_surface_alloc(client->socket, - display->display_layer, 0, 0); + display->display_surface = guac_common_surface_alloc(client, + client->socket, display->display_layer, 0, 0); /* Select layer is a child of the display layer */ guac_protocol_send_move(client->socket, display->select_layer, From 8f2d6f2975b756158aa91039e8c23332f3d9602a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 16:25:52 -0700 Subject: [PATCH 3/9] GUAC-240: Force flush at end of PNG write. --- src/libguac/encode-png.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libguac/encode-png.c b/src/libguac/encode-png.c index d3632f28..516c1f6c 100644 --- a/src/libguac/encode-png.c +++ b/src/libguac/encode-png.c @@ -396,6 +396,8 @@ int guac_png_write(guac_socket* socket, guac_stream* stream, free(png_rows[y]); free(png_rows); + /* Ensure all data is written */ + guac_png_flush_data(&write_state); return 0; } From f79c66119225cd32e46d05d843db84ac00438fa7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 16:26:24 -0700 Subject: [PATCH 4/9] GUAC-240: Add function for streaming PNG images via img instruction. Use for surface. --- src/common/guac_surface.c | 3 ++- src/libguac/client.c | 22 ++++++++++++++++++++++ src/libguac/guacamole/client.h | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/common/guac_surface.c b/src/common/guac_surface.c index 3ef36666..8780c052 100644 --- a/src/common/guac_surface.c +++ b/src/common/guac_surface.c @@ -969,7 +969,8 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface) { surface->stride); /* Send PNG for rect */ - guac_protocol_send_png(socket, GUAC_COMP_OVER, layer, surface->dirty_rect.x, surface->dirty_rect.y, 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; diff --git a/src/libguac/client.c b/src/libguac/client.c index 2bb9004d..ca1c30fe 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -24,6 +24,7 @@ #include "client.h" #include "client-handlers.h" +#include "encode-png.h" #include "error.h" #include "instruction.h" #include "layer.h" @@ -375,3 +376,24 @@ void guac_client_abort(guac_client* client, guac_protocol_status status, } +void guac_client_stream_png(guac_client* client, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface) { + + /* Allocate new stream for image */ + guac_stream* stream = guac_client_alloc_stream(client); + + /* Declare stream as containing image data */ + guac_protocol_send_img(socket, stream, mode, layer, "image/png", x, y); + + /* Write PNG data */ + guac_png_write(socket, stream, surface); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_client_free_stream(client, stream); + +} + diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index 6d0cc85a..b8773c10 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -40,6 +40,7 @@ #include "stream-types.h" #include "timestamp-types.h" +#include #include struct guac_client_info { @@ -627,6 +628,39 @@ guac_object* guac_client_alloc_object(guac_client* client); */ void guac_client_free_object(guac_client* client, guac_object* object); +/** + * Streams the image data of the given surface over an image stream ("img" + * instruction) as PNG-encoded data. The image stream will be automatically + * allocated and freed. + * + * @param client + * The Guacamole client from which the image stream should be allocated. + * + * @param socket + * The socket over which instructions associated with the image stream + * should be sent. + * + * @param mode + * The composite mode to use when rendering the image over the given layer. + * + * @param layer + * The destination layer. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param surface + * A Cairo surface containing the image data to be streamed. + */ +void guac_client_stream_png(guac_client* client, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface); + /** * The default Guacamole client layer, layer 0. */ From 7aff3a257a7d5005b073e2f697d74ddfa9065886 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 20:36:57 -0700 Subject: [PATCH 5/9] GUAC-240: Move JPEG encoder to own file, writing directly to an img stream. --- src/libguac/Makefile.am | 2 + src/libguac/encode-jpeg.c | 271 ++++++++++++++++++++++++++++++++++++++ src/libguac/encode-jpeg.h | 56 ++++++++ 3 files changed, 329 insertions(+) create mode 100644 src/libguac/encode-jpeg.c create mode 100644 src/libguac/encode-jpeg.h diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index c059c822..7e0d1696 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -64,6 +64,7 @@ libguacinc_HEADERS = \ noinst_HEADERS = \ client-handlers.h \ + encode-jpeg.h \ encode-png.h \ palette.h \ wav_encoder.h @@ -72,6 +73,7 @@ libguac_la_SOURCES = \ audio.c \ client.c \ client-handlers.c \ + encode-jpeg.c \ encode-png.c \ error.c \ hash.c \ diff --git a/src/libguac/encode-jpeg.c b/src/libguac/encode-jpeg.c new file mode 100644 index 00000000..e985059d --- /dev/null +++ b/src/libguac/encode-jpeg.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "encode-jpeg.h" +#include "error.h" +#include "palette.h" +#include "protocol.h" +#include "stream.h" + +#include +#include + +#include +#include +#include +#include + +/** + * Extended version of the standard libjpeg jpeg_destination_mgr struct, which + * provides access to the pointers to the output buffer and size. The values + * of this structure will be initialized by jpeg_guac_dest(). + */ +typedef struct guac_jpeg_destination_mgr { + + /** + * Original jpeg_destination_mgr structure. This MUST be the first member + * for guac_jpeg_destination_mgr to be usable as a jpeg_destination_mgr. + */ + struct jpeg_destination_mgr parent; + + /** + * The socket over which all JPEG blobs will be written. + */ + guac_socket* socket; + + /** + * The Guacamole stream to associate with each JPEG blob. + */ + guac_stream* stream; + + /** + * The output buffer. + */ + unsigned char buffer[6048]; + +} guac_jpeg_destination_mgr; + +/** + * Initializes the destination structure of the given compression structure. + * + * @param cinfo + * The compression structure whose destination structure should be + * initialized. + */ +static void guac_jpeg_init_destination(j_compress_ptr cinfo) { + + guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; + + /* Init parent destination state */ + dest->parent.next_output_byte = dest->buffer; + dest->parent.free_in_buffer = sizeof(dest->buffer); + +} + +/** + * Flushes the current output buffer associated with the given compression + * structure, as the current output buffer is full. + * + * @param cinfo + * The compression structure whose output buffer should be flushed. + * + * @return + * TRUE, always, indicating that space is now available. FALSE is returned + * only by applications that may need additional time to empty the buffer. + */ +static boolean guac_jpeg_empty_output_buffer(j_compress_ptr cinfo) { + + guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; + + /* Write blob */ + guac_protocol_send_blob(dest->socket, dest->stream, + dest->buffer, sizeof(dest->buffer)); + + /* Update destination offset */ + dest->parent.next_output_byte = dest->buffer; + dest->parent.free_in_buffer = sizeof(dest->buffer); + + return TRUE; + +} + +/** + * Flushes the final blob of JPEG data, if any, as JPEG compression is now + * complete. + * + * @param cinfo + * The compression structure associated with the now-complete JPEG + * compression operation. + */ +static void guac_jpeg_term_destination(j_compress_ptr cinfo) { + + guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; + + /* Write final blob, if any */ + if (dest->parent.free_in_buffer != sizeof(dest->buffer)) + guac_protocol_send_blob(dest->socket, dest->stream, dest->buffer, + sizeof(dest->buffer) - dest->parent.free_in_buffer); + +} + +/** + * Configures the given compression structure to use the given Guacamole stream + * for JPEG output. + * + * @param cinfo + * The libjpeg compression structure to configure. + * + * @param socket + * The Guacamole socket to use when sending blob instructions. + * + * @param stream + * The stream over which JPEG-encoded blobs of image data should be sent. + */ +static void jpeg_guac_dest(j_compress_ptr cinfo, guac_socket* socket, + guac_stream* stream) { + + guac_jpeg_destination_mgr* dest; + + /* Allocate dest from pool if not already allocated */ + if (cinfo->dest == NULL) + cinfo->dest = (struct jpeg_destination_mgr*) + (cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(guac_jpeg_destination_mgr)); + + /* Pull possibly-new destination struct from cinfo */ + dest = (guac_jpeg_destination_mgr*) cinfo->dest; + + /* Associate destination handlers */ + dest->parent.init_destination = guac_jpeg_init_destination; + dest->parent.empty_output_buffer = guac_jpeg_empty_output_buffer; + dest->parent.term_destination = guac_jpeg_term_destination; + + /* Store Guacamole-specific objects */ + dest->socket = socket; + dest->stream = stream; + +} + +int guac_jpeg_write(guac_socket* socket, guac_stream* stream, + cairo_surface_t* surface, int quality) { + + /* Get image surface properties and data */ + cairo_format_t format = cairo_image_surface_get_format(surface); + + if (format != CAIRO_FORMAT_RGB24) { + guac_error = GUAC_STATUS_INTERNAL_ERROR; + guac_error_message = + "Invalid Cairo image format. Unable to create JPEG."; + return -1; + } + + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + int stride = cairo_image_surface_get_stride(surface); + unsigned char* data = cairo_image_surface_get_data(surface); + + /* Flush pending operations to surface */ + cairo_surface_flush(surface); + + /* Prepare JPEG bits */ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + /* Write JPEG directly to given stream */ + jpeg_guac_dest(&cinfo, socket, stream); + + cinfo.image_width = width; /* image width and height, in pixels */ + cinfo.image_height = height; + cinfo.arith_code = TRUE; + +#ifdef JCS_EXTENSIONS + /* The Turbo JPEG extentions allows us to use the Cairo surface + * (BRGx) as input without converting it */ + cinfo.input_components = 4; + cinfo.in_color_space = JCS_EXT_BGRX; +#else + /* Standard JPEG supports RGB as input so we will have to convert + * the contents of the Cairo surface from (BRGx) to RGB */ + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + /* Create a buffer for the write scan line which is where we will + * put the converted pixels (BRGx -> RGB) */ + int write_stride = cinfo.image_width * cinfo.input_components; + unsigned char *scanline_data = malloc(write_stride); + memset(scanline_data, 0, write_stride); +#endif + + /* Initialize the JPEG compressor */ + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, TRUE); + + JSAMPROW row_pointer[1]; /* pointer to a single row */ + + /* Write scanlines to be used in JPEG compression */ + while (cinfo.next_scanline < cinfo.image_height) { + + int row_offset = stride * cinfo.next_scanline; + +#ifdef JCS_EXTENSIONS + /* In Turbo JPEG we can use the raw BGRx scanline */ + row_pointer[0] = &data[row_offset]; +#else + /* For standard JPEG libraries we have to convert the + * scanline from 24 bit (4 byte) BGRx to 24 bit (3 byte) RGB */ + unsigned char *inptr = data + row_offset; + unsigned char *outptr = scanline_data; + + for (int x = 0; x < width; ++x) { + + outptr[2] = *inptr++; /* B */ + outptr[1] = *inptr++; /* G */ + outptr[0] = *inptr++; /* R */ + inptr++; /* skip the upper byte (x/A) */ + outptr += 3; + + } + + row_pointer[0] = scanline_data; +#endif + + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + +#ifndef JCS_EXTENSIONS + free(scanline_data); +#endif + + /* Finalize compression */ + jpeg_finish_compress(&cinfo); + + /* Clean up */ + jpeg_destroy_compress(&cinfo); + return 0; + +} + diff --git a/src/libguac/encode-jpeg.h b/src/libguac/encode-jpeg.h new file mode 100644 index 00000000..204d127b --- /dev/null +++ b/src/libguac/encode-jpeg.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_ENCODE_JPEG_H +#define GUAC_ENCODE_JPEG_H + +#include "config.h" + +#include "socket.h" +#include "stream.h" + +#include + +/** + * Encodes the given surface as a JPEG, and sends the resulting data over the + * given stream and socket as blobs. + * + * @param socket + * The socket to send JPEG blobs over. + * + * @param stream + * The stream to associate with each blob. + * + * @param surface + * The Cairo surface to write to the given stream and socket as JPEG blobs. + * + * @param quality + * JPEG image quality. + * + * @return + * Zero if the encoding operation is successful, non-zero otherwise. + */ +int guac_jpeg_write(guac_socket* socket, guac_stream* stream, + cairo_surface_t* surface, int quality); + +#endif + From 59f440209c88bb11fa5d9220c1eda7acf275a610 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 20:43:46 -0700 Subject: [PATCH 6/9] GUAC-240: Add function for streaming JPEG images via img instruction. --- src/libguac/client.c | 22 ++++++++++++++++++++++ src/libguac/guacamole/client.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/libguac/client.c b/src/libguac/client.c index ca1c30fe..f7747ea3 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -24,6 +24,7 @@ #include "client.h" #include "client-handlers.h" +#include "encode-jpeg.h" #include "encode-png.h" #include "error.h" #include "instruction.h" @@ -397,3 +398,24 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket, } +void guac_client_stream_jpeg(guac_client* client, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface, int quality) { + + /* Allocate new stream for image */ + guac_stream* stream = guac_client_alloc_stream(client); + + /* Declare stream as containing image data */ + guac_protocol_send_img(socket, stream, mode, layer, "image/jpeg", x, y); + + /* Write JPEG data */ + guac_jpeg_write(socket, stream, surface, quality); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_client_free_stream(client, stream); + +} + diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index b8773c10..3aabf2af 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -661,6 +661,39 @@ void guac_client_stream_png(guac_client* client, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface); +/** + * Streams the image data of the given surface over an image stream ("img" + * instruction) as JPEG-encoded data at the given quality. The image stream + * will be automatically allocated and freed. + * + * @param client + * The Guacamole client from which the image stream should be allocated. + * + * @param socket + * The socket over which instructions associated with the image stream + * should be sent. + * + * @param mode + * The composite mode to use when rendering the image over the given layer. + * + * @param layer + * The destination layer. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * within the given layer. + * + * @param surface + * A Cairo surface containing the image data to be streamed. + */ +void guac_client_stream_jpeg(guac_client* client, guac_socket* socket, + guac_composite_mode mode, const guac_layer* layer, int x, int y, + cairo_surface_t* surface, int quality); + /** * The default Guacamole client layer, layer 0. */ From 08cadba16d2f3f8896913e6133e1f5c53fb863b3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 20:46:43 -0700 Subject: [PATCH 7/9] GUAC-240: Remove guac_protocol_send_png() and guac_protocol_send_jpeg(). --- src/common/guac_dot_cursor.c | 3 +- src/common/guac_pointer_cursor.c | 3 +- src/libguac/guacamole/protocol.h | 53 --- src/libguac/protocol.c | 620 ------------------------------- src/protocols/rdp/rdp_pointer.c | 3 +- src/protocols/vnc/vnc_handlers.c | 4 +- src/terminal/ibar.c | 2 +- src/terminal/pointer.c | 2 +- 8 files changed, 10 insertions(+), 680 deletions(-) diff --git a/src/common/guac_dot_cursor.c b/src/common/guac_dot_cursor.c index 559c9d80..cf672a46 100644 --- a/src/common/guac_dot_cursor.c +++ b/src/common/guac_dot_cursor.c @@ -66,7 +66,8 @@ void guac_common_set_dot_cursor(guac_client* client) { guac_common_dot_cursor_height, guac_common_dot_cursor_stride); - guac_protocol_send_png(socket, GUAC_COMP_SRC, cursor, 0, 0, graphic); + guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor, + 0, 0, graphic); cairo_surface_destroy(graphic); /* Set cursor */ diff --git a/src/common/guac_pointer_cursor.c b/src/common/guac_pointer_cursor.c index 5c1d2d97..65fd43f2 100644 --- a/src/common/guac_pointer_cursor.c +++ b/src/common/guac_pointer_cursor.c @@ -77,7 +77,8 @@ void guac_common_set_pointer_cursor(guac_client* client) { guac_common_pointer_cursor_height, guac_common_pointer_cursor_stride); - guac_protocol_send_png(socket, GUAC_COMP_SRC, cursor, 0, 0, graphic); + guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor, + 0, 0, graphic); cairo_surface_destroy(graphic); /* Set cursor */ diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 7256e7c4..5aa7f128 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -594,59 +594,6 @@ int guac_protocol_send_lstroke(guac_socket* socket, guac_line_cap_style cap, guac_line_join_style join, int thickness, const guac_layer* srcl); -/** - * Sends a png instruction over the given guac_socket connection. The PNG image - * data given will be automatically base64-encoded for transmission. - * - * If an error occurs sending the instruction, a non-zero value is - * returned, and guac_error is set appropriately. - * - * @param socket The guac_socket connection to use. - * @param mode The composite mode to use. - * @param layer The destination layer. - * @param x The destination X coordinate. - * @param y The destination Y coordinate. - * @param surface A cairo surface containing the image data to send. - * @return Zero on success, non-zero on error. - */ -int guac_protocol_send_png(guac_socket* socket, guac_composite_mode mode, - const guac_layer* layer, int x, int y, cairo_surface_t* surface); - -/** - * Sends a jpeg instruction over the given guac_socket connection. The JPEG image - * data given will be automatically base64-encoded for transmission. - * - * If an error occurs sending the instruction, a non-zero value is - * returned, and guac_error is set appropriately. - * - * @param socket - * The guac_socket connection to use. - * - * @param mode - * The composite mode to use. - * - * @param layer - * The destination layer. - * - * @param x - * The destination X coordinate. - * - * @param y - * The destination Y coordinate. - * - * @param surface - * A cairo surface containing the image data to send. - * - * @param quality - * JPEG image quality. - * - * @return - * Zero on success, non-zero on error. - */ -int guac_protocol_send_jpeg(guac_socket* socket, guac_composite_mode mode, - const guac_layer* layer, int x, int y, cairo_surface_t* surface, - int quality); - /** * Sends an img instruction over the given guac_socket connection. * diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 62cdf03c..00aa8bbd 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -31,13 +31,7 @@ #include "stream.h" #include "unicode.h" -#include #include -#include - -#ifdef HAVE_PNGSTRUCT_H -#include -#endif #include #include @@ -75,571 +69,6 @@ ssize_t __guac_socket_write_length_double(guac_socket* socket, double d) { } -#ifndef HAVE_JPEG_MEM_DEST - -/** - * The number of bytes to allocate for the initial JPEG output buffer, if no - * buffer (or an empty buffer) is provided. - */ -#define GUAC_JPEG_DEFAULT_BUFFER_SIZE 16384 - -/** - * Extended version of the standard libjpeg jpeg_destination_mgr struct, which - * provides access to the pointers to the output buffer and size. The values - * of this structure will be initialized by jpeg_mem_dest(). - */ -typedef struct guac_jpeg_destination_mgr { - - /** - * Original jpeg_destination_mgr structure. This MUST be the first member - * for guac_jpeg_destination_mgr to be usable as a jpeg_destination_mgr. - */ - struct jpeg_destination_mgr parent; - - /** - * The current output buffer. - */ - unsigned char* buffer; - - /** - * The size of the current output buffer, in bytes. - */ - unsigned long size; - - /** - * The originally-supplied (via jpeg_mem_dest()) pointer to output buffer - * pointer. - */ - unsigned char** outbuffer; - - /** - * The originally-supplied (via jpeg_mem_dest()) pointer to the output - * buffer size, in bytes. - */ - unsigned long* outsize; - -} guac_jpeg_destination_mgr; - -/** - * Initializes the destination structure of the given compression structure. - * - * @param cinfo - * The compression structure whose destination structure should be - * initialized. - */ -static void guac_jpeg_init_destination(j_compress_ptr cinfo) { - - guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; - - /* Init parent destination state */ - dest->parent.next_output_byte = dest->buffer; - dest->parent.free_in_buffer = dest->size; - -} - -/** - * Re-allocates the output buffer associated with the given compression - * structure, as the current output buffer is full. - * - * @param cinfo - * The compression structure whose output buffer should be re-allocated. - * - * @return - * TRUE, always, indicating that space is now available. FALSE is returned - * only by applications that may need additional time to empty the buffer. - */ -static boolean guac_jpeg_empty_output_buffer(j_compress_ptr cinfo) { - - guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; - unsigned long current_offset = dest->size; - - /* Double size of current buffer */ - dest->size *= 2; - dest->buffer = realloc(dest->buffer, dest->size); - - /* Update destination offset */ - dest->parent.next_output_byte = dest->buffer + current_offset; - dest->parent.free_in_buffer = dest->size - current_offset; - - return TRUE; - -} - -/** - * Updates the given compression structure such that the originally-supplied - * output buffer size describes the size of the output image, not the overall - * size of the output buffer. This function is automatically called once JPEG - * compression ends. - * - * @param cinfo - * The compression structure associated with the now-complete JPEG - * compression operation. - */ -static void guac_jpeg_term_destination(j_compress_ptr cinfo) { - - guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; - - /* Commit current buffer and total image size to provided pointers */ - *dest->outbuffer = dest->buffer; - *dest->outsize = dest->size - dest->parent.free_in_buffer; - -} - -/** - * Configures the given compression structure to use the given buffer for JPEG - * output. If no buffer is supplied, a new buffer is allocated. This is a - * limited, internal implementation of libjpeg's jpeg_mem_dest(), which is - * unavailable in older versions of libjpeg. - * - * @param cinfo - * The libjpeg compression structure to configure. - * - * @param outbuffer - * Pointer to a pointer to the output buffer to write JPEG data to. If the - * output buffer pointer is NULL, a new output buffer will be allocated and - * assigned. - * - * @param outsize - * Pointer to the size of the output buffer, if an output buffer is - * supplied. If a new output buffer is allocated, its size will be stored - * here. When JPEG compression is complete, the total image size (NOT - * buffer size) will be stored here. - */ -static void jpeg_mem_dest(j_compress_ptr cinfo, - unsigned char** outbuffer, unsigned long* outsize) { - - guac_jpeg_destination_mgr* dest; - - /* Allocate dest from pool if not already allocated */ - if (cinfo->dest == NULL) - cinfo->dest = (struct jpeg_destination_mgr*) - (cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, - sizeof(guac_jpeg_destination_mgr)); - - /* Pull possibly-new destination struct from cinfo */ - dest = (guac_jpeg_destination_mgr*) cinfo->dest; - - /* Allocate output buffer if necessary */ - if (*outbuffer == NULL || *outsize == 0) { - dest->buffer = malloc(GUAC_JPEG_DEFAULT_BUFFER_SIZE); - dest->size = GUAC_JPEG_DEFAULT_BUFFER_SIZE; - } - - /* Otherwise, use provided buffer */ - else { - dest->buffer = *outbuffer; - dest->size = *outsize; - } - - /* Associate destination handlers */ - dest->parent.init_destination = guac_jpeg_init_destination; - dest->parent.empty_output_buffer = guac_jpeg_empty_output_buffer; - dest->parent.term_destination = guac_jpeg_term_destination; - - /* Store provided pointers */ - dest->outbuffer = outbuffer; - dest->outsize = outsize; - -} -#endif - -/** - * Converts the image data on a Cairo surface to a JPEG image and sends it - * as a base64-encoded transmission on the socket. - * - * @param socket - * The guac_socket connection to use. - * - * @param surface - *   A cairo surface containing the image data to send. - * - * @param quality - * JPEG image quality. - * - * @return - * Zero on success, non-zero on error. - */ -static int __guac_socket_write_length_jpeg(guac_socket* socket, - cairo_surface_t* surface, int quality) { - - /* Get image surface properties and data */ - cairo_format_t format = cairo_image_surface_get_format(surface); - - if (format != CAIRO_FORMAT_RGB24) { - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = "Invalid Cairo image format. Unable to create JPEG."; - return -1; - } - - int width = cairo_image_surface_get_width(surface); - int height = cairo_image_surface_get_height(surface); - int stride = cairo_image_surface_get_stride(surface); - unsigned char* data = cairo_image_surface_get_data(surface); - - /* Flush pending operations to surface */ - cairo_surface_flush(surface); - - /* Prepare JPEG bits */ - struct jpeg_compress_struct cinfo; - struct jpeg_error_mgr jerr; - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_compress(&cinfo); - - unsigned char *mem = NULL; - unsigned long mem_size = 0; - jpeg_mem_dest(&cinfo, &mem, &mem_size); - - cinfo.image_width = width; /* image width and height, in pixels */ - cinfo.image_height = height; - cinfo.arith_code = TRUE; - -#ifdef JCS_EXTENSIONS - /* The Turbo JPEG extentions allows us to use the Cairo surface - * (BRGx) as input without converting it */ - cinfo.input_components = 4; - cinfo.in_color_space = JCS_EXT_BGRX; -#else - /* Standard JPEG supports RGB as input so we will have to convert - * the contents of the Cairo surface from (BRGx) to RGB */ - cinfo.input_components = 3; - cinfo.in_color_space = JCS_RGB; - - /* Create a buffer for the write scan line which is where we will - * put the converted pixels (BRGx -> RGB) */ - int write_stride = cinfo.image_width * cinfo.input_components; - unsigned char *scanline_data = malloc(write_stride); - memset(scanline_data, 0, write_stride); -#endif - - /* Initialize the JPEG compressor */ - jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); - jpeg_start_compress(&cinfo, TRUE); - - JSAMPROW row_pointer[1]; /* pointer to a single row */ - - /* Write scanlines to be used in JPEG compression */ - while (cinfo.next_scanline < cinfo.image_height) { - - int row_offset = stride * cinfo.next_scanline; - -#ifdef JCS_EXTENSIONS - /* In Turbo JPEG we can use the raw BGRx scanline */ - row_pointer[0] = &data[row_offset]; -#else - /* For standard JPEG libraries we have to convert the - * scanline from 24 bit (4 byte) BGRx to 24 bit (3 byte) RGB */ - unsigned char *inptr = data + row_offset; - unsigned char *outptr = scanline_data; - - for (int x = 0; x < width; ++x) { - - outptr[2] = *inptr++; /* B */ - outptr[1] = *inptr++; /* G */ - outptr[0] = *inptr++; /* R */ - inptr++; /* skip the upper byte (x/A) */ - outptr += 3; - - } - - row_pointer[0] = scanline_data; -#endif - - jpeg_write_scanlines(&cinfo, row_pointer, 1); - } - -#ifndef JCS_EXTENSIONS - free(scanline_data); -#endif - - /* Finalize compression */ - jpeg_finish_compress(&cinfo); - - int base64_length = (mem_size + 2) / 3 * 4; - - /* Write length and data */ - if (guac_socket_write_int(socket, base64_length) - || guac_socket_write_string(socket, ".") - || guac_socket_write_base64(socket, mem, mem_size) - || guac_socket_flush_base64(socket)) { - - /* Something failed. Clean up and return error code. */ - jpeg_destroy_compress(&cinfo); - free(mem); - - return -1; - } - - /* Clean up */ - jpeg_destroy_compress(&cinfo); - free(mem); - - return 0; - -} - -/* PNG output formatting */ - -typedef struct __guac_socket_write_png_data { - - guac_socket* socket; - - char* buffer; - int buffer_size; - int data_size; - -} __guac_socket_write_png_data; - -cairo_status_t __guac_socket_write_png_cairo(void* closure, const unsigned char* data, unsigned int length) { - - __guac_socket_write_png_data* png_data = (__guac_socket_write_png_data*) closure; - - /* Calculate next buffer size */ - int next_size = png_data->data_size + length; - - /* If need resizing, double buffer size until big enough */ - if (next_size > png_data->buffer_size) { - - char* new_buffer; - - do { - png_data->buffer_size <<= 1; - } while (next_size > png_data->buffer_size); - - /* Resize buffer */ - new_buffer = realloc(png_data->buffer, png_data->buffer_size); - png_data->buffer = new_buffer; - - } - - /* Append data to buffer */ - memcpy(png_data->buffer + png_data->data_size, data, length); - png_data->data_size += length; - - return CAIRO_STATUS_SUCCESS; - -} - -int __guac_socket_write_length_png_cairo(guac_socket* socket, cairo_surface_t* surface) { - - __guac_socket_write_png_data png_data; - int base64_length; - - /* Write surface */ - - png_data.socket = socket; - png_data.buffer_size = 8192; - png_data.buffer = malloc(png_data.buffer_size); - png_data.data_size = 0; - - if (cairo_surface_write_to_png_stream(surface, __guac_socket_write_png_cairo, &png_data) != CAIRO_STATUS_SUCCESS) { - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = "Cairo PNG backend failed"; - return -1; - } - - base64_length = (png_data.data_size + 2) / 3 * 4; - - /* Write length and data */ - if ( - guac_socket_write_int(socket, base64_length) - || guac_socket_write_string(socket, ".") - || guac_socket_write_base64(socket, png_data.buffer, png_data.data_size) - || guac_socket_flush_base64(socket)) { - free(png_data.buffer); - return -1; - } - - free(png_data.buffer); - return 0; - -} - -void __guac_socket_write_png(png_structp png, - png_bytep data, png_size_t length) { - - /* Get png buffer structure */ - __guac_socket_write_png_data* png_data; -#ifdef HAVE_PNG_GET_IO_PTR - png_data = (__guac_socket_write_png_data*) png_get_io_ptr(png); -#else - png_data = (__guac_socket_write_png_data*) png->io_ptr; -#endif - - /* Calculate next buffer size */ - int next_size = png_data->data_size + length; - - /* If need resizing, double buffer size until big enough */ - if (next_size > png_data->buffer_size) { - - char* new_buffer; - - do { - png_data->buffer_size <<= 1; - } while (next_size > png_data->buffer_size); - - /* Resize buffer */ - new_buffer = realloc(png_data->buffer, png_data->buffer_size); - png_data->buffer = new_buffer; - - } - - /* Append data to buffer */ - memcpy(png_data->buffer + png_data->data_size, data, length); - png_data->data_size += length; - -} - -void __guac_socket_flush_png(png_structp png) { - /* Dummy function */ -} - -int __guac_socket_write_length_png(guac_socket* socket, cairo_surface_t* surface) { - - png_structp png; - png_infop png_info; - png_byte** png_rows; - int bpp; - - int x, y; - - __guac_socket_write_png_data png_data; - int base64_length; - - /* Get image surface properties and data */ - cairo_format_t format = cairo_image_surface_get_format(surface); - int width = cairo_image_surface_get_width(surface); - int height = cairo_image_surface_get_height(surface); - int stride = cairo_image_surface_get_stride(surface); - unsigned char* data = cairo_image_surface_get_data(surface); - - /* If not RGB24, use Cairo PNG writer */ - if (format != CAIRO_FORMAT_RGB24 || data == NULL) - return __guac_socket_write_length_png_cairo(socket, surface); - - /* Flush pending operations to surface */ - cairo_surface_flush(surface); - - /* Attempt to build palette */ - guac_palette* palette = guac_palette_alloc(surface); - - /* If not possible, resort to Cairo PNG writer */ - if (palette == NULL) - return __guac_socket_write_length_png_cairo(socket, surface); - - /* Calculate BPP from palette size */ - if (palette->size <= 2) bpp = 1; - else if (palette->size <= 4) bpp = 2; - else if (palette->size <= 16) bpp = 4; - else bpp = 8; - - /* Set up PNG writer */ - png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png) { - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = "libpng failed to create write structure"; - return -1; - } - - png_info = png_create_info_struct(png); - if (!png_info) { - png_destroy_write_struct(&png, NULL); - guac_error = GUAC_STATUS_INTERNAL_ERROR; - guac_error_message = "libpng failed to create info structure"; - return -1; - } - - /* Set error handler */ - if (setjmp(png_jmpbuf(png))) { - png_destroy_write_struct(&png, &png_info); - guac_error = GUAC_STATUS_IO_ERROR; - guac_error_message = "libpng output error"; - return -1; - } - - /* Set up buffer structure */ - png_data.socket = socket; - png_data.buffer_size = 8192; - png_data.buffer = malloc(png_data.buffer_size); - png_data.data_size = 0; - - /* Set up writer */ - png_set_write_fn(png, &png_data, - __guac_socket_write_png, - __guac_socket_flush_png); - - /* Copy data from surface into PNG data */ - png_rows = (png_byte**) malloc(sizeof(png_byte*) * height); - for (y=0; ycolors, palette->size); - - /* Write image */ - png_set_rows(png, png_info, png_rows); - png_write_png(png, png_info, PNG_TRANSFORM_PACKING, NULL); - - /* Finish write */ - png_destroy_write_struct(&png, &png_info); - - /* Free palette */ - guac_palette_free(palette); - - /* Free PNG data */ - for (y=0; yindex) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_int(socket, x) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_int(socket, y) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_jpeg(socket, surface, quality) - || guac_socket_write_string(socket, ";"); - - guac_socket_instruction_end(socket); - return ret_val; - -} - -int guac_protocol_send_png(guac_socket* socket, guac_composite_mode mode, - const guac_layer* layer, int x, int y, cairo_surface_t* surface) { - - int ret_val; - - guac_socket_instruction_begin(socket); - ret_val = - guac_socket_write_string(socket, "3.png,") - || __guac_socket_write_length_int(socket, mode) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_int(socket, layer->index) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_int(socket, x) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_int(socket, y) - || guac_socket_write_string(socket, ",") - || __guac_socket_write_length_png(socket, surface) - || guac_socket_write_string(socket, ";"); - - guac_socket_instruction_end(socket); - return ret_val; - -} - int guac_protocol_send_img(guac_socket* socket, const guac_stream* stream, guac_composite_mode mode, const guac_layer* layer, const char* mimetype, int x, int y) { diff --git a/src/protocols/rdp/rdp_pointer.c b/src/protocols/rdp/rdp_pointer.c index 5f9c977d..1d214df2 100644 --- a/src/protocols/rdp/rdp_pointer.c +++ b/src/protocols/rdp/rdp_pointer.c @@ -60,7 +60,8 @@ void guac_rdp_pointer_new(rdpContext* context, rdpPointer* pointer) { pointer->width, pointer->height, 4*pointer->width); /* Send surface to buffer */ - guac_protocol_send_png(socket, GUAC_COMP_SRC, buffer, 0, 0, surface); + guac_client_stream_png(client, socket, GUAC_COMP_SRC, buffer, + 0, 0, surface); /* Free surface */ cairo_surface_destroy(surface); diff --git a/src/protocols/vnc/vnc_handlers.c b/src/protocols/vnc/vnc_handlers.c index 26fdef85..7e7ccee9 100644 --- a/src/protocols/vnc/vnc_handlers.c +++ b/src/protocols/vnc/vnc_handlers.c @@ -121,8 +121,8 @@ void guac_vnc_cursor(rfbClient* client, int x, int y, int w, int h, int bpp) { /* Send cursor data*/ surface = cairo_image_surface_create_for_data(buffer, CAIRO_FORMAT_ARGB32, w, h, stride); - guac_protocol_send_png(socket, - GUAC_COMP_SRC, cursor_layer, 0, 0, surface); + guac_client_stream_png(gc, socket, GUAC_COMP_SRC, cursor_layer, + 0, 0, surface); /* Update cursor */ guac_protocol_send_cursor(socket, x, y, cursor_layer, 0, 0, w, h); diff --git a/src/terminal/ibar.c b/src/terminal/ibar.c index ecdcc5cb..7ba69c3b 100644 --- a/src/terminal/ibar.c +++ b/src/terminal/ibar.c @@ -78,7 +78,7 @@ guac_terminal_cursor* guac_terminal_create_ibar(guac_client* client) { guac_terminal_ibar_height, guac_terminal_ibar_stride); - guac_protocol_send_png(socket, GUAC_COMP_SRC, cursor->buffer, + guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor->buffer, 0, 0, graphic); cairo_surface_destroy(graphic); diff --git a/src/terminal/pointer.c b/src/terminal/pointer.c index 1b7ea272..72e99e3b 100644 --- a/src/terminal/pointer.c +++ b/src/terminal/pointer.c @@ -78,7 +78,7 @@ guac_terminal_cursor* guac_terminal_create_pointer(guac_client* client) { guac_terminal_pointer_height, guac_terminal_pointer_stride); - guac_protocol_send_png(socket, GUAC_COMP_SRC, cursor->buffer, + guac_client_stream_png(client, socket, GUAC_COMP_SRC, cursor->buffer, 0, 0, graphic); cairo_surface_destroy(graphic); From 96bae8bd9a03427f9f37c2a3ed64f083c120b914 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 12 Aug 2015 16:50:24 -0700 Subject: [PATCH 8/9] GUAC-240: No longer need to test for jpeg_mem_dest(). --- configure.ac | 7 ------- 1 file changed, 7 deletions(-) diff --git a/configure.ac b/configure.ac index 653495d7..43bb38d4 100644 --- a/configure.ac +++ b/configure.ac @@ -110,13 +110,6 @@ AC_CHECK_DECL([cairo_format_stride_for_width], [Whether cairo_format_stride_for_width() is defined])],, [#include ]) -AC_CHECK_DECL([jpeg_mem_dest], - [AC_DEFINE([HAVE_JPEG_MEM_DEST],, - [Whether jpeg_mem_dest() is defined])],, - [#include - #include - #include ]) - # Typedefs AC_TYPE_SIZE_T AC_TYPE_SSIZE_T From 3d888c2eb56da7e4143f48a2ea5b2f0296ef3c67 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 12 Aug 2015 21:08:44 -0700 Subject: [PATCH 9/9] GUAC-240: Byte order of pixels is BGR, not BRG. --- src/libguac/encode-jpeg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libguac/encode-jpeg.c b/src/libguac/encode-jpeg.c index e985059d..83f6cd8c 100644 --- a/src/libguac/encode-jpeg.c +++ b/src/libguac/encode-jpeg.c @@ -203,17 +203,17 @@ int guac_jpeg_write(guac_socket* socket, guac_stream* stream, #ifdef JCS_EXTENSIONS /* The Turbo JPEG extentions allows us to use the Cairo surface - * (BRGx) as input without converting it */ + * (BGRx) as input without converting it */ cinfo.input_components = 4; cinfo.in_color_space = JCS_EXT_BGRX; #else /* Standard JPEG supports RGB as input so we will have to convert - * the contents of the Cairo surface from (BRGx) to RGB */ + * the contents of the Cairo surface from (BGRx) to RGB */ cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; /* Create a buffer for the write scan line which is where we will - * put the converted pixels (BRGx -> RGB) */ + * put the converted pixels (BGRx -> RGB) */ int write_stride = cinfo.image_width * cinfo.input_components; unsigned char *scanline_data = malloc(write_stride); memset(scanline_data, 0, write_stride);