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