blob: 8e145d86e1c99c52b55824f38e245856be9422c8 [file] [log] [blame]
/*
* 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 <cairo/cairo.h>
#include <jpeglib.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/**
* 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;
}