/* * 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-webp.h" #include "guacamole/error.h" #include "guacamole/protocol.h" #include "guacamole/stream.h" #include "palette.h" #include #include #include #include #include #include #include /** * Structure which describes the current state of the WebP image writer. */ typedef struct guac_webp_stream_writer { /** * The socket over which all WebP blobs will be written. */ guac_socket* socket; /** * The Guacamole stream to associate with each WebP blob. */ guac_stream* stream; /** * Buffer of pending WebP data. */ char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH]; /** * The number of bytes currently stored in the buffer. */ int buffer_size; } guac_webp_stream_writer; /** * Writes the contents of the WebP stream writer as a blob to its associated * socket. * * @param writer * The writer structure to flush. */ static void guac_webp_flush_data(guac_webp_stream_writer* writer) { /* Send blob */ guac_protocol_send_blob(writer->socket, writer->stream, writer->buffer, writer->buffer_size); /* Clear buffer */ writer->buffer_size = 0; } /** * Configures the given stream writer object to use the given Guacamole stream * object for WebP output. * * @param writer * The Guacamole WebP stream writer structure to configure. * * @param socket * The Guacamole socket to use when sending blob instructions. * * @param stream * The stream over which WebP-encoded blobs of image data should be sent. */ static void guac_webp_stream_writer_init(guac_webp_stream_writer* writer, guac_socket* socket, guac_stream* stream) { writer->buffer_size = 0; /* Store Guacamole-specific objects */ writer->socket = socket; writer->stream = stream; } /** * WebP output function which appends the given WebP data to the internal * buffer of the Guacamole stream writer structure, automatically flushing the * writer as necessary. * * @param data * The segment of data to write. * * @param data_size * The size of segment of data to write. * * @param picture * The WebP picture associated with this write operation. Provides access to * picture->custom_ptr which contains the Guacamole stream writer structure. * * @return * Non-zero if writing was successful, zero on failure. */ static int guac_webp_stream_write(const uint8_t* data, size_t data_size, const WebPPicture* picture) { guac_webp_stream_writer* const writer = (guac_webp_stream_writer*) picture->custom_ptr; assert(writer != NULL); const unsigned char* current = data; int length = data_size; /* Append all data given */ while (length > 0) { /* Calculate space remaining */ int remaining = sizeof(writer->buffer) - writer->buffer_size; /* If no space remains, flush buffer to make room */ if (remaining == 0) { guac_webp_flush_data(writer); remaining = sizeof(writer->buffer); } /* Calculate size of next block of data to append */ int block_size = remaining; if (block_size > length) block_size = length; /* Append block */ memcpy(writer->buffer + writer->buffer_size, current, block_size); /* Next block */ current += block_size; writer->buffer_size += block_size; length -= block_size; } return 1; } int guac_webp_write(guac_socket* socket, guac_stream* stream, cairo_surface_t* surface, int quality, int lossless) { guac_webp_stream_writer writer; WebPPicture picture; uint32_t* argb_output; int x, y; int width = cairo_image_surface_get_width(surface); int height = cairo_image_surface_get_height(surface); int stride = cairo_image_surface_get_stride(surface); cairo_format_t format = cairo_image_surface_get_format(surface); unsigned char* data = cairo_image_surface_get_data(surface); if (format != CAIRO_FORMAT_RGB24 && format != CAIRO_FORMAT_ARGB32) { guac_error = GUAC_STATUS_INTERNAL_ERROR; guac_error_message = "Invalid Cairo image format. Unable to create WebP."; return -1; } /* Flush pending operations to surface */ cairo_surface_flush(surface); /* Configure WebP compression bits */ WebPConfig config; if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality)) return -1; /* Add additional tuning */ config.lossless = lossless; config.quality = quality; config.thread_level = 1; /* Multi threaded */ config.method = 2; /* Compression method (0=fast/larger, 6=slow/smaller) */ /* Validate configuration */ WebPValidateConfig(&config); /* Set up WebP picture */ WebPPictureInit(&picture); picture.use_argb = 1; picture.width = width; picture.height = height; /* Allocate and init writer */ WebPPictureAlloc(&picture); picture.writer = guac_webp_stream_write; picture.custom_ptr = &writer; guac_webp_stream_writer_init(&writer, socket, stream); /* Copy image data into WebP picture */ argb_output = picture.argb; for (y = 0; y < height; y++) { /* Get pixels at start of each row */ uint32_t* src = (uint32_t*) data; uint32_t* dst = argb_output; /* For each pixel in row */ for (x = 0; x < width; x++) { /* Pull pixel data, removing alpha channel if necessary */ uint32_t src_pixel = *src; if (format != CAIRO_FORMAT_ARGB32) src_pixel |= 0xFF000000; /* Store converted pixel data */ *dst = src_pixel; /* Next pixel */ src++; dst++; } /* Next row */ data += stride; argb_output += picture.argb_stride; } /* Encode image */ WebPEncode(&config, &picture); /* Free picture */ WebPPictureFree(&picture); /* Ensure all data is written */ guac_webp_flush_data(&writer); return 0; }