cscg24-guacamole

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

encode-png.c (11600B)


      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-png.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 <png.h>
     30#include <cairo/cairo.h>
     31
     32#ifdef HAVE_PNGSTRUCT_H
     33#include <pngstruct.h>
     34#endif
     35
     36#include <inttypes.h>
     37#include <setjmp.h>
     38#include <stdint.h>
     39#include <stdlib.h>
     40#include <string.h>
     41
     42/**
     43 * Data describing the current write state of PNG data.
     44 */
     45typedef struct guac_png_write_state {
     46
     47    /**
     48     * The socket over which all PNG blobs will be written.
     49     */
     50    guac_socket* socket;
     51
     52    /**
     53     * The Guacamole stream to associate with each PNG blob.
     54     */
     55    guac_stream* stream;
     56
     57    /**
     58     * Buffer of pending PNG data.
     59     */
     60    char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH];
     61
     62    /**
     63     * The number of bytes currently stored in the buffer.
     64     */
     65    int buffer_size;
     66
     67} guac_png_write_state;
     68
     69/**
     70 * Writes the contents of the PNG write state as a blob to its associated
     71 * socket.
     72 *
     73 * @param write_state
     74 *     The write state to flush.
     75 */
     76static void guac_png_flush_data(guac_png_write_state* write_state) {
     77
     78    /* Send blob */
     79    guac_protocol_send_blob(write_state->socket, write_state->stream,
     80            write_state->buffer, write_state->buffer_size);
     81
     82    /* Clear buffer */
     83    write_state->buffer_size = 0;
     84
     85}
     86
     87/**
     88 * Appends the given PNG data to the internal buffer of the given write state,
     89 * automatically flushing the write state as necessary.
     90 * socket.
     91 *
     92 * @param write_state
     93 *     The write state to append the given data to.
     94 *
     95 * @param data
     96 *     The PNG data to append.
     97 *
     98 * @param length
     99 *     The size of the given PNG data, in bytes.
    100 */
    101static void guac_png_write_data(guac_png_write_state* write_state,
    102        const void* data, int length) {
    103
    104    const unsigned char* current = data;
    105
    106    /* Append all data given */
    107    while (length > 0) {
    108
    109        /* Calculate space remaining */
    110        int remaining = sizeof(write_state->buffer) - write_state->buffer_size;
    111
    112        /* If no space remains, flush buffer to make room */
    113        if (remaining == 0) {
    114            guac_png_flush_data(write_state);
    115            remaining = sizeof(write_state->buffer);
    116        }
    117
    118        /* Calculate size of next block of data to append */
    119        int block_size = remaining;
    120        if (block_size > length)
    121            block_size = length;
    122
    123        /* Append block */
    124        memcpy(write_state->buffer + write_state->buffer_size,
    125                current, block_size);
    126
    127        /* Next block */
    128        current += block_size;
    129        write_state->buffer_size += block_size;
    130        length -= block_size;
    131
    132    }
    133
    134}
    135
    136/**
    137 * Writes the given buffer of PNG data to the buffer of the given write state,
    138 * flushing that buffer to blob instructions if necessary. This handler is
    139 * called by Cairo when writing PNG data via
    140 * cairo_surface_write_to_png_stream().
    141 *
    142 * @param closure
    143 *     Pointer to arbitrary data passed to cairo_surface_write_to_png_stream().
    144 *     In the case of this handler, this data will be the guac_png_write_state.
    145 *
    146 * @param data
    147 *     The buffer of PNG data to write.
    148 * 
    149 * @param length
    150 *     The size of the given buffer, in bytes.
    151 *
    152 * @return
    153 *     A Cairo status code indicating whether the write operation succeeded.
    154 *     In the case of this handler, this will always be CAIRO_STATUS_SUCCESS.
    155 */
    156static cairo_status_t guac_png_cairo_write_handler(void* closure,
    157        const unsigned char* data, unsigned int length) {
    158
    159    guac_png_write_state* write_state = (guac_png_write_state*) closure;
    160
    161    /* Append data to buffer, writing as necessary */
    162    guac_png_write_data(write_state, data, length);
    163
    164    return CAIRO_STATUS_SUCCESS;
    165
    166}
    167
    168/**
    169 * Implementation of guac_png_write() which uses Cairo's own PNG encoder to
    170 * write PNG data, rather than using libpng directly.
    171 *
    172 * @param socket
    173 *     The socket to send PNG blobs over.
    174 *
    175 * @param stream
    176 *     The stream to associate with each blob.
    177 *
    178 * @param surface
    179 *     The Cairo surface to write to the given stream and socket as PNG blobs.
    180 *
    181 * @return
    182 *     Zero if the encoding operation is successful, non-zero otherwise.
    183 */
    184static int guac_png_cairo_write(guac_socket* socket, guac_stream* stream,
    185        cairo_surface_t* surface) {
    186
    187    guac_png_write_state write_state;
    188
    189    /* Init write state */
    190    write_state.socket = socket;
    191    write_state.stream = stream;
    192    write_state.buffer_size = 0;
    193
    194    /* Write surface as PNG */
    195    if (cairo_surface_write_to_png_stream(surface,
    196                guac_png_cairo_write_handler,
    197                &write_state) != CAIRO_STATUS_SUCCESS) {
    198        guac_error = GUAC_STATUS_INTERNAL_ERROR;
    199        guac_error_message = "Cairo PNG backend failed";
    200        return -1;
    201    }
    202
    203    /* Flush remaining PNG data */
    204    guac_png_flush_data(&write_state);
    205    return 0;
    206
    207}
    208
    209/**
    210 * Writes the given buffer of PNG data to the buffer of the given write state,
    211 * flushing that buffer to blob instructions if necessary. This handler is
    212 * called by libpng when writing PNG data via png_write_png().
    213 *
    214 * @param png
    215 *     The PNG compression state structure associated with the write operation.
    216 *     The pointer to arbitrary data will have been set to the
    217 *     guac_png_write_state by png_set_write_fn(), and will be accessible via
    218 *     png->io_ptr or png_get_io_ptr(png), depending on the version of libpng.
    219 *
    220 * @param data
    221 *     The buffer of PNG data to write.
    222 * 
    223 * @param length
    224 *     The size of the given buffer, in bytes.
    225 */
    226static void guac_png_write_handler(png_structp png, png_bytep data,
    227        png_size_t length) {
    228
    229    /* Get png buffer structure */
    230    guac_png_write_state* write_state;
    231#ifdef HAVE_PNG_GET_IO_PTR
    232    write_state = (guac_png_write_state*) png_get_io_ptr(png);
    233#else
    234    write_state = (guac_png_write_state*) png->io_ptr;
    235#endif
    236
    237    /* Append data to buffer, writing as necessary */
    238    guac_png_write_data(write_state, data, length);
    239
    240}
    241
    242/**
    243 * Flushes any PNG data within the buffer of the given write state as a blob
    244 * instruction. If no data is within the buffer, this function has no effect.
    245 * This handler is called by libpng when it has finished writing PNG data via
    246 * png_write_png().
    247 *
    248 * @param png
    249 *     The PNG compression state structure associated with the write operation.
    250 *     The pointer to arbitrary data will have been set to the
    251 *     guac_png_write_state by png_set_write_fn(), and will be accessible via
    252 *     png->io_ptr or png_get_io_ptr(png), depending on the version of libpng.
    253 */
    254static void guac_png_flush_handler(png_structp png) {
    255
    256    /* Get png buffer structure */
    257    guac_png_write_state* write_state;
    258#ifdef HAVE_PNG_GET_IO_PTR
    259    write_state = (guac_png_write_state*) png_get_io_ptr(png);
    260#else
    261    write_state = (guac_png_write_state*) png->io_ptr;
    262#endif
    263
    264    /* Flush buffer */
    265    guac_png_flush_data(write_state);
    266
    267}
    268
    269int guac_png_write(guac_socket* socket, guac_stream* stream,
    270        cairo_surface_t* surface) {
    271
    272    png_structp png;
    273    png_infop png_info;
    274    png_byte** png_rows;
    275    int bpp;
    276
    277    int x, y;
    278
    279    guac_png_write_state write_state;
    280
    281    /* Get image surface properties and data */
    282    cairo_format_t format = cairo_image_surface_get_format(surface);
    283    int width = cairo_image_surface_get_width(surface);
    284    int height = cairo_image_surface_get_height(surface);
    285    int stride = cairo_image_surface_get_stride(surface);
    286    unsigned char* data = cairo_image_surface_get_data(surface);
    287
    288    /* If not RGB24, use Cairo PNG writer */
    289    if (format != CAIRO_FORMAT_RGB24 || data == NULL)
    290        return guac_png_cairo_write(socket, stream, surface);
    291
    292    /* Flush pending operations to surface */
    293    cairo_surface_flush(surface);
    294
    295    /* Attempt to build palette */
    296    guac_palette* palette = guac_palette_alloc(surface);
    297
    298    /* If not possible, resort to Cairo PNG writer */
    299    if (palette == NULL)
    300        return guac_png_cairo_write(socket, stream, surface);
    301
    302    /* Calculate BPP from palette size */
    303    if      (palette->size <= 2)  bpp = 1;
    304    else if (palette->size <= 4)  bpp = 2;
    305    else if (palette->size <= 16) bpp = 4;
    306    else                          bpp = 8;
    307
    308    /* Set up PNG writer */
    309    png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    310    if (!png) {
    311        guac_palette_free(palette);
    312        guac_error = GUAC_STATUS_INTERNAL_ERROR;
    313        guac_error_message = "libpng failed to create write structure";
    314        return -1;
    315    }
    316
    317    png_info = png_create_info_struct(png);
    318    if (!png_info) {
    319        png_destroy_write_struct(&png, NULL);
    320        guac_palette_free(palette);
    321        guac_error = GUAC_STATUS_INTERNAL_ERROR;
    322        guac_error_message = "libpng failed to create info structure";
    323        return -1;
    324    }
    325
    326    /* Set error handler */
    327    if (setjmp(png_jmpbuf(png))) {
    328        png_destroy_write_struct(&png, &png_info);
    329        guac_palette_free(palette);
    330        guac_error = GUAC_STATUS_IO_ERROR;
    331        guac_error_message = "libpng output error";
    332        return -1;
    333    }
    334
    335    /* Init write state */
    336    write_state.socket = socket;
    337    write_state.stream = stream;
    338    write_state.buffer_size = 0;
    339
    340    /* Set up writer */
    341    png_set_write_fn(png, &write_state,
    342            guac_png_write_handler,
    343            guac_png_flush_handler);
    344
    345    /* Copy data from surface into PNG data */
    346    png_rows = (png_byte**) guac_mem_alloc(sizeof(png_byte*), height);
    347    for (y=0; y<height; y++) {
    348
    349        /* Allocate new PNG row */
    350        png_byte* row = (png_byte*) guac_mem_alloc(sizeof(png_byte), width);
    351        png_rows[y] = row;
    352
    353        /* Copy data from surface into current row */
    354        for (x=0; x<width; x++) {
    355
    356            /* Get pixel color */
    357            int color = ((uint32_t*) data)[x] & 0xFFFFFF;
    358
    359            /* Set index in row */
    360            row[x] = guac_palette_find(palette, color);
    361
    362        }
    363
    364        /* Advance to next data row */
    365        data += stride;
    366
    367    }
    368
    369    /* Write image info */
    370    png_set_IHDR(
    371        png,
    372        png_info,
    373        width,
    374        height,
    375        bpp,
    376        PNG_COLOR_TYPE_PALETTE,
    377        PNG_INTERLACE_NONE,
    378        PNG_COMPRESSION_TYPE_DEFAULT,
    379        PNG_FILTER_TYPE_DEFAULT
    380    );
    381
    382    /* Write palette */
    383    png_set_PLTE(png, png_info, palette->colors, palette->size);
    384
    385    /* Write image */
    386    png_set_rows(png, png_info, png_rows);
    387    png_write_png(png, png_info, PNG_TRANSFORM_PACKING, NULL);
    388
    389    /* Finish write */
    390    png_destroy_write_struct(&png, &png_info);
    391
    392    /* Free palette */
    393    guac_palette_free(palette);
    394
    395    /* Free PNG data */
    396    for (y=0; y<height; y++)
    397        guac_mem_free(png_rows[y]);
    398    guac_mem_free(png_rows);
    399
    400    /* Ensure all data is written */
    401    guac_png_flush_data(&write_state);
    402    return 0;
    403
    404}
    405