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 +