encode-jpeg.c (8279B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21 22#include "encode-jpeg.h" 23#include "guacamole/mem.h" 24#include "guacamole/error.h" 25#include "guacamole/protocol.h" 26#include "guacamole/stream.h" 27#include "palette.h" 28 29#include <cairo/cairo.h> 30#include <jpeglib.h> 31 32#include <inttypes.h> 33#include <stdint.h> 34#include <stdlib.h> 35#include <string.h> 36 37/** 38 * Extended version of the standard libjpeg jpeg_destination_mgr struct, which 39 * provides access to the pointers to the output buffer and size. The values 40 * of this structure will be initialized by jpeg_guac_dest(). 41 */ 42typedef struct guac_jpeg_destination_mgr { 43 44 /** 45 * Original jpeg_destination_mgr structure. This MUST be the first member 46 * for guac_jpeg_destination_mgr to be usable as a jpeg_destination_mgr. 47 */ 48 struct jpeg_destination_mgr parent; 49 50 /** 51 * The socket over which all JPEG blobs will be written. 52 */ 53 guac_socket* socket; 54 55 /** 56 * The Guacamole stream to associate with each JPEG blob. 57 */ 58 guac_stream* stream; 59 60 /** 61 * The output buffer. 62 */ 63 unsigned char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH]; 64 65} guac_jpeg_destination_mgr; 66 67/** 68 * Initializes the destination structure of the given compression structure. 69 * 70 * @param cinfo 71 * The compression structure whose destination structure should be 72 * initialized. 73 */ 74static void guac_jpeg_init_destination(j_compress_ptr cinfo) { 75 76 guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; 77 78 /* Init parent destination state */ 79 dest->parent.next_output_byte = dest->buffer; 80 dest->parent.free_in_buffer = sizeof(dest->buffer); 81 82} 83 84/** 85 * Flushes the current output buffer associated with the given compression 86 * structure, as the current output buffer is full. 87 * 88 * @param cinfo 89 * The compression structure whose output buffer should be flushed. 90 * 91 * @return 92 * TRUE, always, indicating that space is now available. FALSE is returned 93 * only by applications that may need additional time to empty the buffer. 94 */ 95static boolean guac_jpeg_empty_output_buffer(j_compress_ptr cinfo) { 96 97 guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; 98 99 /* Write blob */ 100 guac_protocol_send_blob(dest->socket, dest->stream, 101 dest->buffer, sizeof(dest->buffer)); 102 103 /* Update destination offset */ 104 dest->parent.next_output_byte = dest->buffer; 105 dest->parent.free_in_buffer = sizeof(dest->buffer); 106 107 return TRUE; 108 109} 110 111/** 112 * Flushes the final blob of JPEG data, if any, as JPEG compression is now 113 * complete. 114 * 115 * @param cinfo 116 * The compression structure associated with the now-complete JPEG 117 * compression operation. 118 */ 119static void guac_jpeg_term_destination(j_compress_ptr cinfo) { 120 121 guac_jpeg_destination_mgr* dest = (guac_jpeg_destination_mgr*) cinfo->dest; 122 123 /* Write final blob, if any */ 124 if (dest->parent.free_in_buffer != sizeof(dest->buffer)) 125 guac_protocol_send_blob(dest->socket, dest->stream, dest->buffer, 126 sizeof(dest->buffer) - dest->parent.free_in_buffer); 127 128} 129 130/** 131 * Configures the given compression structure to use the given Guacamole stream 132 * for JPEG output. 133 * 134 * @param cinfo 135 * The libjpeg compression structure to configure. 136 * 137 * @param socket 138 * The Guacamole socket to use when sending blob instructions. 139 * 140 * @param stream 141 * The stream over which JPEG-encoded blobs of image data should be sent. 142 */ 143static void jpeg_guac_dest(j_compress_ptr cinfo, guac_socket* socket, 144 guac_stream* stream) { 145 146 guac_jpeg_destination_mgr* dest; 147 148 /* Allocate dest from pool if not already allocated */ 149 if (cinfo->dest == NULL) 150 cinfo->dest = (struct jpeg_destination_mgr*) 151 (cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, 152 sizeof(guac_jpeg_destination_mgr)); 153 154 /* Pull possibly-new destination struct from cinfo */ 155 dest = (guac_jpeg_destination_mgr*) cinfo->dest; 156 157 /* Associate destination handlers */ 158 dest->parent.init_destination = guac_jpeg_init_destination; 159 dest->parent.empty_output_buffer = guac_jpeg_empty_output_buffer; 160 dest->parent.term_destination = guac_jpeg_term_destination; 161 162 /* Store Guacamole-specific objects */ 163 dest->socket = socket; 164 dest->stream = stream; 165 166} 167 168int guac_jpeg_write(guac_socket* socket, guac_stream* stream, 169 cairo_surface_t* surface, int quality) { 170 171 /* Get image surface properties and data */ 172 cairo_format_t format = cairo_image_surface_get_format(surface); 173 174 if (format != CAIRO_FORMAT_RGB24) { 175 guac_error = GUAC_STATUS_INTERNAL_ERROR; 176 guac_error_message = 177 "Invalid Cairo image format. Unable to create JPEG."; 178 return -1; 179 } 180 181 int width = cairo_image_surface_get_width(surface); 182 int height = cairo_image_surface_get_height(surface); 183 int stride = cairo_image_surface_get_stride(surface); 184 unsigned char* data = cairo_image_surface_get_data(surface); 185 186 /* Flush pending operations to surface */ 187 cairo_surface_flush(surface); 188 189 /* Prepare JPEG bits */ 190 struct jpeg_compress_struct cinfo; 191 struct jpeg_error_mgr jerr; 192 cinfo.err = jpeg_std_error(&jerr); 193 jpeg_create_compress(&cinfo); 194 195 /* Write JPEG directly to given stream */ 196 jpeg_guac_dest(&cinfo, socket, stream); 197 198 cinfo.image_width = width; /* image width and height, in pixels */ 199 cinfo.image_height = height; 200 cinfo.arith_code = TRUE; 201 202#ifdef JCS_EXTENSIONS 203 /* The Turbo JPEG extentions allows us to use the Cairo surface 204 * (BGRx) as input without converting it */ 205 cinfo.input_components = 4; 206 cinfo.in_color_space = JCS_EXT_BGRX; 207#else 208 /* Standard JPEG supports RGB as input so we will have to convert 209 * the contents of the Cairo surface from (BGRx) to RGB */ 210 cinfo.input_components = 3; 211 cinfo.in_color_space = JCS_RGB; 212 213 /* Create a buffer for the write scan line which is where we will 214 * put the converted pixels (BGRx -> RGB) */ 215 unsigned char *scanline_data = guac_mem_zalloc(cinfo.image_width, cinfo.input_components); 216#endif 217 218 /* Initialize the JPEG compressor */ 219 jpeg_set_defaults(&cinfo); 220 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); 221 jpeg_start_compress(&cinfo, TRUE); 222 223 JSAMPROW row_pointer[1]; /* pointer to a single row */ 224 225 /* Write scanlines to be used in JPEG compression */ 226 while (cinfo.next_scanline < cinfo.image_height) { 227 228 int row_offset = stride * cinfo.next_scanline; 229 230#ifdef JCS_EXTENSIONS 231 /* In Turbo JPEG we can use the raw BGRx scanline */ 232 row_pointer[0] = &data[row_offset]; 233#else 234 /* For standard JPEG libraries we have to convert the 235 * scanline from 24 bit (4 byte) BGRx to 24 bit (3 byte) RGB */ 236 unsigned char *inptr = data + row_offset; 237 unsigned char *outptr = scanline_data; 238 239 for (int x = 0; x < width; ++x) { 240 241 outptr[2] = *inptr++; /* B */ 242 outptr[1] = *inptr++; /* G */ 243 outptr[0] = *inptr++; /* R */ 244 inptr++; /* skip the upper byte (x/A) */ 245 outptr += 3; 246 247 } 248 249 row_pointer[0] = scanline_data; 250#endif 251 252 jpeg_write_scanlines(&cinfo, row_pointer, 1); 253 } 254 255#ifndef JCS_EXTENSIONS 256 guac_mem_free(scanline_data); 257#endif 258 259 /* Finalize compression */ 260 jpeg_finish_compress(&cinfo); 261 262 /* Clean up */ 263 jpeg_destroy_compress(&cinfo); 264 return 0; 265 266} 267