guac_palette* guac_palette_alloc(cairo_surface_t* surface) { 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); unsigned char* data = cairo_image_surface_get_data(surface); /* Allocate palette */ guac_palette* palette = (guac_palette*) malloc(sizeof(guac_palette)); memset(palette, 0, sizeof(guac_palette)); for (y=0; y<height; y++) { for (x=0; x<width; x++) { /* Get pixel color */ int color = ((uint32_t*) data)[x] & 0xFFFFFF; /* Calculate hash code */ int hash = ((color & 0xFFF000) >> 12) ^ (color & 0xFFF); guac_palette_entry* entry; /* Search for open palette entry */ for (;;) { entry = &(palette->entries[hash]); /* If we've found a free space, use it */ if (entry->index == 0) { png_color* c; /* Stop if already at capacity */ if (palette->size == 256) { guac_palette_free(palette); return NULL; } /* Store in palette */ c = &(palette->colors[palette->size]); c->blue = (color ) & 0xFF; c->green = (color >> 8 ) & 0xFF; c->red = (color >> 16) & 0xFF; /* Add color to map */ entry->index = ++palette->size; entry->color = color; break; } /* Otherwise, if already stored here, done */ if (entry->color == color) break; /* Otherwise, collision. Move on to another bucket */ hash = (hash+1) & 0xFFF; } } /* Advance to next data row */ data += stride; }
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_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_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_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; }