GUAC-240: Implement support for JPEG server protocol and image compression.
This commit is contained in:
parent
498844b4e7
commit
c27e2997dd
@ -53,6 +53,10 @@ AC_CHECK_LIB([m], [cos],
|
||||
AC_CHECK_LIB([png], [png_write_png], [PNG_LIBS=-lpng],
|
||||
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
|
||||
AC_CHECK_LIB([cairo], [cairo_create], [CAIRO_LIBS=-lcairo],
|
||||
AC_MSG_ERROR("Cairo is required for drawing instructions"))
|
||||
@ -87,6 +91,7 @@ AC_CHECK_LIB([wsock32], [main])
|
||||
AC_SUBST(LIBADD_DLOPEN)
|
||||
AC_SUBST(MATH_LIBS)
|
||||
AC_SUBST(PNG_LIBS)
|
||||
AC_SUBST(JPEG_LIBS)
|
||||
AC_SUBST(CAIRO_LIBS)
|
||||
AC_SUBST(PTHREAD_LIBS)
|
||||
AC_SUBST(UUID_LIBS)
|
||||
|
@ -100,8 +100,8 @@ libguac_la_LDFLAGS = \
|
||||
@PNG_LIBS@ \
|
||||
@PTHREAD_LIBS@ \
|
||||
@UUID_LIBS@ \
|
||||
@VORBIS_LIBS@
|
||||
@VORBIS_LIBS@ \
|
||||
@JPEG_LIBS@
|
||||
|
||||
libguac_la_LIBADD = \
|
||||
@LIBADD_DLOPEN@
|
||||
|
||||
|
@ -612,6 +612,37 @@ int guac_protocol_send_lstroke(guac_socket* socket,
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
|
@ -33,6 +33,7 @@
|
||||
|
||||
#include <png.h>
|
||||
#include <cairo/cairo.h>
|
||||
#include <jpeglib.h>
|
||||
|
||||
#ifdef HAVE_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 */
|
||||
|
||||
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,
|
||||
const guac_layer* layer, int x, int y, cairo_surface_t* surface) {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user