GUAC-240: Implement support for JPEG server protocol and image compression.

This commit is contained in:
Frode Langelo 2015-07-31 15:05:52 -07:00 committed by Michael Jumper
parent 498844b4e7
commit c27e2997dd
4 changed files with 195 additions and 2 deletions

View File

@ -53,6 +53,10 @@ AC_CHECK_LIB([m], [cos],
AC_CHECK_LIB([png], [png_write_png], [PNG_LIBS=-lpng], AC_CHECK_LIB([png], [png_write_png], [PNG_LIBS=-lpng],
AC_MSG_ERROR("libpng is required for writing png messages")) AC_MSG_ERROR("libpng is required for writing png messages"))
# libjpeg
AC_CHECK_LIB([jpeg], [jpeg_start_compress], [JPEG_LIBS=-ljpeg],
AC_MSG_ERROR("libjpeg is required for writing jpeg messages"))
# Cairo # Cairo
AC_CHECK_LIB([cairo], [cairo_create], [CAIRO_LIBS=-lcairo], AC_CHECK_LIB([cairo], [cairo_create], [CAIRO_LIBS=-lcairo],
AC_MSG_ERROR("Cairo is required for drawing instructions")) AC_MSG_ERROR("Cairo is required for drawing instructions"))
@ -87,6 +91,7 @@ AC_CHECK_LIB([wsock32], [main])
AC_SUBST(LIBADD_DLOPEN) AC_SUBST(LIBADD_DLOPEN)
AC_SUBST(MATH_LIBS) AC_SUBST(MATH_LIBS)
AC_SUBST(PNG_LIBS) AC_SUBST(PNG_LIBS)
AC_SUBST(JPEG_LIBS)
AC_SUBST(CAIRO_LIBS) AC_SUBST(CAIRO_LIBS)
AC_SUBST(PTHREAD_LIBS) AC_SUBST(PTHREAD_LIBS)
AC_SUBST(UUID_LIBS) AC_SUBST(UUID_LIBS)

View File

@ -100,8 +100,8 @@ libguac_la_LDFLAGS = \
@PNG_LIBS@ \ @PNG_LIBS@ \
@PTHREAD_LIBS@ \ @PTHREAD_LIBS@ \
@UUID_LIBS@ \ @UUID_LIBS@ \
@VORBIS_LIBS@ @VORBIS_LIBS@ \
@JPEG_LIBS@
libguac_la_LIBADD = \ libguac_la_LIBADD = \
@LIBADD_DLOPEN@ @LIBADD_DLOPEN@

View File

@ -612,6 +612,37 @@ int guac_protocol_send_lstroke(guac_socket* socket,
int guac_protocol_send_png(guac_socket* socket, guac_composite_mode mode, int guac_protocol_send_png(guac_socket* socket, guac_composite_mode mode,
const guac_layer* layer, int x, int y, cairo_surface_t* surface); 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.
*
* @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);
/** /**
* Sends a pop instruction over the given guac_socket connection. * Sends a pop instruction over the given guac_socket connection.
* *

View File

@ -33,6 +33,7 @@
#include <png.h> #include <png.h>
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <jpeglib.h>
#ifdef HAVE_PNGSTRUCT_H #ifdef HAVE_PNGSTRUCT_H
#include <pngstruct.h> #include <pngstruct.h>
@ -74,6 +75,138 @@ ssize_t __guac_socket_write_length_double(guac_socket* socket, double d) {
} }
/**
* 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.
*
* @return
* Zero on success, non-zero on error.
*/
static int __guac_socket_write_length_jpeg(guac_socket* socket, cairo_surface_t* surface) {
/* 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 quality = 80; /* JPEG compression quality setting */
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 JOEG 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 */ /* PNG output formatting */
typedef struct __guac_socket_write_png_data { typedef struct __guac_socket_write_png_data {
@ -1047,6 +1180,30 @@ int guac_protocol_send_pipe(guac_socket* socket, const guac_stream* stream,
} }
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 ret_val;
guac_socket_instruction_begin(socket);
ret_val =
guac_socket_write_string(socket, "4.jpeg,")
|| __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_jpeg(socket, surface)
|| 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, int guac_protocol_send_png(guac_socket* socket, guac_composite_mode mode,
const guac_layer* layer, int x, int y, cairo_surface_t* surface) { const guac_layer* layer, int x, int y, cairo_surface_t* surface) {