GUAC-240: Move JPEG encoder to own file, writing directly to an img stream.
This commit is contained in:
parent
f79c661192
commit
7aff3a257a
@ -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 \
|
||||
|
271
src/libguac/encode-jpeg.c
Normal file
271
src/libguac/encode-jpeg.c
Normal file
@ -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 <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[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;
|
||||
|
||||
}
|
||||
|
56
src/libguac/encode-jpeg.h
Normal file
56
src/libguac/encode-jpeg.h
Normal file
@ -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 <cairo/cairo.h>
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
Loading…
Reference in New Issue
Block a user