/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2013 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "cairo-image-surface-xcf.h" #define TILE_WIDTH 64 #define MAX_TILE_SIZE (TILE_WIDTH * TILE_WIDTH * 4 * 1.5) #define DISSOLVE_SEED 737893334 typedef enum { GIMP_RGB, GIMP_GRAY, GIMP_INDEXED } GimpImageBaseType; typedef enum { GIMP_COMPRESSION_NONE, GIMP_COMPRESSION_RLE, GIMP_COMPRESSION_ZLIB, GIMP_COMPRESSION_FRACTAL } GimpCompression; typedef enum { GIMP_RGB_IMAGE, GIMP_RGBA_IMAGE, GIMP_GRAY_IMAGE, GIMP_GRAYA_IMAGE, GIMP_INDEXED_IMAGE, GIMP_INDEXEDA_IMAGE } GimpImageType; typedef enum { GIMP_LAYER_MODE_NORMAL, GIMP_LAYER_MODE_DISSOLVE, /* (random dithering to discrete alpha) */ GIMP_LAYER_MODE_BEHIND, /* (not selectable in the GIMP UI) */ GIMP_LAYER_MODE_MULTIPLY, GIMP_LAYER_MODE_SCREEN, GIMP_LAYER_MODE_OVERLAY, GIMP_LAYER_MODE_DIFFERENCE, GIMP_LAYER_MODE_ADDITION, GIMP_LAYER_MODE_SUBTRACT, GIMP_LAYER_MODE_DARKEN_ONLY, GIMP_LAYER_MODE_LIGHTEN_ONLY, GIMP_LAYER_MODE_HUE, /* (H of HSV) */ GIMP_LAYER_MODE_SATURATION, /* (S of HSV) */ GIMP_LAYER_MODE_COLOR, /* (H and S of HSL) */ GIMP_LAYER_MODE_VALUE, /* (V of HSV) */ GIMP_LAYER_MODE_DIVIDE, GIMP_LAYER_MODE_DODGE, GIMP_LAYER_MODE_BURN, GIMP_LAYER_MODE_HARD_LIGHT, GIMP_LAYER_MODE_SOFT_LIGHT, /* (XCF version >= 2 only) */ GIMP_LAYER_MODE_GRAIN_EXTRACT, /* (XCF version >= 2 only) */ GIMP_LAYER_MODE_GRAIN_MERGE /* (XCF version >= 2 only) */ } GimpLayerMode; typedef struct { int n; guint width; guint height; GimpImageType type; char *name; guint32 opacity; gboolean visible; gboolean floating_selection; GimpLayerMode mode; gboolean apply_mask; gint32 h_offset; gint32 v_offset; int stride; guchar *pixels; guchar *alpha_mask; int bpp; struct { gboolean dirty; int rows; int columns; int n_tiles; int last_row_height; int last_col_width; } tiles; } GimpLayer; typedef struct { guchar color1; guchar color2; guchar color3; } GimpColormap; static int cairo_rgba[4] = { CAIRO_RED, CAIRO_GREEN, CAIRO_BLUE, CAIRO_ALPHA }; static int cairo_graya[2] = { 0, CAIRO_ALPHA }; static int cairo_indexed[2] = { 0, CAIRO_ALPHA }; /* -- GDataInputStream functions -- */ static char * _g_data_input_stream_read_c_string (GDataInputStream *stream, gsize size, GCancellable *cancellable, GError **error) { char *string; gsize bytes_read; g_return_val_if_fail (size > 0, NULL); string = g_new (char, size + 1); if (g_input_stream_read_all (G_INPUT_STREAM (stream), string, size, &bytes_read, cancellable, error)) { string[bytes_read] = 0; } else string[0] = 0; return string; } static char * _g_data_input_stream_read_xcf_string (GDataInputStream *stream, GCancellable *cancellable, GError **error) { guint32 n_bytes; n_bytes = g_data_input_stream_read_uint32 (stream, cancellable, error); if (n_bytes == 0) return NULL; return _g_data_input_stream_read_c_string (stream, n_bytes, cancellable, error); } /* -- GimpLayer -- */ static GimpLayer * gimp_layer_new (int n) { GimpLayer *layer; layer = g_new0 (GimpLayer, 1); layer->n = n; layer->width = 0; layer->height = 0; layer->type = GIMP_RGBA_IMAGE; layer->name = NULL; layer->opacity = 255; layer->visible = TRUE; layer->floating_selection = FALSE; layer->mode = GIMP_LAYER_MODE_NORMAL; layer->apply_mask = FALSE; layer->h_offset = 0; layer->v_offset = 0; layer->tiles.dirty = TRUE; layer->tiles.n_tiles = 0; layer->pixels = NULL; layer->alpha_mask = NULL; return layer; } static gboolean gimp_layer_get_tile_size (GimpLayer *layer, int n_tile, int bpp, goffset *offset, int *width, int *height) { int tile_row, tile_column; gsize tile_width; gsize tile_height; if (layer->tiles.dirty) { layer->tiles.last_col_width = layer->width % TILE_WIDTH; layer->tiles.last_row_height = layer->height % TILE_WIDTH; layer->tiles.columns = layer->width / TILE_WIDTH; if (layer->tiles.last_col_width > 0) layer->tiles.columns++; else layer->tiles.last_col_width = TILE_WIDTH; layer->tiles.rows = layer->height / TILE_WIDTH; if (layer->tiles.last_row_height > 0) layer->tiles.rows++; else layer->tiles.last_row_height = TILE_WIDTH; layer->tiles.n_tiles = layer->tiles.columns * layer->tiles.rows; layer->tiles.dirty = FALSE; layer->stride = layer->width * bpp; } if ((n_tile < 0) || (n_tile >= layer->tiles.n_tiles)) return FALSE; tile_column = (n_tile % layer->tiles.columns); if (tile_column == layer->tiles.columns - 1) tile_width = layer->tiles.last_col_width; else tile_width = TILE_WIDTH; tile_row = (n_tile / layer->tiles.columns); if (tile_row == layer->tiles.rows - 1) tile_height = layer->tiles.last_row_height; else tile_height = TILE_WIDTH; *offset = ((tile_row * TILE_WIDTH) * layer->stride) + (tile_column * TILE_WIDTH * bpp); *width = tile_width; *height = tile_height; return TRUE; } static void gimp_layer_free (GimpLayer *layer) { if (layer == NULL) return; g_free (layer->pixels); g_free (layer->alpha_mask); g_free (layer->name); g_free (layer); } /* -- _cairo_image_surface_create_from_xcf -- */ static void _cairo_image_surface_paint_layer (cairo_surface_t *image, GimpLayer *layer) { int image_width; int image_height; int image_row_stride; guchar *image_row; int layer_width; int layer_height; int layer_row_stride; guchar *layer_row; guchar *mask_row; int x, y, width, height; guchar *image_pixel; guchar *layer_pixel; guchar *mask_pixel; GRand *rand_gen; int i, j; guchar r, g, b, a; int temp, temp2; guchar image_hue, image_sat, image_val, image_lum; guchar layer_hue, layer_sat, layer_val, layer_lum; if ((image == NULL) || (layer->pixels == NULL)) return; image_width = cairo_image_surface_get_width (image); image_height = cairo_image_surface_get_height (image); image_row_stride = cairo_image_surface_get_stride (image); layer_width = layer->width; layer_height = layer->height; layer_row_stride = layer->width * 4; /* compute the layer <-> image intersection */ { cairo_region_t *region; cairo_rectangle_int_t rect; rect.x = 0; rect.y = 0; rect.width = image_width; rect.height = image_height; region = cairo_region_create_rectangle (&rect); rect.x = layer->h_offset; rect.y = layer->v_offset; rect.width = layer_width; rect.height = layer_height; cairo_region_intersect_rectangle (region, &rect); cairo_region_get_extents (region, &rect); cairo_region_destroy (region); if ((rect.width == 0) || (rect.height == 0)) return; x = rect.x; y = rect.y; width = rect.width; height = rect.height; } image_row = _cairo_image_surface_flush_and_get_data (image) + (y * image_row_stride) + (x * 4); x = (layer->h_offset < 0) ? -layer->h_offset : 0; y = (layer->v_offset < 0) ? -layer->v_offset : 0; layer_row = layer->pixels + (y * layer_row_stride) + (x * 4); mask_row = layer->alpha_mask + (y * layer_width) + x; rand_gen = NULL; if (layer->mode == GIMP_LAYER_MODE_DISSOLVE) rand_gen = g_rand_new_with_seed (DISSOLVE_SEED); for (i = 0; i < height; i++) { image_pixel = image_row; layer_pixel = layer_row; mask_pixel = mask_row; for (j = 0; j < width; j++) { a = ((layer->bpp == 2) || (layer->bpp == 4)) ? layer_pixel[CAIRO_ALPHA] : 255; a = ADD_ALPHA (a, layer->opacity); if (layer->alpha_mask && (layer->alpha_mask != NULL)) a = ADD_ALPHA (a, mask_pixel[0]); if (a == 0) goto next_pixel; switch (layer->mode) { case GIMP_LAYER_MODE_NORMAL: default: r = layer_pixel[CAIRO_RED]; g = layer_pixel[CAIRO_GREEN]; b = layer_pixel[CAIRO_BLUE]; break; case GIMP_LAYER_MODE_DISSOLVE: if (g_rand_int_range (rand_gen, 0, 256) > a) goto next_pixel; r = layer_pixel[CAIRO_RED]; g = layer_pixel[CAIRO_GREEN]; b = layer_pixel[CAIRO_BLUE]; a = 255; break; case GIMP_LAYER_MODE_LIGHTEN_ONLY: r = GIMP_OP_LIGHTEN_ONLY (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_LIGHTEN_ONLY (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_LIGHTEN_ONLY (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_SCREEN: r = GIMP_OP_SCREEN (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_SCREEN (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_SCREEN (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_DODGE: r = GIMP_OP_DODGE (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_DODGE (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_DODGE (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_ADDITION: r = GIMP_OP_ADDITION (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_ADDITION (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_ADDITION (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_DARKEN_ONLY: r = GIMP_OP_DARKEN_ONLY (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_DARKEN_ONLY (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_DARKEN_ONLY (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_MULTIPLY: r = GIMP_OP_MULTIPLY (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_MULTIPLY (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_MULTIPLY (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_BURN: r = GIMP_OP_BURN (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_BURN (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_BURN (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_OVERLAY: case GIMP_LAYER_MODE_SOFT_LIGHT: r = GIMP_OP_SOFT_LIGHT (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_SOFT_LIGHT (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_SOFT_LIGHT (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_HARD_LIGHT: r = GIMP_OP_HARD_LIGHT (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_HARD_LIGHT (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_HARD_LIGHT (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_DIFFERENCE: r = GIMP_OP_DIFFERENCE (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_DIFFERENCE (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_DIFFERENCE (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_SUBTRACT: r = GIMP_OP_SUBTRACT (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_SUBTRACT (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_SUBTRACT (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_GRAIN_EXTRACT: r = GIMP_OP_GRAIN_EXTRACT (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_GRAIN_EXTRACT (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_GRAIN_EXTRACT (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_GRAIN_MERGE: r = GIMP_OP_GRAIN_MERGE (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_GRAIN_MERGE (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_GRAIN_MERGE (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_DIVIDE: r = GIMP_OP_DIVIDE (layer_pixel[CAIRO_RED], image_pixel[CAIRO_RED]); g = GIMP_OP_DIVIDE (layer_pixel[CAIRO_GREEN], image_pixel[CAIRO_GREEN]); b = GIMP_OP_DIVIDE (layer_pixel[CAIRO_BLUE], image_pixel[CAIRO_BLUE]); break; case GIMP_LAYER_MODE_HUE: case GIMP_LAYER_MODE_SATURATION: case GIMP_LAYER_MODE_VALUE: gimp_rgb_to_hsv (image_pixel[CAIRO_RED], image_pixel[CAIRO_GREEN], image_pixel[CAIRO_BLUE], &image_hue, &image_sat, &image_val); gimp_rgb_to_hsv (layer_pixel[CAIRO_RED], layer_pixel[CAIRO_GREEN], layer_pixel[CAIRO_BLUE], &layer_hue, &layer_sat, &layer_val); switch (layer->mode) { case GIMP_LAYER_MODE_HUE: gimp_hsv_to_rgb (layer_hue, image_sat, image_val, &r, &g, &b); break; case GIMP_LAYER_MODE_SATURATION: gimp_hsv_to_rgb (image_hue, layer_sat, image_val, &r, &g, &b); break; case GIMP_LAYER_MODE_VALUE: gimp_hsv_to_rgb (image_hue, image_sat, layer_val, &r, &g, &b); break; default: g_assert_not_reached (); break; } break; case GIMP_LAYER_MODE_COLOR: gimp_rgb_to_hsl (image_pixel[CAIRO_RED], image_pixel[CAIRO_GREEN], image_pixel[CAIRO_BLUE], &image_hue, &image_sat, &image_lum); gimp_rgb_to_hsl (layer_pixel[CAIRO_RED], layer_pixel[CAIRO_GREEN], layer_pixel[CAIRO_BLUE], &layer_hue, &layer_sat, &layer_lum); gimp_hsl_to_rgb (layer_hue, layer_sat, image_lum, &r, &g, &b); break; } image_pixel[CAIRO_RED] = GIMP_OP_NORMAL (r, image_pixel[CAIRO_RED], a); image_pixel[CAIRO_GREEN] = GIMP_OP_NORMAL (g, image_pixel[CAIRO_GREEN], a); image_pixel[CAIRO_BLUE] = GIMP_OP_NORMAL (b, image_pixel[CAIRO_BLUE], a); image_pixel[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_pixel[CAIRO_ALPHA], a); next_pixel: image_pixel += 4; layer_pixel += 4; mask_pixel += 1; } image_row += image_row_stride; layer_row += layer_row_stride; mask_row += layer_width; } if (layer->mode == GIMP_LAYER_MODE_DISSOLVE) g_rand_free (rand_gen); cairo_surface_mark_dirty (image); } static cairo_surface_t * _cairo_image_surface_create_from_layers (int canvas_width, int canvas_height, GimpImageBaseType base_type, GList *layers) { cairo_surface_t *image; GList *scan; image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, canvas_width, canvas_height); if (cairo_surface_status (image) != CAIRO_STATUS_SUCCESS) { cairo_surface_destroy (image); return NULL; } for (scan = layers; scan; scan = scan->next) { GimpLayer *layer = scan->data; if (layer->pixels == NULL) continue; /* the bottommost layer only supports NORMAL and DISSOLVE */ if ((scan == layers) && (layer->mode != GIMP_LAYER_MODE_DISSOLVE)) layer->mode = GIMP_LAYER_MODE_NORMAL; /* indexed images only support NORMAL and DISSOLVE */ if ((base_type == GIMP_INDEXED) && (layer->mode != GIMP_LAYER_MODE_DISSOLVE)) layer->mode = GIMP_LAYER_MODE_NORMAL; /* modes supported only by RGB images */ if (base_type != GIMP_RGB) { if ((layer->mode == GIMP_LAYER_MODE_HUE) || (layer->mode == GIMP_LAYER_MODE_SATURATION) || (layer->mode == GIMP_LAYER_MODE_COLOR) || (layer->mode == GIMP_LAYER_MODE_VALUE)) { layer->mode = GIMP_LAYER_MODE_NORMAL; } } _cairo_image_surface_paint_layer (image, layer); performance (DEBUG_INFO, "end paint layer %d, mode %d", layer->n, layer->mode); } return image; } static guchar * read_pixels_from_hierarchy (GDataInputStream *data_stream, guint32 hierarchy_offset, GimpLayer *layer, GimpColormap *colormap, GimpImageBaseType base_type, GimpCompression compression, gboolean is_gimp_channel, GCancellable *cancellable, GError **error) { guchar *image_pixels = NULL; guint32 width; guint32 height; guint32 in_bpp; guint32 out_bpp; int row_stride; guint32 level_offset; GArray *tile_offsets = NULL; guint32 tile_offset; guint32 last_tile_offset; int n_tiles; int t; /* read the hierarchy structure */ if (! g_seekable_seek (G_SEEKABLE (data_stream), hierarchy_offset, G_SEEK_SET, cancellable, error)) { return NULL; } width = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto read_error; height = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto read_error; in_bpp = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto read_error; level_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto read_error; if (is_gimp_channel && in_bpp != 1){ printf("Error: in_bpp = %d and is_gimp_channel is true. Expected in_bpp = 1 when is_gimp_channel is true.\n", in_bpp); goto read_error; } if (! is_gimp_channel) layer->bpp = in_bpp; layer->tiles.dirty = TRUE; /* read the level structure */ if (! g_seekable_seek (G_SEEKABLE (data_stream), level_offset, G_SEEK_SET, cancellable, error)) { goto read_error; } width = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto read_error; height = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto read_error; /* tiles */ out_bpp = is_gimp_channel ? 1 : 4; row_stride = width * out_bpp; image_pixels = g_new (guchar, row_stride * height); tile_offsets = g_array_new (FALSE, FALSE, sizeof (guint32));; n_tiles = 0; last_tile_offset = 0; while ((tile_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error)) != 0) { n_tiles += 1; last_tile_offset = tile_offset; g_array_append_val (tile_offsets, tile_offset); } tile_offset = last_tile_offset + MAX_TILE_SIZE; g_array_append_val (tile_offsets, tile_offset); if (*error != NULL) goto read_error; if (compression == GIMP_COMPRESSION_RLE) { guchar *tile_data = NULL; /* go to the first tile */ tile_offset = g_array_index (tile_offsets, guint32, 0); if (! g_seekable_seek (G_SEEKABLE (data_stream), tile_offset, G_SEEK_SET, cancellable, error)) { goto read_error; } /* decompress the tile data */ tile_data = g_malloc (MAX_TILE_SIZE); for (t = 0; t < n_tiles; t++) { goffset tile_data_size; guchar *tile_data_p; guchar *tile_data_limit; gsize data_read; goffset tile_pixels_offset; gsize tile_pixels_size; int tile_width; int tile_height; int c; /* read the tile data */ tile_data_size = g_array_index (tile_offsets, guint32, t + 1) - g_array_index (tile_offsets, guint32, t); if (tile_data_size <= 0) continue; if (! g_input_stream_read_all (G_INPUT_STREAM (data_stream), tile_data, tile_data_size, &data_read, cancellable, error)) { goto rle_error; } /* decompress the channel streams */ if (! gimp_layer_get_tile_size (layer, t, out_bpp, &tile_pixels_offset, &tile_width, &tile_height)) { goto rle_error; } tile_pixels_size = tile_width * tile_height; tile_data_p = tile_data; tile_data_limit = tile_data + data_read - 1; for (c = 0; c < in_bpp; c++) { int channel_offset; guchar *pixels_row; guchar *pixel; int size; int n, p, q, v; int tile_column; if (is_gimp_channel) channel_offset = 0; else if (base_type == GIMP_INDEXED) channel_offset = cairo_indexed[c]; else if (in_bpp >= 3) channel_offset = cairo_rgba[c]; else if (in_bpp <= 2) channel_offset = cairo_graya[c]; else channel_offset = 0; pixels_row = image_pixels + tile_pixels_offset + channel_offset; pixel = pixels_row; size = tile_pixels_size; tile_column = 0; #define SET_PIXEL(v) { \ tile_column++; \ if (tile_column > tile_width) { \ pixels_row += row_stride; \ pixel = pixels_row; \ tile_column = 1; \ } \ if ((base_type == GIMP_INDEXED) && (c == 0)) { \ guchar *color = (guchar *) (colormap + (v)); \ pixel[CAIRO_RED] = color[0]; \ pixel[CAIRO_GREEN] = color[1]; \ pixel[CAIRO_BLUE] = color[2]; \ } \ else if (! is_gimp_channel && (in_bpp <= 2) && (c == 0)) { \ pixel[CAIRO_RED] = (v); \ pixel[CAIRO_GREEN] = (v); \ pixel[CAIRO_BLUE] = (v); \ } \ else \ *pixel = (v); \ pixel += out_bpp; \ } while (size > 0) { if (tile_data_p > tile_data_limit) goto rle_error; n = *tile_data_p++; if ((n >= 0) && (n <= 127)) { /* byte n For 0 <= n <= 126: a short run of identical bytes * byte v Repeat this value n+1 times */ /* byte 127 A long run of identical bytes * byte p * byte q * byte v Repeat this value p*256 + q times */ if (n == 127) { if (tile_data_p + 2 > tile_data_limit) goto rle_error; p = *tile_data_p++; q = *tile_data_p++; v = *tile_data_p++; n = (p * 256) + q; } else { if (tile_data_p > tile_data_limit) goto rle_error; v = *tile_data_p++; n++; } size -= n; if (size < 0) goto rle_error; while (n-- > 0) SET_PIXEL (v); } else if ((n >= 128) && (n <= 255)) { /* byte 128 A long run of different bytes * byte p * byte q * byte[p*256+q] data Copy these verbatim to the output stream */ /* byte n For 129 <= n <= 255: a short run of different bytes * byte[256-n] data Copy these verbatim to the output stream */ if (n == 128) { if (tile_data_p + 1 > tile_data_limit) goto rle_error; p = *tile_data_p++; q = *tile_data_p++; n = (p * 256) + q; } else n = 256 - n; if (tile_data_p + n - 1 > tile_data_limit) goto rle_error; size -= n; if (size < 0) goto rle_error; while (n-- > 0) { v = *tile_data_p++; SET_PIXEL (v); } } } } } rle_error: g_free (tile_data); } else if (compression == GIMP_COMPRESSION_NONE) { /* Gimp doesn't save in uncompressed mode. */ } performance (DEBUG_INFO, "end read hierarchy"); g_array_free (tile_offsets, TRUE); return image_pixels; read_error: g_free (image_pixels); g_array_free (tile_offsets, TRUE); return NULL; } #undef SET_PIXEL GthImage * _cairo_image_surface_create_from_xcf (GInputStream *istream, GthFileData *file_data, int requested_size, int *original_width, int *original_height, gboolean *loaded_original, gpointer user_data, GCancellable *cancellable, GError **error) { GthImage *image = NULL; cairo_surface_t *surface; GDataInputStream *data_stream; char *file_type; char *version; guint32 canvas_width; guint32 canvas_height; GimpImageBaseType base_type; guint32 property_type; guint32 payload_length; GimpCompression compression; GList *layers; guint32 n_colors; GimpColormap *colormap; guint n_properties; gboolean read_properties; GArray *layer_offsets; guint32 layer_offset; guint n_layers; guint32 channel_offset; guint n_channels; int i; performance (DEBUG_INFO, "start loading"); gimp_op_init (); performance (DEBUG_INFO, "end init"); compression = GIMP_COMPRESSION_RLE; layers = NULL; layer_offsets = NULL; colormap = NULL; data_stream = g_data_input_stream_new (istream); g_data_input_stream_set_byte_order (data_stream, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN); /* file type magic */ file_type = _g_data_input_stream_read_c_string (data_stream, 9, cancellable, error); if (*error != NULL) goto out; if (g_strcmp0 (file_type, "gimp xcf ") != 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid format"); return NULL; } g_free (file_type); /* version */ version = _g_data_input_stream_read_c_string (data_stream, 5, cancellable, error); if (*error != NULL) goto out; g_free (version); /* canvas size */ canvas_width = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; canvas_height = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /* base type */ base_type = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /* properties */ read_properties = TRUE; n_properties = 0; while (read_properties) { property_type = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; payload_length = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; n_properties += 1; switch (property_type) { case 0: /* PROP_END */ read_properties = FALSE; break; case 1: /* PROP_COLORMAP */ n_colors = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; if (base_type == GIMP_INDEXED) { int i; guchar *c; colormap = g_new (GimpColormap, n_colors); c = (guchar *) colormap; for (i = 0; i < n_colors; i++) { c[0] = g_data_input_stream_read_byte (data_stream, cancellable, error); if (*error != NULL) goto out; c[1] = g_data_input_stream_read_byte (data_stream, cancellable, error); if (*error != NULL) goto out; c[2] = g_data_input_stream_read_byte (data_stream, cancellable, error); if (*error != NULL) goto out; c += 3; } } else { /* when skipping the colormap do not trust the payload_length value. */ g_input_stream_skip (G_INPUT_STREAM (data_stream), (n_colors * 3), cancellable, error); if (*error != NULL) goto out; } break; case 17: /* PROP_COMPRESSION */ compression = g_data_input_stream_read_byte (data_stream, cancellable, error); if (*error != NULL) goto out; break; default: g_input_stream_skip (G_INPUT_STREAM (data_stream), payload_length, cancellable, error); if (*error != NULL) goto out; break; } } /* layers */ n_layers = 0; layer_offsets = g_array_new (FALSE, FALSE, sizeof (guint32)); while ((layer_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error)) != 0) { n_layers += 1; g_array_append_val (layer_offsets, layer_offset); } /* channels */ n_channels = 0; while ((channel_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error)) != 0) n_channels += 1; if (*error != NULL) goto out; /* read the layers */ performance (DEBUG_INFO, "start read layers"); for (i = 0; i < n_layers; i++) { GimpLayer *layer; guint32 hierarchy_offset; guint32 mask_offset; char *mask_name; layer_offset = g_array_index (layer_offsets, guint32, i); if (! g_seekable_seek (G_SEEKABLE (data_stream), layer_offset, G_SEEK_SET, cancellable, error)) { goto out; } layer = gimp_layer_new (i); layers = g_list_prepend (layers, layer); /* size */ layer->width = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; layer->height = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /* type */ layer->type = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /* name */ layer->name = _g_data_input_stream_read_xcf_string (data_stream, cancellable, error); if (*error != NULL) goto out; /* properties */ read_properties = TRUE; n_properties = 0; while (read_properties) { property_type = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; payload_length = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; n_properties += 1; switch (property_type) { case 0: /* PROP_END */ read_properties = FALSE; break; case 5: /* PROP_FLOATING_SELECTION */ layer->floating_selection = TRUE; g_input_stream_skip (G_INPUT_STREAM (data_stream), payload_length, cancellable, error); break; case 6: /* PROP_OPACITY */ layer->opacity = g_data_input_stream_read_uint32 (data_stream, cancellable, error); break; case 7: /* PROP_MODE */ layer->mode = g_data_input_stream_read_uint32 (data_stream, cancellable, error); break; case 8: /* PROP_VISIBLE */ layer->visible = g_data_input_stream_read_uint32 (data_stream, cancellable, error); break; case 11: /* PROP_APPLY_MASK */ layer->apply_mask = (g_data_input_stream_read_uint32 (data_stream, cancellable, error) == 1); break; case 15: /* PROP_OFFSETS */ layer->h_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; layer->v_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error); break; default: g_input_stream_skip (G_INPUT_STREAM (data_stream), payload_length, cancellable, error); break; } if (*error != NULL) goto out; } /* hierarchy structure offset */ hierarchy_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /* layer mask offset */ mask_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /* the layer image */ if (layer->floating_selection || ! layer->visible || (layer->opacity == 0)) continue; layer->pixels = read_pixels_from_hierarchy (data_stream, hierarchy_offset, layer, colormap, base_type, compression, FALSE, cancellable, error); if (*error != NULL) goto out; /* read the mask */ if (! layer->apply_mask || (mask_offset == 0)) continue; if (! g_seekable_seek (G_SEEKABLE (data_stream), mask_offset, G_SEEK_SET, cancellable, error)) { goto out; } /* mask width, height and name */ /*mask_width = */ g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; /*mask_height = */ g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; mask_name = _g_data_input_stream_read_xcf_string (data_stream, cancellable, error); if (*error != NULL) goto out; g_free (mask_name); /* mask properties */ read_properties = TRUE; n_properties = 0; while (read_properties) { property_type = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; payload_length = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; n_properties += 1; switch (property_type) { case 0: /* PROP_END */ read_properties = FALSE; break; default: g_input_stream_skip (G_INPUT_STREAM (data_stream), payload_length, cancellable, error); if (*error != NULL) goto out; break; } } /* the mask image */ hierarchy_offset = g_data_input_stream_read_uint32 (data_stream, cancellable, error); if (*error != NULL) goto out; layer->alpha_mask = read_pixels_from_hierarchy (data_stream, hierarchy_offset, layer, colormap, base_type, compression, TRUE, cancellable, error); if (*error != NULL) goto out; } performance (DEBUG_INFO, "end read layers"); image = gth_image_new (); surface = _cairo_image_surface_create_from_layers (canvas_width, canvas_height, base_type, layers); if (surface != NULL) { gth_image_set_cairo_surface (image, surface); cairo_surface_destroy (surface); } performance (DEBUG_INFO, "end rendering"); out: g_list_free_full (layers, (GDestroyNotify) gimp_layer_free); if (layer_offsets != NULL) g_array_free (layer_offsets, TRUE); g_free (colormap); if (data_stream != NULL) g_object_unref (data_stream); return image; }