/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "config.h" #include "encode-jpeg.h" #include "guacamole/error.h" #include "guacamole/protocol.h" #include "guacamole/stream.h" #include "palette.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[GUAC_PROTOCOL_BLOB_MAX_LENGTH]; } 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 * (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 (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 (BGRx -> 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; }