| /* |
| * 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-png.h" |
| #include "guacamole/error.h" |
| #include "guacamole/protocol.h" |
| #include "guacamole/stream.h" |
| #include "palette.h" |
| |
| #include <png.h> |
| #include <cairo/cairo.h> |
| |
| #ifdef HAVE_PNGSTRUCT_H |
| #include <pngstruct.h> |
| #endif |
| |
| #include <inttypes.h> |
| #include <setjmp.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| /** |
| * Data describing the current write state of PNG data. |
| */ |
| typedef struct guac_png_write_state { |
| |
| /** |
| * The socket over which all PNG blobs will be written. |
| */ |
| guac_socket* socket; |
| |
| /** |
| * The Guacamole stream to associate with each PNG blob. |
| */ |
| guac_stream* stream; |
| |
| /** |
| * Buffer of pending PNG data. |
| */ |
| char buffer[6048]; |
| |
| /** |
| * The number of bytes currently stored in the buffer. |
| */ |
| int buffer_size; |
| |
| } guac_png_write_state; |
| |
| /** |
| * Writes the contents of the PNG write state as a blob to its associated |
| * socket. |
| * |
| * @param write_state |
| * The write state to flush. |
| */ |
| static void guac_png_flush_data(guac_png_write_state* write_state) { |
| |
| /* Send blob */ |
| guac_protocol_send_blob(write_state->socket, write_state->stream, |
| write_state->buffer, write_state->buffer_size); |
| |
| /* Clear buffer */ |
| write_state->buffer_size = 0; |
| |
| } |
| |
| /** |
| * Appends the given PNG data to the internal buffer of the given write state, |
| * automatically flushing the write state as necessary. |
| * socket. |
| * |
| * @param write_state |
| * The write state to append the given data to. |
| * |
| * @param data |
| * The PNG data to append. |
| * |
| * @param length |
| * The size of the given PNG data, in bytes. |
| */ |
| static void guac_png_write_data(guac_png_write_state* write_state, |
| const void* data, int length) { |
| |
| const unsigned char* current = data; |
| |
| /* Append all data given */ |
| while (length > 0) { |
| |
| /* Calculate space remaining */ |
| int remaining = sizeof(write_state->buffer) - write_state->buffer_size; |
| |
| /* If no space remains, flush buffer to make room */ |
| if (remaining == 0) { |
| guac_png_flush_data(write_state); |
| remaining = sizeof(write_state->buffer); |
| } |
| |
| /* Calculate size of next block of data to append */ |
| int block_size = remaining; |
| if (block_size > length) |
| block_size = length; |
| |
| /* Append block */ |
| memcpy(write_state->buffer + write_state->buffer_size, |
| current, block_size); |
| |
| /* Next block */ |
| current += block_size; |
| write_state->buffer_size += block_size; |
| length -= block_size; |
| |
| } |
| |
| } |
| |
| /** |
| * Writes the given buffer of PNG data to the buffer of the given write state, |
| * flushing that buffer to blob instructions if necessary. This handler is |
| * called by Cairo when writing PNG data via |
| * cairo_surface_write_to_png_stream(). |
| * |
| * @param closure |
| * Pointer to arbitrary data passed to cairo_surface_write_to_png_stream(). |
| * In the case of this handler, this data will be the guac_png_write_state. |
| * |
| * @param data |
| * The buffer of PNG data to write. |
| * |
| * @param length |
| * The size of the given buffer, in bytes. |
| * |
| * @return |
| * A Cairo status code indicating whether the write operation succeeded. |
| * In the case of this handler, this will always be CAIRO_STATUS_SUCCESS. |
| */ |
| static cairo_status_t guac_png_cairo_write_handler(void* closure, |
| const unsigned char* data, unsigned int length) { |
| |
| guac_png_write_state* write_state = (guac_png_write_state*) closure; |
| |
| /* Append data to buffer, writing as necessary */ |
| guac_png_write_data(write_state, data, length); |
| |
| return CAIRO_STATUS_SUCCESS; |
| |
| } |
| |
| /** |
| * Implementation of guac_png_write() which uses Cairo's own PNG encoder to |
| * write PNG data, rather than using libpng directly. |
| * |
| * @param socket |
| * The socket to send PNG 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 PNG blobs. |
| * |
| * @return |
| * Zero if the encoding operation is successful, non-zero otherwise. |
| */ |
| static int guac_png_cairo_write(guac_socket* socket, guac_stream* stream, |
| cairo_surface_t* surface) { |
| |
| guac_png_write_state write_state; |
| |
| /* Init write state */ |
| write_state.socket = socket; |
| write_state.stream = stream; |
| write_state.buffer_size = 0; |
| |
| /* Write surface as PNG */ |
| if (cairo_surface_write_to_png_stream(surface, |
| guac_png_cairo_write_handler, |
| &write_state) != CAIRO_STATUS_SUCCESS) { |
| guac_error = GUAC_STATUS_INTERNAL_ERROR; |
| guac_error_message = "Cairo PNG backend failed"; |
| return -1; |
| } |
| |
| /* Flush remaining PNG data */ |
| guac_png_flush_data(&write_state); |
| return 0; |
| |
| } |
| |
| /** |
| * Writes the given buffer of PNG data to the buffer of the given write state, |
| * flushing that buffer to blob instructions if necessary. This handler is |
| * called by libpng when writing PNG data via png_write_png(). |
| * |
| * @param png |
| * The PNG compression state structure associated with the write operation. |
| * The pointer to arbitrary data will have been set to the |
| * guac_png_write_state by png_set_write_fn(), and will be accessible via |
| * png->io_ptr or png_get_io_ptr(png), depending on the version of libpng. |
| * |
| * @param data |
| * The buffer of PNG data to write. |
| * |
| * @param length |
| * The size of the given buffer, in bytes. |
| */ |
| static void guac_png_write_handler(png_structp png, png_bytep data, |
| png_size_t length) { |
| |
| /* Get png buffer structure */ |
| guac_png_write_state* write_state; |
| #ifdef HAVE_PNG_GET_IO_PTR |
| write_state = (guac_png_write_state*) png_get_io_ptr(png); |
| #else |
| write_state = (guac_png_write_state*) png->io_ptr; |
| #endif |
| |
| /* Append data to buffer, writing as necessary */ |
| guac_png_write_data(write_state, data, length); |
| |
| } |
| |
| /** |
| * Flushes any PNG data within the buffer of the given write state as a blob |
| * instruction. If no data is within the buffer, this function has no effect. |
| * This handler is called by libpng when it has finished writing PNG data via |
| * png_write_png(). |
| * |
| * @param png |
| * The PNG compression state structure associated with the write operation. |
| * The pointer to arbitrary data will have been set to the |
| * guac_png_write_state by png_set_write_fn(), and will be accessible via |
| * png->io_ptr or png_get_io_ptr(png), depending on the version of libpng. |
| */ |
| static void guac_png_flush_handler(png_structp png) { |
| |
| /* Get png buffer structure */ |
| guac_png_write_state* write_state; |
| #ifdef HAVE_PNG_GET_IO_PTR |
| write_state = (guac_png_write_state*) png_get_io_ptr(png); |
| #else |
| write_state = (guac_png_write_state*) png->io_ptr; |
| #endif |
| |
| /* Flush buffer */ |
| guac_png_flush_data(write_state); |
| |
| } |
| |
| int guac_png_write(guac_socket* socket, guac_stream* stream, |
| cairo_surface_t* surface) { |
| |
| png_structp png; |
| png_infop png_info; |
| png_byte** png_rows; |
| int bpp; |
| |
| int x, y; |
| |
| guac_png_write_state write_state; |
| |
| /* Get image surface properties and data */ |
| cairo_format_t format = cairo_image_surface_get_format(surface); |
| 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); |
| |
| /* If not RGB24, use Cairo PNG writer */ |
| if (format != CAIRO_FORMAT_RGB24 || data == NULL) |
| return guac_png_cairo_write(socket, stream, surface); |
| |
| /* Flush pending operations to surface */ |
| cairo_surface_flush(surface); |
| |
| /* Attempt to build palette */ |
| guac_palette* palette = guac_palette_alloc(surface); |
| |
| /* If not possible, resort to Cairo PNG writer */ |
| if (palette == NULL) |
| return guac_png_cairo_write(socket, stream, surface); |
| |
| /* Calculate BPP from palette size */ |
| if (palette->size <= 2) bpp = 1; |
| else if (palette->size <= 4) bpp = 2; |
| else if (palette->size <= 16) bpp = 4; |
| else bpp = 8; |
| |
| /* Set up PNG writer */ |
| png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| if (!png) { |
| guac_palette_free(palette); |
| guac_error = GUAC_STATUS_INTERNAL_ERROR; |
| guac_error_message = "libpng failed to create write structure"; |
| return -1; |
| } |
| |
| png_info = png_create_info_struct(png); |
| if (!png_info) { |
| png_destroy_write_struct(&png, NULL); |
| guac_palette_free(palette); |
| guac_error = GUAC_STATUS_INTERNAL_ERROR; |
| guac_error_message = "libpng failed to create info structure"; |
| return -1; |
| } |
| |
| /* Set error handler */ |
| if (setjmp(png_jmpbuf(png))) { |
| png_destroy_write_struct(&png, &png_info); |
| guac_palette_free(palette); |
| guac_error = GUAC_STATUS_IO_ERROR; |
| guac_error_message = "libpng output error"; |
| return -1; |
| } |
| |
| /* Init write state */ |
| write_state.socket = socket; |
| write_state.stream = stream; |
| write_state.buffer_size = 0; |
| |
| /* Set up writer */ |
| png_set_write_fn(png, &write_state, |
| guac_png_write_handler, |
| guac_png_flush_handler); |
| |
| /* Copy data from surface into PNG data */ |
| png_rows = (png_byte**) malloc(sizeof(png_byte*) * height); |
| for (y=0; y<height; y++) { |
| |
| /* Allocate new PNG row */ |
| png_byte* row = (png_byte*) malloc(sizeof(png_byte) * width); |
| png_rows[y] = row; |
| |
| /* Copy data from surface into current row */ |
| for (x=0; x<width; x++) { |
| |
| /* Get pixel color */ |
| int color = ((uint32_t*) data)[x] & 0xFFFFFF; |
| |
| /* Set index in row */ |
| row[x] = guac_palette_find(palette, color); |
| |
| } |
| |
| /* Advance to next data row */ |
| data += stride; |
| |
| } |
| |
| /* Write image info */ |
| png_set_IHDR( |
| png, |
| png_info, |
| width, |
| height, |
| bpp, |
| PNG_COLOR_TYPE_PALETTE, |
| PNG_INTERLACE_NONE, |
| PNG_COMPRESSION_TYPE_DEFAULT, |
| PNG_FILTER_TYPE_DEFAULT |
| ); |
| |
| /* Write palette */ |
| png_set_PLTE(png, png_info, palette->colors, palette->size); |
| |
| /* Write image */ |
| png_set_rows(png, png_info, png_rows); |
| png_write_png(png, png_info, PNG_TRANSFORM_PACKING, NULL); |
| |
| /* Finish write */ |
| png_destroy_write_struct(&png, &png_info); |
| |
| /* Free palette */ |
| guac_palette_free(palette); |
| |
| /* Free PNG data */ |
| for (y=0; y<height; y++) |
| free(png_rows[y]); |
| free(png_rows); |
| |
| /* Ensure all data is written */ |
| guac_png_flush_data(&write_state); |
| return 0; |
| |
| } |
| |