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_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)
|
||||||
|
@ -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@
|
||||||
|
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user