cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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