diff --git a/plug-ins/file-psd/psd-layer-res-load.c b/plug-ins/file-psd/psd-layer-res-load.c index c676b13f7c..4e6ab37065 100644 --- a/plug-ins/file-psd/psd-layer-res-load.c +++ b/plug-ins/file-psd/psd-layer-res-load.c @@ -20,24 +20,24 @@ /* ----- Known Layer Resource Block Types ----- All layer resources not otherwise handled, including unknown types - are dropped with a warning. + will show up empty with a warning. * Adjustment layer IDs * - PSD_LADJ_LEVEL "levl" Drop Layer * Adjustment layer - levels (PS4) * - PSD_LADJ_CURVE "curv" Drop Layer * Adjustment layer - curves (PS4) * - PSD_LADJ_BRIGHTNESS "brit" Drop Layer * Adjustment layer - brightness contrast (PS4) * - PSD_LADJ_BALANCE "blnc" Drop Layer * Adjustment layer - color balance (PS4) * - PSD_LADJ_BLACK_WHITE "blwh" Drop Layer * Adjustment layer - black & white (PS10) * - PSD_LADJ_HUE "hue " Drop Layer * Adjustment layer - old hue saturation (PS4) * - PSD_LADJ_HUE2 "hue2" Drop Layer * Adjustment layer - hue saturation (PS5) * - PSD_LADJ_SELECTIVE "selc" Drop Layer * Adjustment layer - selective color (PS4) * - PSD_LADJ_MIXER "mixr" Drop Layer * Adjustment layer - channel mixer (PS9) * - PSD_LADJ_GRAD_MAP "grdm" Drop Layer * Adjustment layer - gradient map (PS9) * - PSD_LADJ_PHOTO_FILT "phfl" Drop Layer * Adjustment layer - photo filter (PS9) * - PSD_LADJ_EXPOSURE "expA" Drop Layer * Adjustment layer - exposure (PS10) * - PSD_LADJ_INVERT "nvrt" Drop Layer * Adjustment layer - invert (PS4) * - PSD_LADJ_THRESHOLD "thrs" Drop Layer * Adjustment layer - threshold (PS4) * - PSD_LADJ_POSTERIZE "post" Drop Layer * Adjustment layer - posterize (PS4) * + PSD_LADJ_LEVEL "levl" Empty Layer * Adjustment layer - levels (PS4) * + PSD_LADJ_CURVE "curv" Empty Layer * Adjustment layer - curves (PS4) * + PSD_LADJ_BRIGHTNESS "brit" Empty Layer * Adjustment layer - brightness contrast (PS4) * + PSD_LADJ_BALANCE "blnc" Empty Layer * Adjustment layer - color balance (PS4) * + PSD_LADJ_BLACK_WHITE "blwh" Empty Layer * Adjustment layer - black & white (PS10) * + PSD_LADJ_HUE "hue " Empty Layer * Adjustment layer - old hue saturation (PS4) * + PSD_LADJ_HUE2 "hue2" Empty Layer * Adjustment layer - hue saturation (PS5) * + PSD_LADJ_SELECTIVE "selc" Empty Layer * Adjustment layer - selective color (PS4) * + PSD_LADJ_MIXER "mixr" Empty Layer * Adjustment layer - channel mixer (PS9) * + PSD_LADJ_GRAD_MAP "grdm" Empty Layer * Adjustment layer - gradient map (PS9) * + PSD_LADJ_PHOTO_FILT "phfl" Empty Layer * Adjustment layer - photo filter (PS9) * + PSD_LADJ_EXPOSURE "expA" Empty Layer * Adjustment layer - exposure (PS10) * + PSD_LADJ_INVERT "nvrt" Empty Layer * Adjustment layer - invert (PS4) * + PSD_LADJ_THRESHOLD "thrs" Empty Layer * Adjustment layer - threshold (PS4) * + PSD_LADJ_POSTERIZE "post" Empty Layer * Adjustment layer - posterize (PS4) * PSD_LADJ_VIBRANCE "vibA" - * Adjustment layer - vibrance (PS10) * PSD_LADJ_COLOR_LOOKUP "clrL" - * Adjustment layer - color lookup (PS13) * @@ -503,18 +503,8 @@ load_resource_ladj (const PSDlayerres *res_a, GError **error) { /* Load adjustment layer */ - static gboolean msg_flag = FALSE; IFDBG(2) g_debug ("Process layer resource block %.4s: Adjustment layer", res_a->key); - lyr_a->drop = TRUE; - if (! msg_flag && CONVERSION_WARNINGS) - { - g_message ("Warning:\n" - "The image file contains adjustment layers. " - "These are not supported by the GIMP and will " - "be dropped."); - msg_flag = TRUE; - } return 0; } @@ -526,17 +516,8 @@ load_resource_lfil (const PSDlayerres *res_a, GError **error) { /* Load fill layer */ - static gboolean msg_flag = FALSE; IFDBG(2) g_debug ("Process layer resource block %.4s: Fill layer", res_a->key); - if (! msg_flag && CONVERSION_WARNINGS) - { - g_message ("Warning:\n" - "The image file contains fill layers. " - "These are not supported by the GIMP and will " - "be rasterized."); - msg_flag = TRUE; - } return 0; } @@ -548,17 +529,8 @@ load_resource_lfx (const PSDlayerres *res_a, GError **error) { /* Load layer effects */ - static gboolean msg_flag = FALSE; IFDBG(2) g_debug ("Process layer resource block %.4s: Layer effects", res_a->key); - if (! msg_flag && CONVERSION_WARNINGS) - { - g_message ("Warning:\n" - "The image file contains layer effects. " - "These are not supported by the GIMP and will " - "be dropped."); - msg_flag = TRUE; - } return 0; } @@ -583,17 +555,7 @@ load_resource_ltyp (const PSDlayerres *res_a, guint64 t_ty; gchar *classID; - static gboolean msg_flag = FALSE; - IFDBG(2) g_debug ("Process layer resource block %.4s: Type tool layer", res_a->key); - if (! msg_flag && CONVERSION_WARNINGS) - { - g_message ("Warning:\n" - "The image file contains type tool layers. " - "These are not supported by the GIMP and will " - "be dropped."); - msg_flag = TRUE; - } /* New style type tool layers (ps6) */ if (memcmp (res_a->key, PSD_LTYP_TYPE2, 4) == 0) diff --git a/plug-ins/file-psd/psd-load.c b/plug-ins/file-psd/psd-load.c index f3cc97c4a1..e6a23ec4b8 100644 --- a/plug-ins/file-psd/psd-load.c +++ b/plug-ins/file-psd/psd-load.c @@ -792,7 +792,6 @@ read_layer_info (PSDimage *img_a, lyr_a[lidx] = (PSDlayer *) g_malloc (sizeof (PSDlayer) ); /* Initialise record */ - lyr_a[lidx]->drop = FALSE; lyr_a[lidx]->id = 0; lyr_a[lidx]->group_type = 0; @@ -1916,624 +1915,607 @@ add_layers (GimpImage *image, { IFDBG(2) g_debug ("Process Layer No %d (%s).", lidx, lyr_a[lidx]->name); - if (lyr_a[lidx]->drop) - { - IFDBG(2) g_debug ("Drop layer %d", lidx); + /* Empty layer */ + if (lyr_a[lidx]->bottom - lyr_a[lidx]->top == 0 + || lyr_a[lidx]->right - lyr_a[lidx]->left == 0) + empty = TRUE; + else + empty = FALSE; - /* Step past layer data */ - for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + /* Empty mask */ + if (lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top == 0 + || lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left == 0) + empty_mask = TRUE; + else + empty_mask = FALSE; + + IFDBG(3) g_debug ("Empty mask %d, size %d %d", empty_mask, + lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top, + lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left); + + /* Load layer channel data */ + IFDBG(2) g_debug ("Number of channels: %d", lyr_a[lidx]->num_channels); + /* Create pointer array for the channel records */ + lyr_chn = g_new0 (PSDchannel *, lyr_a[lidx]->num_channels); + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + { + guint16 comp_mode = PSD_COMP_RAW; + + /* Allocate channel record */ + lyr_chn[cidx] = g_malloc (sizeof (PSDchannel) ); + + lyr_chn[cidx]->id = lyr_a[lidx]->chn_info[cidx].channel_id; + lyr_chn[cidx]->rows = lyr_a[lidx]->bottom - lyr_a[lidx]->top; + lyr_chn[cidx]->columns = lyr_a[lidx]->right - lyr_a[lidx]->left; + lyr_chn[cidx]->data = NULL; + + if (lyr_chn[cidx]->id == PSD_CHANNEL_EXTRA_MASK) { if (! psd_seek (input, lyr_a[lidx]->chn_info[cidx].data_len, G_SEEK_CUR, error)) { psd_set_error (error); + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); return -1; } + + continue; + } + else if (lyr_chn[cidx]->id == PSD_CHANNEL_MASK) + { + /* Works around a bug in panotools psd files where the layer mask + size is given as 0 but data exists. Set mask size to layer size. + */ + if (empty_mask && lyr_a[lidx]->chn_info[cidx].data_len - 2 > 0) + { + empty_mask = FALSE; + if (lyr_a[lidx]->layer_mask.top == lyr_a[lidx]->layer_mask.bottom) + { + lyr_a[lidx]->layer_mask.top = lyr_a[lidx]->top; + lyr_a[lidx]->layer_mask.bottom = lyr_a[lidx]->bottom; + } + if (lyr_a[lidx]->layer_mask.right == lyr_a[lidx]->layer_mask.left) + { + lyr_a[lidx]->layer_mask.right = lyr_a[lidx]->right; + lyr_a[lidx]->layer_mask.left = lyr_a[lidx]->left; + } + } + lyr_chn[cidx]->rows = (lyr_a[lidx]->layer_mask.bottom - + lyr_a[lidx]->layer_mask.top); + lyr_chn[cidx]->columns = (lyr_a[lidx]->layer_mask.right - + lyr_a[lidx]->layer_mask.left); + } + + IFDBG(3) g_debug ("Channel id %d, %dx%d", + lyr_chn[cidx]->id, + lyr_chn[cidx]->columns, + lyr_chn[cidx]->rows); + + /* Only read channel data if there is any channel + * data. Note that the channel data can contain a + * compression method but no actual data. + */ + if (lyr_a[lidx]->chn_info[cidx].data_len >= COMP_MODE_SIZE) + { + if (psd_read (input, &comp_mode, COMP_MODE_SIZE, error) < COMP_MODE_SIZE) + { + psd_set_error (error); + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + return -1; + } + + if (! img_a->ibm_pc_format) + comp_mode = GUINT16_FROM_BE (comp_mode); + else + comp_mode = GUINT16_FROM_LE (comp_mode); + IFDBG(3) g_debug ("Compression mode: %d", comp_mode); + } + if (lyr_a[lidx]->chn_info[cidx].data_len > COMP_MODE_SIZE) + { + switch (comp_mode) + { + case PSD_COMP_RAW: /* Planar raw data */ + IFDBG(3) g_debug ("Raw data length: %" G_GSIZE_FORMAT, + lyr_a[lidx]->chn_info[cidx].data_len - 2); + if (read_channel_data (lyr_chn[cidx], img_a->bps, + PSD_COMP_RAW, NULL, input, 0, + error) < 1) + { + psd_set_error (error); + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + return -1; + } + break; + + case PSD_COMP_RLE: /* Packbits */ + if (! read_RLE_channel (img_a, lyr_chn[cidx], + lyr_a[lidx]->chn_info[cidx].data_len, + input, error)) + { + psd_set_error (error); + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + return -1; + } + break; + + case PSD_COMP_ZIP: /* ? */ + case PSD_COMP_ZIP_PRED: + if (read_channel_data (lyr_chn[cidx], img_a->bps, + comp_mode, NULL, input, + lyr_a[lidx]->chn_info[cidx].data_len - 2, + error) < 1) + { + psd_set_error (error); + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + return -1; + } + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported compression mode: %d"), comp_mode); + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + return -1; + break; + } + } + } + + /* Draw layer */ + + alpha = FALSE; + alpha_chn = -1; + user_mask = FALSE; + user_mask_chn = -1; + layer_channels = 0; + l_x = 0; + l_y = 0; + l_w = img_a->columns; + l_h = img_a->rows; + if (parent_group_stack->len > 0) + parent_group = g_array_index (parent_group_stack, GimpLayer *, + parent_group_stack->len - 1); + else + parent_group = NULL; /* root */ + + if (img_a->color_mode == PSD_CMYK) + base_channels = 4; + else if (img_a->color_mode == PSD_RGB || img_a->color_mode == PSD_LAB) + base_channels = 3; + else + base_channels = 1; + + IFDBG(3) g_debug ("Re-hash channel indices"); + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + { + IFDBG(3) g_debug ("Channel: %d - id: %d", cidx, lyr_chn[cidx]->id); + if (lyr_chn[cidx]->id == PSD_CHANNEL_MASK || + lyr_chn[cidx]->id == PSD_CHANNEL_EXTRA_MASK) + { + /* According to the specs the extra mask (which they call + * real user supplied layer mask) is used "when both a user + * mask and a vector mask are present". + * I haven't seen an example that has the extra mask, so not + * sure which of the masks would appear first. + * For now assuming that the extra mask will be first. */ + if (! user_mask) + { + user_mask = TRUE; + user_mask_chn = cidx; + } + else + { + /* Not using this mask, make sure it gets freed. */ + g_free (lyr_chn[cidx]->data); + } + } + else if (lyr_chn[cidx]->id == PSD_CHANNEL_ALPHA) + { + alpha = TRUE; + alpha_chn = cidx; + } + else if (lyr_chn[cidx]->data) + { + if (layer_channels < base_channels) + { + channel_idx[layer_channels] = cidx; /* Assumes in sane order */ + layer_channels++; /* RGB, Lab, CMYK etc. */ + } + else + { + /* channel_idx[base_channels] is reserved for alpha channel, + * but this layer apparently has extra channels. + * From the one example I have (see #8411) it looks like + * that channel is the same as the alpha channel. */ + IFDBG(2) g_debug ("This layer has an extra channel (id: %d)", lyr_chn[cidx]->id); + channel_idx[layer_channels+1] = cidx; /* Assumes in sane order */ + layer_channels += 2; /* RGB, Lab, CMYK etc. */ + } + } + else + { + IFDBG(4) g_debug ("Channel %d (id: %d) has no data", cidx, lyr_chn[cidx]->id); + } + } + + if (alpha) + { + if (layer_channels <= base_channels) + { + channel_idx[layer_channels] = alpha_chn; + layer_channels++; + } + else + { + channel_idx[base_channels] = alpha_chn; + } + base_channels++; + } + + IFDBG(4) g_debug ("Create the layer (group type: %d, clipping group type: %d)", + lyr_a[lidx]->group_type, lyr_a[lidx]->clipping_group_type); + + /* Create the layer */ + if (lyr_a[lidx]->group_type != 0) + { + if (lyr_a[lidx]->group_type == 3) + { + /* the marker layers are used to + * assemble the layer structure in a single pass + */ + IFDBG(2) g_debug ("Create placeholder group layer"); + layer = gimp_layer_group_new (image); + /* add this group layer as the new parent */ + g_array_append_val (parent_group_stack, layer); + } + else /* group-type == 1 || group_type == 2 */ + { + if (parent_group_stack->len) + { + layer = g_array_index (parent_group_stack, GimpLayer *, + parent_group_stack->len - 1); + IFDBG(2) g_debug ("End group layer id %d.", gimp_item_get_id (GIMP_ITEM (layer))); + /* since the layers are stored in reverse, the group + * layer start marker actually means we're done with + * that layer group + */ + g_array_remove_index (parent_group_stack, + parent_group_stack->len - 1); + + gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &l_x, &l_y); + + l_w = gimp_drawable_get_width (GIMP_DRAWABLE (layer)); + l_h = gimp_drawable_get_height (GIMP_DRAWABLE (layer)); + } + else + { + IFDBG(1) g_debug ("WARNING: Unmatched group layer start marker."); + layer = NULL; + } } } else { - /* Empty layer */ - if (lyr_a[lidx]->bottom - lyr_a[lidx]->top == 0 - || lyr_a[lidx]->right - lyr_a[lidx]->left == 0) - empty = TRUE; - else - empty = FALSE; - - /* Empty mask */ - if (lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top == 0 - || lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left == 0) - empty_mask = TRUE; - else - empty_mask = FALSE; - - IFDBG(3) g_debug ("Empty mask %d, size %d %d", empty_mask, - lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top, - lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left); - - /* Load layer channel data */ - IFDBG(2) g_debug ("Number of channels: %d", lyr_a[lidx]->num_channels); - /* Create pointer array for the channel records */ - lyr_chn = g_new0 (PSDchannel *, lyr_a[lidx]->num_channels); - for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + if (lyr_a[lidx]->clipping_group_type == 1) { - guint16 comp_mode = PSD_COMP_RAW; - - /* Allocate channel record */ - lyr_chn[cidx] = g_malloc (sizeof (PSDchannel) ); - - lyr_chn[cidx]->id = lyr_a[lidx]->chn_info[cidx].channel_id; - lyr_chn[cidx]->rows = lyr_a[lidx]->bottom - lyr_a[lidx]->top; - lyr_chn[cidx]->columns = lyr_a[lidx]->right - lyr_a[lidx]->left; - lyr_chn[cidx]->data = NULL; - - if (lyr_chn[cidx]->id == PSD_CHANNEL_EXTRA_MASK) - { - if (! psd_seek (input, lyr_a[lidx]->chn_info[cidx].data_len, - G_SEEK_CUR, error)) - { - psd_set_error (error); - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); - return -1; - } - - continue; - } - else if (lyr_chn[cidx]->id == PSD_CHANNEL_MASK) - { - /* Works around a bug in panotools psd files where the layer mask - size is given as 0 but data exists. Set mask size to layer size. - */ - if (empty_mask && lyr_a[lidx]->chn_info[cidx].data_len - 2 > 0) - { - empty_mask = FALSE; - if (lyr_a[lidx]->layer_mask.top == lyr_a[lidx]->layer_mask.bottom) - { - lyr_a[lidx]->layer_mask.top = lyr_a[lidx]->top; - lyr_a[lidx]->layer_mask.bottom = lyr_a[lidx]->bottom; - } - if (lyr_a[lidx]->layer_mask.right == lyr_a[lidx]->layer_mask.left) - { - lyr_a[lidx]->layer_mask.right = lyr_a[lidx]->right; - lyr_a[lidx]->layer_mask.left = lyr_a[lidx]->left; - } - } - lyr_chn[cidx]->rows = (lyr_a[lidx]->layer_mask.bottom - - lyr_a[lidx]->layer_mask.top); - lyr_chn[cidx]->columns = (lyr_a[lidx]->layer_mask.right - - lyr_a[lidx]->layer_mask.left); - } - - IFDBG(3) g_debug ("Channel id %d, %dx%d", - lyr_chn[cidx]->id, - lyr_chn[cidx]->columns, - lyr_chn[cidx]->rows); - - /* Only read channel data if there is any channel - * data. Note that the channel data can contain a - * compression method but no actual data. - */ - if (lyr_a[lidx]->chn_info[cidx].data_len >= COMP_MODE_SIZE) - { - if (psd_read (input, &comp_mode, COMP_MODE_SIZE, error) < COMP_MODE_SIZE) - { - psd_set_error (error); - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); - return -1; - } - - if (! img_a->ibm_pc_format) - comp_mode = GUINT16_FROM_BE (comp_mode); - else - comp_mode = GUINT16_FROM_LE (comp_mode); - IFDBG(3) g_debug ("Compression mode: %d", comp_mode); - } - if (lyr_a[lidx]->chn_info[cidx].data_len > COMP_MODE_SIZE) - { - switch (comp_mode) - { - case PSD_COMP_RAW: /* Planar raw data */ - IFDBG(3) g_debug ("Raw data length: %" G_GSIZE_FORMAT, - lyr_a[lidx]->chn_info[cidx].data_len - 2); - if (read_channel_data (lyr_chn[cidx], img_a->bps, - PSD_COMP_RAW, NULL, input, 0, - error) < 1) - { - psd_set_error (error); - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); - return -1; - } - break; - - case PSD_COMP_RLE: /* Packbits */ - if (! read_RLE_channel (img_a, lyr_chn[cidx], - lyr_a[lidx]->chn_info[cidx].data_len, - input, error)) - { - psd_set_error (error); - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); - return -1; - } - break; - - case PSD_COMP_ZIP: /* ? */ - case PSD_COMP_ZIP_PRED: - if (read_channel_data (lyr_chn[cidx], img_a->bps, - comp_mode, NULL, input, - lyr_a[lidx]->chn_info[cidx].data_len - 2, - error) < 1) - { - psd_set_error (error); - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); - return -1; - } - break; - - default: - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Unsupported compression mode: %d"), comp_mode); - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); - return -1; - break; - } - } + clipping_group = add_clipping_group (image, parent_group); } - /* Draw layer */ - - alpha = FALSE; - alpha_chn = -1; - user_mask = FALSE; - user_mask_chn = -1; - layer_channels = 0; - l_x = 0; - l_y = 0; - l_w = img_a->columns; - l_h = img_a->rows; - if (parent_group_stack->len > 0) - parent_group = g_array_index (parent_group_stack, GimpLayer *, - parent_group_stack->len - 1); - else - parent_group = NULL; /* root */ - - if (img_a->color_mode == PSD_CMYK) - base_channels = 4; - else if (img_a->color_mode == PSD_RGB || img_a->color_mode == PSD_LAB) - base_channels = 3; - else - base_channels = 1; - - IFDBG(3) g_debug ("Re-hash channel indices"); - for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + if (empty) { - IFDBG(3) g_debug ("Channel: %d - id: %d", cidx, lyr_chn[cidx]->id); - if (lyr_chn[cidx]->id == PSD_CHANNEL_MASK || - lyr_chn[cidx]->id == PSD_CHANNEL_EXTRA_MASK) - { - /* According to the specs the extra mask (which they call - * real user supplied layer mask) is used "when both a user - * mask and a vector mask are present". - * I haven't seen an example that has the extra mask, so not - * sure which of the masks would appear first. - * For now assuming that the extra mask will be first. */ - if (! user_mask) - { - user_mask = TRUE; - user_mask_chn = cidx; - } - else - { - /* Not using this mask, make sure it gets freed. */ - g_free (lyr_chn[cidx]->data); - } - } - else if (lyr_chn[cidx]->id == PSD_CHANNEL_ALPHA) - { - alpha = TRUE; - alpha_chn = cidx; - } - else if (lyr_chn[cidx]->data) - { - if (layer_channels < base_channels) - { - channel_idx[layer_channels] = cidx; /* Assumes in sane order */ - layer_channels++; /* RGB, Lab, CMYK etc. */ - } - else - { - /* channel_idx[base_channels] is reserved for alpha channel, - * but this layer apparently has extra channels. - * From the one example I have (see #8411) it looks like - * that channel is the same as the alpha channel. */ - IFDBG(2) g_debug ("This layer has an extra channel (id: %d)", lyr_chn[cidx]->id); - channel_idx[layer_channels+1] = cidx; /* Assumes in sane order */ - layer_channels += 2; /* RGB, Lab, CMYK etc. */ - } - } - else - { - IFDBG(4) g_debug ("Channel %d (id: %d) has no data", cidx, lyr_chn[cidx]->id); - } + IFDBG(2) g_debug ("Create blank layer"); } - - if (alpha) + else { - if (layer_channels <= base_channels) - { - channel_idx[layer_channels] = alpha_chn; - layer_channels++; - } - else - { - channel_idx[base_channels] = alpha_chn; - } - base_channels++; - } + IFDBG(2) g_debug ("Create normal layer"); + l_x = lyr_a[lidx]->left; + l_y = lyr_a[lidx]->top; + l_w = lyr_a[lidx]->right - lyr_a[lidx]->left; + l_h = lyr_a[lidx]->bottom - lyr_a[lidx]->top; - IFDBG(4) g_debug ("Create the layer (group type: %d, clipping group type: %d)", - lyr_a[lidx]->group_type, lyr_a[lidx]->clipping_group_type); - - /* Create the layer */ - if (lyr_a[lidx]->group_type != 0) - { - if (lyr_a[lidx]->group_type == 3) + if (l_h <= 0) { - /* the marker layers are used to - * assemble the layer structure in a single pass + /* For fill layers this can be -1 apparently; + e.g. test file CT_SkillTest_v1.psd + Mark as empty and set height to 1. */ - IFDBG(2) g_debug ("Create placeholder group layer"); - layer = gimp_layer_group_new (image); - /* add this group layer as the new parent */ - g_array_append_val (parent_group_stack, layer); + empty = TRUE; + l_h = 1; } - else /* group-type == 1 || group_type == 2 */ + if (l_w <= 0) { - if (parent_group_stack->len) - { - layer = g_array_index (parent_group_stack, GimpLayer *, - parent_group_stack->len - 1); - IFDBG(2) g_debug ("End group layer id %d.", gimp_item_get_id (GIMP_ITEM (layer))); - /* since the layers are stored in reverse, the group - * layer start marker actually means we're done with - * that layer group - */ - g_array_remove_index (parent_group_stack, - parent_group_stack->len - 1); - - gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &l_x, &l_y); - - l_w = gimp_drawable_get_width (GIMP_DRAWABLE (layer)); - l_h = gimp_drawable_get_height (GIMP_DRAWABLE (layer)); - } - else - { - IFDBG(1) g_debug ("WARNING: Unmatched group layer start marker."); - layer = NULL; - } + g_warning ("Unexpected layer width: %d", l_w); + empty = TRUE; + l_w = 1; } } - else + + image_type = get_gimp_image_type (img_a->base_type, TRUE); + IFDBG(3) g_debug ("Layer type %d", image_type); + + layer = gimp_layer_new (image, lyr_a[lidx]->name, + l_w, l_h, image_type, + 100, GIMP_LAYER_MODE_NORMAL); + } + + if (layer != NULL) + { + /* Set the layer name. Note that we do this even for group-end + * markers, to avoid having the default group name collide with + * subsequent layers; the real group name is set by the group + * start marker. + */ + if (lyr_a[lidx]->name) + gimp_item_set_name (GIMP_ITEM (layer), lyr_a[lidx]->name); + + /* Set the layer properties (skip this for layer group end + * markers; we set their properties when processing the start + * marker.) + */ + if (lyr_a[lidx]->group_type != 3) { - if (lyr_a[lidx]->clipping_group_type == 1) - { - clipping_group = add_clipping_group (image, parent_group); - } + /* Mode */ + psd_to_gimp_blend_mode (lyr_a[lidx], &mode_info); + gimp_layer_set_mode (layer, mode_info.mode); + gimp_layer_set_blend_space (layer, mode_info.blend_space); + gimp_layer_set_composite_space (layer, mode_info.composite_space); + gimp_layer_set_composite_mode (layer, mode_info.composite_mode); - if (empty) - { - IFDBG(2) g_debug ("Create blank layer"); - } - else - { - IFDBG(2) g_debug ("Create normal layer"); - l_x = lyr_a[lidx]->left; - l_y = lyr_a[lidx]->top; - l_w = lyr_a[lidx]->right - lyr_a[lidx]->left; - l_h = lyr_a[lidx]->bottom - lyr_a[lidx]->top; + /* Opacity */ + gimp_layer_set_opacity (layer, + lyr_a[lidx]->opacity * 100.0 / 255.0); - if (l_h <= 0) - { - /* For fill layers this can be -1 apparently; - e.g. test file CT_SkillTest_v1.psd - Mark as empty and set height to 1. - */ - empty = TRUE; - l_h = 1; - } - if (l_w <= 0) - { - g_warning ("Unexpected layer width: %d", l_w); - empty = TRUE; - l_w = 1; - } - } - - image_type = get_gimp_image_type (img_a->base_type, TRUE); - IFDBG(3) g_debug ("Layer type %d", image_type); - - layer = gimp_layer_new (image, lyr_a[lidx]->name, - l_w, l_h, image_type, - 100, GIMP_LAYER_MODE_NORMAL); - } - - if (layer != NULL) - { - /* Set the layer name. Note that we do this even for group-end - * markers, to avoid having the default group name collide with - * subsequent layers; the real group name is set by the group - * start marker. - */ - if (lyr_a[lidx]->name) - gimp_item_set_name (GIMP_ITEM (layer), lyr_a[lidx]->name); - - /* Set the layer properties (skip this for layer group end - * markers; we set their properties when processing the start - * marker.) - */ - if (lyr_a[lidx]->group_type != 3) - { - /* Mode */ - psd_to_gimp_blend_mode (lyr_a[lidx], &mode_info); - gimp_layer_set_mode (layer, mode_info.mode); - gimp_layer_set_blend_space (layer, mode_info.blend_space); - gimp_layer_set_composite_space (layer, mode_info.composite_space); - gimp_layer_set_composite_mode (layer, mode_info.composite_mode); - - /* Opacity */ - gimp_layer_set_opacity (layer, - lyr_a[lidx]->opacity * 100.0 / 255.0); - - /* Flags */ - gimp_layer_set_lock_alpha (layer, lyr_a[lidx]->layer_flags.trans_prot); - gimp_item_set_visible (GIMP_ITEM (layer), lyr_a[lidx]->layer_flags.visible); + /* Flags */ + gimp_layer_set_lock_alpha (layer, lyr_a[lidx]->layer_flags.trans_prot); + gimp_item_set_visible (GIMP_ITEM (layer), lyr_a[lidx]->layer_flags.visible); #if 0 - /* according to the spec, the 'irrelevant' flag indicates - * that the layer's "pixel data is irrelevant to the - * appearance of the document". what this seems to mean is - * not that the layer doesn't contribute to the image, but - * rather that its appearance can be entirely derived from - * sources other than the pixel data, such as vector data. - * in particular, this doesn't mean that the layer is - * invisible. since we only support raster layers atm, we can - * just ignore this flag. - */ - if (lyr_a[lidx]->layer_flags.irrelevant && - lyr_a[lidx]->group_type == 0) - { - gimp_item_set_visible (GIMP_ITEM (layer), FALSE); - } + /* according to the spec, the 'irrelevant' flag indicates + * that the layer's "pixel data is irrelevant to the + * appearance of the document". what this seems to mean is + * not that the layer doesn't contribute to the image, but + * rather that its appearance can be entirely derived from + * sources other than the pixel data, such as vector data. + * in particular, this doesn't mean that the layer is + * invisible. since we only support raster layers atm, we can + * just ignore this flag. + */ + if (lyr_a[lidx]->layer_flags.irrelevant && + lyr_a[lidx]->group_type == 0) + { + gimp_item_set_visible (GIMP_ITEM (layer), FALSE); + } #endif - /* Position */ - if (l_x != 0 || l_y != 0) - gimp_layer_set_offsets (layer, l_x, l_y); + /* Position */ + if (l_x != 0 || l_y != 0) + gimp_layer_set_offsets (layer, l_x, l_y); - /* Color tag */ - gimp_item_set_color_tag (GIMP_ITEM (layer), - psd_to_gimp_layer_color_tag (lyr_a[lidx]->color_tag[0])); + /* Color tag */ + gimp_item_set_color_tag (GIMP_ITEM (layer), + psd_to_gimp_layer_color_tag (lyr_a[lidx]->color_tag[0])); - /* Tattoo */ - if (lyr_a[lidx]->id) - gimp_item_set_tattoo (GIMP_ITEM (layer), lyr_a[lidx]->id); + /* Tattoo */ + if (lyr_a[lidx]->id) + gimp_item_set_tattoo (GIMP_ITEM (layer), lyr_a[lidx]->id); - /* For layer groups, expand or collapse the group */ - if (lyr_a[lidx]->group_type != 0) - { - gimp_item_set_expanded (GIMP_ITEM (layer), - lyr_a[lidx]->group_type == 1); - } - } - - /* Remember the selected layers: - * - Layer Selection ID(s) (0x042D) are prioritary; - * - Layer state information (0x0400) is used instead - * otherwise. - */ - if (img_a->layer_selection) + /* For layer groups, expand or collapse the group */ + if (lyr_a[lidx]->group_type != 0) { - if (g_list_find (img_a->layer_selection, GINT_TO_POINTER (lyr_a[lidx]->id)) != NULL) - { - selected_layers = g_list_prepend (selected_layers, layer); - } + gimp_item_set_expanded (GIMP_ITEM (layer), + lyr_a[lidx]->group_type == 1); } - else if (lidx == img_a->layer_state) + } + + /* Remember the selected layers: + * - Layer Selection ID(s) (0x042D) are prioritary; + * - Layer state information (0x0400) is used instead + * otherwise. + */ + if (img_a->layer_selection) + { + if (g_list_find (img_a->layer_selection, GINT_TO_POINTER (lyr_a[lidx]->id)) != NULL) { selected_layers = g_list_prepend (selected_layers, layer); } + } + else if (lidx == img_a->layer_state) + { + selected_layers = g_list_prepend (selected_layers, layer); + } - /* Set the layer data */ - if (lyr_a[lidx]->group_type == 0) + /* Set the layer data */ + if (lyr_a[lidx]->group_type == 0) + { + IFDBG(3) g_debug ("Draw layer"); + + if (empty) { - IFDBG(3) g_debug ("Draw layer"); + gimp_drawable_fill (GIMP_DRAWABLE (layer), GIMP_FILL_TRANSPARENT); + } + else + { + GeglBufferIterator *iter; - if (empty) + bps = img_a->bps / 8; + if (bps == 0) + bps++; + + buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)); + + iter = gegl_buffer_iterator_new ( + buffer, NULL, 0, get_layer_format (img_a, alpha), + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (iter)) { - gimp_drawable_fill (GIMP_DRAWABLE (layer), GIMP_FILL_TRANSPARENT); - } - else - { - GeglBufferIterator *iter; + const GeglRectangle *roi = &iter->items[0].roi; + guint8 *dst0 = iter->items[0].data; + gint src_step = bps; + gint dst_step = bps * base_channels; - bps = img_a->bps / 8; - if (bps == 0) - bps++; - - buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)); - - iter = gegl_buffer_iterator_new ( - buffer, NULL, 0, get_layer_format (img_a, alpha), - GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); - - while (gegl_buffer_iterator_next (iter)) + if (img_a->color_mode == PSD_CMYK || img_a->color_mode == PSD_LAB) { - const GeglRectangle *roi = &iter->items[0].roi; - guint8 *dst0 = iter->items[0].data; - gint src_step = bps; - gint dst_step = bps * base_channels; + dst0 = gegl_scratch_alloc (base_channels * bps * + iter->length); + } - if (img_a->color_mode == PSD_CMYK || img_a->color_mode == PSD_LAB) + for (cidx = 0; cidx < base_channels; ++cidx) + { + gint b; + + if (roi->x == 0 && roi->y == 0) + IFDBG(3) g_debug ("Start channel %d", channel_idx[cidx]); + + for (b = 0; b < bps; ++b) { - dst0 = gegl_scratch_alloc (base_channels * bps * - iter->length); - } + guint8 *dst; + gint y; - for (cidx = 0; cidx < base_channels; ++cidx) - { - gint b; + dst = &dst0[cidx * bps + b]; - if (roi->x == 0 && roi->y == 0) - IFDBG(3) g_debug ("Start channel %d", channel_idx[cidx]); - - for (b = 0; b < bps; ++b) + for (y = 0; y < roi->height; y++) { - guint8 *dst; - gint y; + const guint8 *src; + gint x; - dst = &dst0[cidx * bps + b]; + src = (const guint8 *) + &lyr_chn[channel_idx[cidx]]->data[ + ((roi->y + y) * l_w + + roi->x) * bps + + b]; - for (y = 0; y < roi->height; y++) + for (x = 0; x < roi->width; ++x) { - const guint8 *src; - gint x; + *dst = *src; - src = (const guint8 *) - &lyr_chn[channel_idx[cidx]]->data[ - ((roi->y + y) * l_w + - roi->x) * bps + - b]; - - for (x = 0; x < roi->width; ++x) - { - *dst = *src; - - src += src_step; - dst += dst_step; - } + src += src_step; + dst += dst_step; } } } - - if (img_a->color_mode == PSD_CMYK) - { - psd_convert_cmyk_to_srgb ( - img_a, - iter->items[0].data, dst0, - roi->width, roi->height, - alpha, error); - - gegl_scratch_free (dst0); - } - else if (img_a->color_mode == PSD_LAB) - { - psd_convert_lab_to_srgb ( - img_a, - iter->items[0].data, dst0, - roi->width, roi->height, - alpha); - - gegl_scratch_free (dst0); - } } - for (cidx = 0; cidx < layer_channels; ++cidx) - g_free (lyr_chn[channel_idx[cidx]]->data); + if (img_a->color_mode == PSD_CMYK) + { + psd_convert_cmyk_to_srgb ( + img_a, + iter->items[0].data, dst0, + roi->width, roi->height, + alpha, error); - g_object_unref (buffer); + gegl_scratch_free (dst0); + } + else if (img_a->color_mode == PSD_LAB) + { + psd_convert_lab_to_srgb ( + img_a, + iter->items[0].data, dst0, + roi->width, roi->height, + alpha); + + gegl_scratch_free (dst0); + } } - } - /* Layer mask */ - if (user_mask && lyr_a[lidx]->group_type != 3) + for (cidx = 0; cidx < layer_channels; ++cidx) + g_free (lyr_chn[channel_idx[cidx]]->data); + + g_object_unref (buffer); + } + } + + /* Layer mask */ + if (user_mask && lyr_a[lidx]->group_type != 3) + { + if (empty_mask) { - if (empty_mask) + IFDBG(3) g_debug ("Create empty mask"); + if (lyr_a[lidx]->layer_mask.def_color == 255) + mask = gimp_layer_create_mask (layer, + GIMP_ADD_MASK_WHITE); + else + mask = gimp_layer_create_mask (layer, + GIMP_ADD_MASK_BLACK); + gimp_layer_add_mask (layer, mask); + gimp_layer_set_apply_mask (layer, + ! lyr_a[lidx]->layer_mask.mask_flags.disabled); + } + else + { + GeglRectangle mask_rect; + + /* Load layer mask data */ + lm_x = lyr_a[lidx]->layer_mask.left - l_x; + lm_y = lyr_a[lidx]->layer_mask.top - l_y; + lm_w = lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left; + lm_h = lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top; + IFDBG(3) g_debug ("Mask channel index %d", user_mask_chn); + IFDBG(3) g_debug ("Original Mask %d %d %d %d", lm_x, lm_y, lm_w, lm_h); + /* Crop mask at layer boundary, and draw layer mask data, + * if any + */ + if (gegl_rectangle_intersect ( + &mask_rect, + GEGL_RECTANGLE (0, 0, l_w, l_h), + GEGL_RECTANGLE (lm_x, lm_y, lm_w, lm_h))) { - IFDBG(3) g_debug ("Create empty mask"); + IFDBG(3) g_debug ("Layer %d %d %d %d", l_x, l_y, l_w, l_h); + IFDBG(3) g_debug ("Mask %d %d %d %d", + mask_rect.x, mask_rect.y, + mask_rect.width, mask_rect.height); + if (lyr_a[lidx]->layer_mask.def_color == 255) mask = gimp_layer_create_mask (layer, GIMP_ADD_MASK_WHITE); else mask = gimp_layer_create_mask (layer, GIMP_ADD_MASK_BLACK); + + bps = img_a->bps / 8; + if (bps == 0) + bps++; + + IFDBG(3) g_debug ("New layer mask %d", gimp_item_get_id (GIMP_ITEM (mask))); gimp_layer_add_mask (layer, mask); + buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)); + gegl_buffer_set (buffer, + &mask_rect, + 0, get_mask_format (img_a), + lyr_chn[user_mask_chn]->data + ( + (mask_rect.y - lm_y) * lm_w + + (mask_rect.x - lm_x)) * bps, + lm_w * bps); + g_object_unref (buffer); gimp_layer_set_apply_mask (layer, ! lyr_a[lidx]->layer_mask.mask_flags.disabled); } - else - { - GeglRectangle mask_rect; - - /* Load layer mask data */ - lm_x = lyr_a[lidx]->layer_mask.left - l_x; - lm_y = lyr_a[lidx]->layer_mask.top - l_y; - lm_w = lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left; - lm_h = lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top; - IFDBG(3) g_debug ("Mask channel index %d", user_mask_chn); - IFDBG(3) g_debug ("Original Mask %d %d %d %d", lm_x, lm_y, lm_w, lm_h); - /* Crop mask at layer boundary, and draw layer mask data, - * if any - */ - if (gegl_rectangle_intersect ( - &mask_rect, - GEGL_RECTANGLE (0, 0, l_w, l_h), - GEGL_RECTANGLE (lm_x, lm_y, lm_w, lm_h))) - { - IFDBG(3) g_debug ("Layer %d %d %d %d", l_x, l_y, l_w, l_h); - IFDBG(3) g_debug ("Mask %d %d %d %d", - mask_rect.x, mask_rect.y, - mask_rect.width, mask_rect.height); - - if (lyr_a[lidx]->layer_mask.def_color == 255) - mask = gimp_layer_create_mask (layer, - GIMP_ADD_MASK_WHITE); - else - mask = gimp_layer_create_mask (layer, - GIMP_ADD_MASK_BLACK); - - bps = img_a->bps / 8; - if (bps == 0) - bps++; - - IFDBG(3) g_debug ("New layer mask %d", gimp_item_get_id (GIMP_ITEM (mask))); - gimp_layer_add_mask (layer, mask); - buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)); - gegl_buffer_set (buffer, - &mask_rect, - 0, get_mask_format (img_a), - lyr_chn[user_mask_chn]->data + ( - (mask_rect.y - lm_y) * lm_w + - (mask_rect.x - lm_x)) * bps, - lm_w * bps); - g_object_unref (buffer); - gimp_layer_set_apply_mask (layer, - ! lyr_a[lidx]->layer_mask.mask_flags.disabled); - } - g_free (lyr_chn[user_mask_chn]->data); - } + g_free (lyr_chn[user_mask_chn]->data); } - - /* Insert the layer */ - if (lyr_a[lidx]->group_type == 0 || /* normal layer */ - lyr_a[lidx]->group_type == 3 /* group layer end marker */) - { - if (clipping_group) - { - gimp_image_insert_layer (image, layer, clipping_group, 0); - - if (lyr_a[lidx]->clipping_group_type == 2) - { - /* End of our clipping group. */ - clipping_group = NULL; - } - } - else - { - gimp_image_insert_layer (image, layer, parent_group, 0); - } - } } - free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + /* Insert the layer */ + if (lyr_a[lidx]->group_type == 0 || /* normal layer */ + lyr_a[lidx]->group_type == 3 /* group layer end marker */) + { + if (clipping_group) + { + gimp_image_insert_layer (image, layer, clipping_group, 0); + + if (lyr_a[lidx]->clipping_group_type == 2) + { + /* End of our clipping group. */ + clipping_group = NULL; + } + } + else + { + gimp_image_insert_layer (image, layer, parent_group, 0); + } + } } + + free_lyr_chn (lyr_chn, lyr_a[lidx]->num_channels); + g_free (lyr_a[lidx]->chn_info); g_free (lyr_a[lidx]->name); g_free (lyr_a[lidx]); @@ -3621,11 +3603,11 @@ load_dialog (const gchar *title, if (unsupported_features->adjustment_layer) ADD_UNSUPPORTED_MESSAGE (message, "Adjustment layers are not yet " - "supported and will be dropped."); + "supported and will show up as empty layers."); if (unsupported_features->psd_metadata) ADD_UNSUPPORTED_MESSAGE (message, - "Metadata non-raster layers are " - "not yet supported and will be dropped."); + "Metadata non-raster layers are not yet " + "supported and will show up as empty layers."); if (unsupported_features->fill_layer) ADD_UNSUPPORTED_MESSAGE (message, "Fill layers are partially " @@ -3639,7 +3621,7 @@ load_dialog (const gchar *title, if (unsupported_features->linked_layer) ADD_UNSUPPORTED_MESSAGE (message, "Linked layers are not yet " - "supported and will be dropped."); + "supported and will show up as empty layers."); if (unsupported_features->vector_mask) ADD_UNSUPPORTED_MESSAGE (message, "Vector masks are partially " @@ -3648,20 +3630,20 @@ load_dialog (const gchar *title, if (unsupported_features->stroke) ADD_UNSUPPORTED_MESSAGE (message, "Vector strokes are not yet " - "supported and will be dropped."); + "supported and will show up as empty layers."); if (unsupported_features->layer_effect) ADD_UNSUPPORTED_MESSAGE (message, "Layer effects are not yet " - "supported and will be dropped."); + "supported and will show up as empty layers."); if (unsupported_features->smart_object) ADD_UNSUPPORTED_MESSAGE (message, "Smart objects are not yet " - "supported and will be dropped."); + "supported and will show up as empty layers."); if (unsupported_features->layer_comp) ADD_UNSUPPORTED_MESSAGE (message, /* Translators: short for layer compositions */ "Layer comps are not yet " - "supported and will be dropped."); + "supported and will show up as empty layers."); #undef ADD_UNSUPPORTED_MESSAGE scrolled_window = gtk_scrolled_window_new (NULL, NULL); diff --git a/plug-ins/file-psd/psd.c b/plug-ins/file-psd/psd.c index a0e5034624..616cdf1da9 100644 --- a/plug-ins/file-psd/psd.c +++ b/plug-ins/file-psd/psd.c @@ -540,7 +540,7 @@ psd_load_metadata (GimpProcedure *procedure, default: g_printerr ("[%s] %s\n", "file-psd-load-metadata", _("Metadata fill layers are not supported " - "and will be dropped.")); + "and will show up as empty layers.")); break; } diff --git a/plug-ins/file-psd/psd.h b/plug-ins/file-psd/psd.h index 9c41ca8bf1..4528ef72ab 100644 --- a/plug-ins/file-psd/psd.h +++ b/plug-ins/file-psd/psd.h @@ -28,7 +28,7 @@ #define PSD_DEBUG 3 #define IFDBG(level) if (PSD_DEBUG >= level) -/* Set to FALSE to suppress pop-up warnings about lossy file conversions */ +/* Set to FALSE to suppress pop-up warnings about lossy blend mode conversions */ #define CONVERSION_WARNINGS FALSE #define LOAD_PROC "file-psd-load" @@ -601,7 +601,6 @@ typedef struct /* PSD Layer data structure */ typedef struct { - gboolean drop; /* Do not add layer to GIMP image */ gint32 top; /* Layer top */ gint32 left; /* Layer left */ gint32 bottom; /* Layer bottom */