Submitted By: Joe Locash Date: 2026-02-19 Initial Package Version: 3.0.6 Upstream Status: Committed Origin: Upstream commits: - d6dba93bb5b6eea4216ac5b34f30d2a90799361b - 8cf2772f5631719ae0e4e701bd7ef793b1f59cfa - 02886e626df5e4c5f73f838a64fd3f21809dda09 - f876094768501f1d55f392117846efac465e9599 - d9d0f5b4e642dd5b101e70728042027d568bb01d - 058ada8f3ffc0a42b7dd1561a8817c8cc83b7d2a Description: Fixes 5 security vulnerabilites, 3 of which have been assigned: CVE-2026-2239, CVE-2026-2271, and CVE-2026-2272. diff -Nuar gimp-3.0.6.orig/plug-ins/common/file-psp.c gimp-3.0.6/plug-ins/common/file-psp.c --- gimp-3.0.6.orig/plug-ins/common/file-psp.c 2025-10-05 13:14:02.000000000 -0400 +++ gimp-3.0.6/plug-ins/common/file-psp.c 2026-02-19 13:51:17.386225232 -0500 @@ -1121,7 +1121,17 @@ } keyword = GUINT16_FROM_LE (keyword); length = GUINT32_FROM_LE (length); - switch (keyword) + + if ((goffset) ftell (f) + length > (goffset) data_start + total_len) + { + /* FIXME: After string freeze is over, we should consider changing + * this error message to be a bit more descriptive. */ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading creator keyword data")); + return -1; + } + + switch (keyword) { case PSP_CRTR_FLD_TITLE: case PSP_CRTR_FLD_ARTIST: diff -Nuar gimp-3.0.6.orig/plug-ins/file-ico/ico-load.c gimp-3.0.6/plug-ins/file-ico/ico-load.c --- gimp-3.0.6.orig/plug-ins/file-ico/ico-load.c 2025-10-05 13:14:02.000000000 -0400 +++ gimp-3.0.6/plug-ins/file-ico/ico-load.c 2026-02-19 13:51:38.462022365 -0500 @@ -426,6 +426,7 @@ gint *height) { IcoFileDataHeader data; + gsize data_size; gint length; gint x, y, w, h; guchar *xor_map, *and_map; @@ -471,7 +472,9 @@ return FALSE; } - if (data.width * data.height * 2 > maxsize) + if (! g_size_checked_mul (&data_size, data.width, data.height) || + ! g_size_checked_mul (&data_size, data_size, 2) || + data_size > maxsize) { D(("skipping image: too large\n")); return FALSE; @@ -726,7 +729,14 @@ image = gimp_image_new (max_width, max_height, GIMP_RGB); maxsize = max_width * max_height * 4; - buf = g_new (guchar, max_width * max_height * 4); + buf = g_try_new (guchar, maxsize); + if (! buf) + { + g_free (info); + fclose (fp); + return NULL; + } + for (i = 0; i < icon_count; i++) { GimpLayer *layer; diff -Nuar gimp-3.0.6.orig/plug-ins/file-ico/ico-load.c.orig gimp-3.0.6/plug-ins/file-ico/ico-load.c.orig --- gimp-3.0.6.orig/plug-ins/file-ico/ico-load.c.orig 1969-12-31 19:00:00.000000000 -0500 +++ gimp-3.0.6/plug-ins/file-ico/ico-load.c.orig 2025-10-05 13:14:02.000000000 -0400 @@ -0,0 +1,1101 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis + * + * GIMP Plug-in for Windows Icon files. + * Copyright (C) 2002 Christian Kreibich . + * + * 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 3 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 "config.h" + +#include +#include + +#include + +#include +#include + +#include + +/* #define ICO_DBG */ + +#include "ico.h" +#include "ico-load.h" + +#include "libgimp/stdplugins-intl.h" + + +#define A_VAL(p) ((guchar *)(p))[3] +#define R_VAL(p) ((guchar *)(p))[2] +#define G_VAL(p) ((guchar *)(p))[1] +#define B_VAL(p) ((guchar *)(p))[0] + +#define A_VAL_GIMP(p) ((guchar *)(p))[3] +#define R_VAL_GIMP(p) ((guchar *)(p))[0] +#define G_VAL_GIMP(p) ((guchar *)(p))[1] +#define B_VAL_GIMP(p) ((guchar *)(p))[2] + + +static gint ico_read_int8 (FILE *fp, + guint8 *data, + gint count); +static gint ico_read_int16 (FILE *fp, + guint16 *data, + gint count); +static gint ico_read_int32 (FILE *fp, + guint32 *data, + gint count); + +static gint +ico_read_int32 (FILE *fp, + guint32 *data, + gint count) +{ + gint i, total; + + total = count; + if (count > 0) + { + ico_read_int8 (fp, (guint8 *) data, count * 4); + for (i = 0; i < count; i++) + data[i] = GUINT32_FROM_LE (data[i]); + } + + return total * 4; +} + + +static gint +ico_read_int16 (FILE *fp, + guint16 *data, + gint count) +{ + gint i, total; + + total = count; + if (count > 0) + { + ico_read_int8 (fp, (guint8 *) data, count * 2); + for (i = 0; i < count; i++) + data[i] = GUINT16_FROM_LE (data[i]); + } + + return total * 2; +} + + +static gint +ico_read_int8 (FILE *fp, + guint8 *data, + gint count) +{ + gint total; + gint bytes; + + total = count; + while (count > 0) + { + bytes = fread ((gchar *) data, sizeof (gchar), count, fp); + if (bytes <= 0) /* something bad happened */ + break; + + count -= bytes; + data += bytes; + } + + return total; +} + + +static IcoFileHeader +ico_read_init (FILE *fp) +{ + IcoFileHeader header; + + /* read and check file header */ + if (! ico_read_int16 (fp, &header.reserved, 1) || + ! ico_read_int16 (fp, &header.resource_type, 1) || + ! ico_read_int16 (fp, &header.icon_count, 1) || + header.reserved != 0 || + (header.resource_type != 1 && header.resource_type != 2)) + { + header.icon_count = 0; + return header; + } + + return header; +} + + +static gboolean +ico_read_size (FILE *fp, + gint32 file_offset, + IcoLoadInfo *info) +{ + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 w, h; + gint32 bpp; + gint32 color_type; + guint32 magic; + + if (fseek (fp, info->offset + file_offset, SEEK_SET) < 0) + return FALSE; + + ico_read_int32 (fp, &magic, 1); + + if (magic == ICO_PNG_MAGIC) + { + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, + NULL); + if (! png_ptr) + return FALSE; + + info_ptr = png_create_info_struct (png_ptr); + if (! info_ptr) + { + png_destroy_read_struct (&png_ptr, NULL, NULL); + return FALSE; + } + + if (setjmp (png_jmpbuf (png_ptr))) + { + png_destroy_read_struct (&png_ptr, NULL, NULL); + return FALSE; + } + png_init_io (png_ptr, fp); + png_set_sig_bytes (png_ptr, 4); + png_read_info (png_ptr, info_ptr); + png_get_IHDR (png_ptr, info_ptr, &w, &h, &bpp, &color_type, + NULL, NULL, NULL); + png_destroy_read_struct (&png_ptr, &info_ptr, NULL); + info->width = w; + info->height = h; + D(("ico_read_size: PNG: %ix%i\n", info->width, info->height)); + return TRUE; + } + else if (magic == 40) + { + if (ico_read_int32 (fp, &info->width, 1) && + ico_read_int32 (fp, &info->height, 1)) + { + info->height /= 2; + D(("ico_read_size: ICO: %ix%i\n", info->width, info->height)); + return TRUE; + } + else + { + info->width = 0; + info->height = 0; + return FALSE; + } + } + return FALSE; +} + +static IcoLoadInfo* +ico_read_info (FILE *fp, + gint icon_count, + gint32 file_offset, + GError **error) +{ + gint i; + IcoFileEntry *entries; + IcoLoadInfo *info; + + /* read icon entries */ + entries = g_new (IcoFileEntry, icon_count); + if (fread (entries, sizeof (IcoFileEntry), icon_count, fp) <= 0) + { + g_set_error (error, G_FILE_ERROR, 0, + _("Could not read '%lu' bytes"), + (long unsigned int) sizeof (IcoFileEntry)); + g_free (entries); + return NULL; + } + + info = g_new (IcoLoadInfo, icon_count); + for (i = 0; i < icon_count; i++) + { + info[i].width = entries[i].width; + info[i].height = entries[i].height; + info[i].planes = entries[i].planes; + info[i].bpp = GUINT16_FROM_LE (entries[i].bpp); + info[i].size = GUINT32_FROM_LE (entries[i].size); + info[i].offset = GUINT32_FROM_LE (entries[i].offset); + + if (info[i].width == 0 || info[i].height == 0) + { + ico_read_size (fp, file_offset, info + i); + } + + D(("ico_read_info: %ix%i (%i bits, size: %i, offset: %i)\n", + info[i].width, info[i].height, info[i].bpp, + info[i].size, info[i].offset)); + + if (info[i].width == 0 || info[i].height == 0) + { + g_set_error (error, G_FILE_ERROR, 0, + _("Icon #%d has zero width or height"), i); + g_free (info); + g_free (entries); + return NULL; + } + } + + g_free (entries); + + return info; +} + +static gboolean +ico_read_png (FILE *fp, + guint32 header, + guchar *buf, + gint maxsize, + gint *width, + gint *height) +{ + png_structp png_ptr; + png_infop info; + png_uint_32 w; + png_uint_32 h; + gint32 bit_depth; + gint32 color_type; + guint32 **rows; + gint i; + + png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (! png_ptr) + return FALSE; + info = png_create_info_struct (png_ptr); + if (! info) + { + png_destroy_read_struct (&png_ptr, NULL, NULL); + return FALSE; + } + + if (setjmp (png_jmpbuf (png_ptr))) + { + png_destroy_read_struct (&png_ptr, &info, NULL); + return FALSE; + } + + png_init_io (png_ptr, fp); + png_set_sig_bytes (png_ptr, 4); + png_read_info (png_ptr, info); + png_get_IHDR (png_ptr, info, &w, &h, &bit_depth, &color_type, + NULL, NULL, NULL); + /* Check for overflow */ + if ((w * h * 4) < w || + (w * h * 4) < h || + (w * h * 4) < (w * h) || + (w * h * 4) > maxsize) + { + png_destroy_read_struct (&png_ptr, &info, NULL); + return FALSE; + } + D(("ico_read_png: %ix%i, %i bits, %i type\n", (gint)w, (gint)h, + bit_depth, color_type)); + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY: + png_set_expand_gray_1_2_4_to_8 (png_ptr); + if (bit_depth == 16) + png_set_strip_16 (png_ptr); + png_set_gray_to_rgb (png_ptr); + png_set_add_alpha (png_ptr, 0xff, PNG_FILLER_AFTER); + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + png_set_expand_gray_1_2_4_to_8 (png_ptr); + if (bit_depth == 16) + png_set_strip_16 (png_ptr); + png_set_gray_to_rgb (png_ptr); + break; + case PNG_COLOR_TYPE_PALETTE: + png_set_palette_to_rgb (png_ptr); + if (png_get_valid (png_ptr, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha (png_ptr); + else + png_set_add_alpha (png_ptr, 0xff, PNG_FILLER_AFTER); + break; + case PNG_COLOR_TYPE_RGB: + if (bit_depth == 16) + png_set_strip_16 (png_ptr); + png_set_add_alpha (png_ptr, 0xff, PNG_FILLER_AFTER); + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + if (bit_depth == 16) + png_set_strip_16 (png_ptr); + break; + } + + *width = w; + *height = h; + rows = g_new (guint32*, h); + rows[0] = (guint32*) buf; + for (i = 1; i < h; i++) + rows[i] = rows[i-1] + w; + png_read_image (png_ptr, (png_bytepp) rows); + png_destroy_read_struct (&png_ptr, &info, NULL); + g_free (rows); + return TRUE; +} + +gint +ico_get_bit_from_data (const guint8 *data, + gint line_width, + gint bit) +{ + gint line; + gint width32; + gint offset; + gint result; + + /* width per line in multiples of 32 bits */ + width32 = (line_width % 32 == 0 ? line_width/32 : line_width/32 + 1); + line = bit / line_width; + offset = bit % line_width; + + result = (data[line * width32 * 4 + offset/8] & (1 << (7 - (offset % 8)))); + + return (result ? 1 : 0); +} + + +gint +ico_get_nibble_from_data (const guint8 *data, + gint line_width, + gint nibble) +{ + gint line; + gint width32; + gint offset; + gint result; + + /* width per line in multiples of 32 bits */ + width32 = (line_width % 8 == 0 ? line_width/8 : line_width/8 + 1); + line = nibble / line_width; + offset = nibble % line_width; + + result = + (data[line * width32 * 4 + offset/2] & (0x0F << (4 * (1 - offset % 2)))); + + if (offset % 2 == 0) + result = result >> 4; + + return result; +} + +gint +ico_get_byte_from_data (const guint8 *data, + gint line_width, + gint byte) +{ + gint line; + gint width32; + gint offset; + + /* width per line in multiples of 32 bits */ + width32 = (line_width % 4 == 0 ? line_width / 4 : line_width / 4 + 1); + line = byte / line_width; + offset = byte % line_width; + + return data[line * width32 * 4 + offset]; +} + +static gboolean +ico_read_icon (FILE *fp, + guint32 header_size, + guchar *buf, + gint maxsize, + gint *width, + gint *height) +{ + IcoFileDataHeader data; + gint length; + gint x, y, w, h; + guchar *xor_map, *and_map; + guint32 *palette; + guint32 *dest_vec; + guchar *row; + gint rowstride; + + palette = NULL; + + data.header_size = header_size; + ico_read_int32 (fp, &data.width, 1); + ico_read_int32 (fp, &data.height, 1); + ico_read_int16 (fp, &data.planes, 1); + ico_read_int16 (fp, &data.bpp, 1); + ico_read_int32 (fp, &data.compression, 1); + ico_read_int32 (fp, &data.image_size, 1); + ico_read_int32 (fp, &data.x_res, 1); + ico_read_int32 (fp, &data.y_res, 1); + ico_read_int32 (fp, &data.used_clrs, 1); + ico_read_int32 (fp, &data.important_clrs, 1); + + D((" header size %i, " + "w %i, h %i, planes %i, size %i, bpp %i, used %i, imp %i.\n", + data.header_size, data.width, data.height, + data.planes, data.image_size, data.bpp, + data.used_clrs, data.important_clrs)); + + if (data.planes != 1 || + data.compression != 0) + { + D(("skipping image: invalid header\n")); + return FALSE; + } + + if (data.bpp != 1 && + data.bpp != 4 && + data.bpp != 8 && + data.bpp != 24 && + data.bpp != 32) + { + D(("skipping image: invalid depth: %i\n", data.bpp)); + return FALSE; + } + + if (data.width * data.height * 2 > maxsize) + { + D(("skipping image: too large\n")); + return FALSE; + } + + w = data.width; + h = data.height / 2; + + if (data.bpp <= 8) + { + if (data.used_clrs == 0) + data.used_clrs = (1 << data.bpp); + + D((" allocating a %i-slot palette for %i bpp.\n", + data.used_clrs, data.bpp)); + + palette = g_new0 (guint32, data.used_clrs); + ico_read_int8 (fp, (guint8 *) palette, data.used_clrs * 4); + } + + xor_map = ico_alloc_map (w, h, data.bpp, &length); + ico_read_int8 (fp, xor_map, length); + D((" length of xor_map: %i\n", length)); + + /* Read in and_map. It's padded out to 32 bits per line: */ + and_map = ico_alloc_map (w, h, 1, &length); + ico_read_int8 (fp, and_map, length); + D((" length of and_map: %i\n", length)); + + dest_vec = (guint32 *) buf; + switch (data.bpp) + { + case 1: + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + { + guint32 color = palette[ico_get_bit_from_data (xor_map, + w, y * w + x)]; + guint32 *dest = dest_vec + (h - 1 - y) * w + x; + + R_VAL_GIMP (dest) = R_VAL (&color); + G_VAL_GIMP (dest) = G_VAL (&color); + B_VAL_GIMP (dest) = B_VAL (&color); + + if (ico_get_bit_from_data (and_map, w, y * w + x)) + A_VAL_GIMP (dest) = 0; + else + A_VAL_GIMP (dest) = 255; + } + break; + + case 4: + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + { + guint32 color = palette[ico_get_nibble_from_data (xor_map, + w, y * w + x)]; + guint32 *dest = dest_vec + (h - 1 - y) * w + x; + + R_VAL_GIMP (dest) = R_VAL (&color); + G_VAL_GIMP (dest) = G_VAL (&color); + B_VAL_GIMP (dest) = B_VAL (&color); + + if (ico_get_bit_from_data (and_map, w, y * w + x)) + A_VAL_GIMP (dest) = 0; + else + A_VAL_GIMP (dest) = 255; + } + break; + + case 8: + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + { + guint32 color = palette[ico_get_byte_from_data (xor_map, + w, y * w + x)]; + guint32 *dest = dest_vec + (h - 1 - y) * w + x; + + R_VAL_GIMP (dest) = R_VAL (&color); + G_VAL_GIMP (dest) = G_VAL (&color); + B_VAL_GIMP (dest) = B_VAL (&color); + + if (ico_get_bit_from_data (and_map, w, y * w + x)) + A_VAL_GIMP (dest) = 0; + else + A_VAL_GIMP (dest) = 255; + } + break; + + default: + { + gint bytespp = data.bpp / 8; + + rowstride = ico_rowstride (w, data.bpp); + + for (y = 0; y < h; y++) + { + row = xor_map + rowstride * y; + + for (x = 0; x < w; x++) + { + guint32 *dest = dest_vec + (h - 1 - y) * w + x; + + B_VAL_GIMP (dest) = row[0]; + G_VAL_GIMP (dest) = row[1]; + R_VAL_GIMP (dest) = row[2]; + + if (data.bpp < 32) + { + if (ico_get_bit_from_data (and_map, w, y * w + x)) + A_VAL_GIMP (dest) = 0; + else + A_VAL_GIMP (dest) = 255; + } + else + { + A_VAL_GIMP (dest) = row[3]; + } + + row += bytespp; + } + } + } + } + if (palette) + g_free (palette); + g_free (xor_map); + g_free (and_map); + *width = w; + *height = h; + return TRUE; +} + +static GimpLayer * +ico_load_layer (FILE *fp, + GimpImage *image, + gint32 icon_num, + guchar *buf, + gint maxsize, + gint32 file_offset, + gchar *layer_prefix, + IcoLoadInfo *info) +{ + gint width, height; + GimpLayer *layer; + guint32 first_bytes; + GeglBuffer *buffer; + + if (fseek (fp, info->offset + file_offset, SEEK_SET) < 0 || + ! ico_read_int32 (fp, &first_bytes, 1)) + return NULL; + + if (first_bytes == ICO_PNG_MAGIC) + { + if (!ico_read_png (fp, first_bytes, buf, maxsize, &width, &height)) + return NULL; + } + else if (first_bytes == 40) + { + if (!ico_read_icon (fp, first_bytes, buf, maxsize, &width, &height)) + return NULL; + } + else + { + return NULL; + } + + /* read successfully. add to image */ + layer = gimp_layer_new (image, layer_prefix, width, height, + GIMP_RGBA_IMAGE, 100, + gimp_image_get_default_new_layer_mode (image)); + gimp_image_insert_layer (image, layer, NULL, icon_num); + + buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)); + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0, + NULL, buf, GEGL_AUTO_ROWSTRIDE); + + g_object_unref (buffer); + + return layer; +} + + +GimpImage * +ico_load_image (GFile *file, + gint32 *file_offset, + gint frame_num, + GError **error) +{ + FILE *fp; + IcoFileHeader header; + IcoLoadInfo *info; + gint max_width, max_height; + gint i; + GimpImage *image; + guchar *buf; + guint icon_count; + gint maxsize; + gchar *str; + + if (! file_offset) + gimp_progress_init_printf (_("Opening '%s'"), + gimp_file_get_utf8_name (file)); + + fp = g_fopen (g_file_peek_path (file), "rb"); + + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_file_get_utf8_name (file), g_strerror (errno)); + return NULL; + } + + if (file_offset) + fseek (fp, *file_offset, SEEK_SET); + + header = ico_read_init (fp); + icon_count = header.icon_count; + if (!icon_count) + { + fclose (fp); + return NULL; + } + + info = ico_read_info (fp, icon_count, file_offset ? *file_offset : 0, error); + if (! info) + { + fclose (fp); + return NULL; + } + + /* find width and height of image */ + max_width = 0; + max_height = 0; + for (i = 0; i < icon_count; i++) + { + if (info[i].width > max_width) + max_width = info[i].width; + if (info[i].height > max_height) + max_height = info[i].height; + } + if (max_width <= 0 || max_height <= 0) + { + g_free (info); + fclose (fp); + return NULL; + } + D(("image size: %ix%i\n", max_width, max_height)); + + image = gimp_image_new (max_width, max_height, GIMP_RGB); + + maxsize = max_width * max_height * 4; + buf = g_new (guchar, max_width * max_height * 4); + for (i = 0; i < icon_count; i++) + { + GimpLayer *layer; + gchar *layer_prefix; + gchar *icon_metadata; + + if (info[i].bpp) + icon_metadata = g_strdup_printf ("(%dx%d, %dbpp)", info[i].width, + info[i].height, info[i].bpp); + else + icon_metadata = g_strdup_printf ("(%dx%d)", info[i].width, + info[i].height); + + if (frame_num > -1) + { + layer_prefix = g_strdup_printf ("Cursor %s Frame #%i", icon_metadata, + frame_num); + } + else + { + if (header.resource_type == 1) + layer_prefix = g_strdup_printf ("Icon #%i %s ", i + 1, + icon_metadata); + else + layer_prefix = g_strdup_printf ("Cursor #%i %s ", i + 1, + icon_metadata); + } + + layer = ico_load_layer (fp, image, i + 1, buf, maxsize, + file_offset ? *file_offset : 0, + layer_prefix, info + i); + g_free (icon_metadata); + g_free (layer_prefix); + + /* Save CUR hot spot information */ + if (header.resource_type == 2) + { + GimpParasite *parasite; + + str = g_strdup_printf ("%d %d", info[i].planes, info[i].bpp); + parasite = gimp_parasite_new ("cur-hot-spot", + GIMP_PARASITE_PERSISTENT, + strlen (str) + 1, (gpointer) str); + g_free (str); + gimp_item_attach_parasite (GIMP_ITEM (layer), parasite); + gimp_parasite_free (parasite); + } + } + + if (file_offset) + *file_offset = ftell (fp); + + g_free (buf); + g_free (info); + fclose (fp); + + /* Don't update progress here if .ani file */ + if (! file_offset) + gimp_progress_update (1.0); + + return image; +} + +/* Ported from James Huang's ani.c code, under the GPL license, version 3 + * or any later version of the license */ +GimpImage * +ani_load_image (GFile *file, + gboolean load_thumb, + gint *width, + gint *height, + GError **error) +{ + FILE *fp; + GimpImage *image = NULL; + GimpParasite *parasite; + gchar id[4]; + guint32 size; + guint8 padding; + gint32 file_offset; + guint frame = 1; + AniFileHeader header; + gchar *inam = NULL; + gchar *iart = NULL; + gchar *str; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_file_get_utf8_name (file)); + + fp = g_fopen (g_file_peek_path (file), "rb"); + + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_file_get_utf8_name (file), g_strerror (errno)); + return NULL; + } + + while (fread (id, 1, 4, fp) == 4) + { + if (memcmp (id, "RIFF", 4) == 0 ) + { + fread (&size, sizeof (size), 1, fp); + } + else if (memcmp (id, "anih", 4) == 0) + { + fread (&size, sizeof (size), 1, fp); + fread (&header, sizeof (header), 1, fp); + } + else if (memcmp (id, "rate", 4) == 0) + { + fread (&size, sizeof (size), 1, fp); + fseek (fp, size, SEEK_CUR); + } + else if (memcmp (id, "seq ", 4) == 0) + { + fread (&size, sizeof (size), 1, fp); + fseek (fp, size, SEEK_CUR); + } + else if (memcmp (id, "LIST", 4) == 0) + { + fread (&size, sizeof (size), 1, fp); + } + else if (memcmp (id, "INAM", 4) == 0) + { + gint n_read = -1; + + fread (&size, sizeof (size), 1, fp); + if (size > 0) + { + if (inam) + g_free (inam); + + inam = g_new0 (gchar, size + 1); + n_read = fread (inam, sizeof (gchar), size, fp); + inam[size] = '\0'; + } + + if (n_read < 1 || (inam && ! g_utf8_validate (inam, -1, NULL))) + { + fclose (fp); + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Invalid ANI metadata")); + return NULL; + } + + /* Metadata length must be even. If data itself is odd, + * then an extra 0x00 is added for padding. We read in + * that extra byte to keep loading properly. + * See discussion in #8562. + */ + if (size % 2 != 0) + fread (&padding, sizeof (padding), 1, fp); + } + else if (memcmp (id, "IART", 4) == 0) + { + gint n_read = -1; + + fread (&size, sizeof (size), 1, fp); + if (size > 0) + { + if (iart) + g_free (iart); + + iart = g_new0 (gchar, size + 1); + n_read = fread (iart, sizeof (gchar), size, fp); + iart[size] = '\0'; + } + + if (n_read < 1 || (iart && ! g_utf8_validate (iart, -1, NULL))) + { + fclose (fp); + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Invalid ANI metadata")); + return NULL; + } + + if (size % 2 != 0) + fread (&padding, sizeof (padding), 1, fp); + } + else if (memcmp (id, "icon", 4) == 0) + { + fread (&size, sizeof (size), 1, fp); + file_offset = ftell (fp); + if (load_thumb) + { + image = ico_load_thumbnail_image (file, width, height, file_offset, error); + break; + } + else + { + if (! image) + { + image = ico_load_image (file, &file_offset, 1, error); + } + else + { + GimpImage *temp_image = NULL; + GimpLayer **layers; + GimpLayer *new_layer; + + temp_image = ico_load_image (file, &file_offset, frame + 1, + error); + layers = gimp_image_get_layers (temp_image); + if (layers) + { + for (gint i = 0; layers[i]; i++) + { + new_layer = gimp_layer_new_from_drawable (GIMP_DRAWABLE (layers[i]), + image); + gimp_image_insert_layer (image, new_layer, NULL, frame); + frame++; + } + g_free (layers); + } + gimp_image_delete (temp_image); + } + + /* Update position after reading icon data */ + fseek (fp, file_offset, SEEK_SET); + if (header.frames > 0) + gimp_progress_update ((gdouble) frame / + (gdouble) header.frames); + } + } + } + fclose (fp); + + /* Saving header metadata */ + str = g_strdup_printf ("%d", header.jif_rate); + parasite = gimp_parasite_new ("ani-header", + GIMP_PARASITE_PERSISTENT, + strlen (str) + 1, (gpointer) str); + g_free (str); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + /* Saving INFO block */ + if (inam && strlen (inam) > 0) + { + str = g_strdup_printf ("%s", inam); + parasite = gimp_parasite_new ("ani-info-inam", + GIMP_PARASITE_PERSISTENT, + strlen (str) + 1, (gpointer) str); + g_free (str); + g_free (inam); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + } + + if (iart && strlen (iart) > 0) + { + str = g_strdup_printf ("%s", iart); + parasite = gimp_parasite_new ("ani-info-iart", + GIMP_PARASITE_PERSISTENT, + strlen (str) + 1, (gpointer) str); + g_free (str); + g_free (iart); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + } + + gimp_progress_update (1.0); + + return image; +} + +GimpImage * +ico_load_thumbnail_image (GFile *file, + gint *width, + gint *height, + gint32 file_offset, + GError **error) +{ + FILE *fp; + IcoLoadInfo *info; + IcoFileHeader header; + GimpImage *image; + gint max_width; + gint max_height; + gint w = 0; + gint h = 0; + gint bpp = 0; + gint match = 0; + gint i, icon_count; + guchar *buf; + + gimp_progress_init_printf (_("Opening thumbnail for '%s'"), + gimp_file_get_utf8_name (file)); + + fp = g_fopen (g_file_peek_path (file), "rb"); + + if (! fp) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_file_get_utf8_name (file), g_strerror (errno)); + return NULL; + } + + if (file_offset > 0) + fseek (fp, file_offset, SEEK_SET); + + header = ico_read_init (fp); + icon_count = header.icon_count; + if (! icon_count) + { + fclose (fp); + return NULL; + } + + D(("*** %s: Microsoft icon file, containing %i icon(s)\n", + gimp_file_get_utf8_name (file), icon_count)); + + info = ico_read_info (fp, icon_count, file_offset, error); + if (! info) + { + fclose (fp); + return NULL; + } + + max_width = 0; + max_height = 0; + + /* Do a quick scan of the icons in the file to find the best match */ + for (i = 0; i < icon_count; i++) + { + if (info[i].width > max_width) + max_width = info[i].width; + if (info[i].height > max_height) + max_height = info[i].height; + + if ((info[i].width > w && w < *width) || + (info[i].height > h && h < *height)) + { + w = info[i].width; + h = info[i].height; + bpp = info[i].bpp; + + match = i; + } + else if (w == info[i].width && + h == info[i].height && + info[i].bpp > bpp) + { + /* better quality */ + bpp = info[i].bpp; + match = i; + } + } + + if (w <= 0 || h <= 0) + return NULL; + + image = gimp_image_new (w, h, GIMP_RGB); + buf = g_new (guchar, w*h*4); + ico_load_layer (fp, image, match, buf, w*h*4, file_offset, + "Thumbnail", info + match); + g_free (buf); + + *width = max_width; + *height = max_height; + + D(("*** thumbnail successfully loaded.\n\n")); + + gimp_progress_update (1.0); + + g_free (info); + fclose (fp); + + return image; +} diff -Nuar gimp-3.0.6.orig/plug-ins/file-psd/psd-image-res-load.c gimp-3.0.6/plug-ins/file-psd/psd-image-res-load.c --- gimp-3.0.6.orig/plug-ins/file-psd/psd-image-res-load.c 2025-10-05 13:14:02.000000000 -0400 +++ gimp-3.0.6/plug-ins/file-psd/psd-image-res-load.c 2026-02-19 13:50:21.170338855 -0500 @@ -650,8 +650,6 @@ return 0; } - img_a->alpha_names = g_ptr_array_new (); - block_rem = res_a->data_len; while (block_rem > 1) { @@ -661,6 +659,8 @@ IFDBG(3) g_debug ("String: %s, %d, %d", str, read_len, write_len); if (write_len >= 0) { + if (! img_a->alpha_names) + img_a->alpha_names = g_ptr_array_new (); g_ptr_array_add (img_a->alpha_names, (gpointer) str); } block_rem -= read_len; diff -Nuar gimp-3.0.6.orig/plug-ins/file-psd/psd-image-res-load.c.orig gimp-3.0.6/plug-ins/file-psd/psd-image-res-load.c.orig --- gimp-3.0.6.orig/plug-ins/file-psd/psd-image-res-load.c.orig 1969-12-31 19:00:00.000000000 -0500 +++ gimp-3.0.6/plug-ins/file-psd/psd-image-res-load.c.orig 2025-10-05 13:14:02.000000000 -0400 @@ -0,0 +1,1786 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 3 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 . + */ + +/* ----- Known Image Resource Block Types ----- + All image resources not otherwise handled, including unknown types + are added as image parasites. + The data is attached as-is from the file (i.e. in big endian order). + + PSD_PS2_IMAGE_INFO = 1000, Dropped * 0x03e8 - Obsolete - ps 2.0 image info * + PSD_MAC_PRINT_INFO = 1001, PS Only * 0x03e9 - Optional - Mac print manager print info record * + PSD_PS2_COLOR_TAB = 1003, Dropped * 0x03eb - Obsolete - ps 2.0 indexed color table * + PSD_RESN_INFO = 1005, Loaded * 0x03ed - ResolutionInfo structure * + PSD_ALPHA_NAMES = 1006, Loaded * 0x03ee - Alpha channel names * + PSD_DISPLAY_INFO = 1007, Loaded * 0x03ef - DisplayInfo structure * + PSD_CAPTION = 1008, Loaded * 0x03f0 - Optional - Caption string * + PSD_BORDER_INFO = 1009, * 0x03f1 - Border info * + PSD_BACKGROUND_COL = 1010, * 0x03f2 - Background color * + PSD_PRINT_FLAGS = 1011, * 0x03f3 - Print flags * + PSD_GREY_HALFTONE = 1012, * 0x03f4 - Greyscale and multichannel halftoning info * + PSD_COLOR_HALFTONE = 1013, * 0x03f5 - Color halftoning info * + PSD_DUOTONE_HALFTONE = 1014, * 0x03f6 - Duotone halftoning info * + PSD_GREY_XFER = 1015, * 0x03f7 - Greyscale and multichannel transfer functions * + PSD_COLOR_XFER = 1016, * 0x03f8 - Color transfer functions * + PSD_DUOTONE_XFER = 1017, * 0x03f9 - Duotone transfer functions * + PSD_DUOTONE_INFO = 1018, * 0x03fa - Duotone image information * + PSD_EFFECTIVE_BW = 1019, * 0x03fb - Effective black & white values for dot range * + PSD_OBSOLETE_01 = 1020, Dropped * 0x03fc - Obsolete * + PSD_EPS_OPT = 1021, * 0x03fd - EPS options * + PSD_QUICK_MASK = 1022, Loaded * 0x03fe - Quick mask info * + PSD_OBSOLETE_02 = 1023, Dropped * 0x03ff - Obsolete * + PSD_LAYER_STATE = 1024, Loaded * 0x0400 - Layer state info * + PSD_WORKING_PATH = 1025, * 0x0401 - Working path (not saved) * + PSD_LAYER_GROUP = 1026, * 0x0402 - Layers group info * + PSD_OBSOLETE_03 = 1027, Dropped * 0x0403 - Obsolete * + PSD_IPTC_NAA_DATA = 1028, Loaded * 0x0404 - IPTC-NAA record (IMV4.pdf) * + PSD_IMAGE_MODE_RAW = 1029, * 0x0405 - Image mode for raw format files * + PSD_JPEG_QUAL = 1030, PS Only * 0x0406 - JPEG quality * + PSD_GRID_GUIDE = 1032, Loaded * 0x0408 - Grid & guide info * + PSD_THUMB_RES = 1033, Special * 0x0409 - Thumbnail resource * + PSD_COPYRIGHT_FLG = 1034, * 0x040a - Copyright flag * + PSD_URL = 1035, * 0x040b - URL string * + PSD_THUMB_RES2 = 1036, Special * 0x040c - Thumbnail resource * + PSD_GLOBAL_ANGLE = 1037, * 0x040d - Global angle * + PSD_COLOR_SAMPLER = 1038, * 0x040e - Color samplers resource * + PSD_ICC_PROFILE = 1039, Loaded * 0x040f - ICC Profile * + PSD_WATERMARK = 1040, * 0x0410 - Watermark * + PSD_ICC_UNTAGGED = 1041, * 0x0411 - Do not use ICC profile flag * + PSD_EFFECTS_VISIBLE = 1042, * 0x0412 - Show hide all effects layers * + PSD_SPOT_HALFTONE = 1043, * 0x0413 - Spot halftone * + PSD_DOC_IDS = 1044, * 0x0414 - Document specific IDs * + PSD_ALPHA_NAMES_UNI = 1045, Loaded * 0x0415 - Unicode alpha names * + PSD_IDX_COL_TAB_CNT = 1046, Loaded * 0x0416 - Indexed color table count * + PSD_IDX_TRANSPARENT = 1047, * 0x0417 - Index of transparent color (if any) * + PSD_GLOBAL_ALT = 1049, * 0x0419 - Global altitude * + PSD_SLICES = 1050, * 0x041a - Slices * + PSD_WORKFLOW_URL_UNI = 1051, * 0x041b - Workflow URL - Unicode string * + PSD_JUMP_TO_XPEP = 1052, * 0x041c - Jump to XPEP (?) * + PSD_ALPHA_ID = 1053, Loaded * 0x041d - Alpha IDs * + PSD_URL_LIST_UNI = 1054, * 0x041e - URL list - unicode * + PSD_VERSION_INFO = 1057, * 0x0421 - Version info * + PSD_EXIF_DATA = 1058, Loaded * 0x0422 - Exif data block 1 * + PSD_EXIF_DATA_3 = 1059 * 0X0423 - Exif data block 3 (?) * + PSD_XMP_DATA = 1060, Loaded * 0x0424 - XMP data block * + PSD_CAPTION_DIGEST = 1061, * 0x0425 - Caption digest * + PSD_PRINT_SCALE = 1062, * 0x0426 - Print scale * + PSD_PIXEL_AR = 1064, * 0x0428 - Pixel aspect ratio * + PSD_LAYER_COMPS = 1065, * 0x0429 - Layer comps * + PSD_ALT_DUOTONE_COLOR = 1066, * 0x042A - Alternative Duotone colors * + PSD_ALT_SPOT_COLOR = 1067, * 0x042B - Alternative Spot colors * + PSD_LAYER_SELECT_ID = 1069, * 0x042D - Layer selection ID * + PSD_HDR_TONING_INFO = 1070, * 0x042E - HDR toning information * + PSD_PRINT_INFO_SCALE = 1071, * 0x042F - Print scale * + PSD_LAYER_GROUP_E_ID = 1072, * 0x0430 - Layer group(s) enabled ID * + PSD_COLOR_SAMPLER_NEW = 1073, * 0x0431 - Color sampler resource for ps CS3 and higher PSD files * + PSD_MEASURE_SCALE = 1074, * 0x0432 - Measurement scale * + PSD_TIMELINE_INFO = 1075, * 0x0433 - Timeline information * + PSD_SHEET_DISCLOSE = 1076, * 0x0434 - Sheet discloser * + PSD_DISPLAY_INFO_NEW = 1077, Loaded * 0x0435 - DisplayInfo structure for ps CS3 and higher PSD files * + PSD_ONION_SKINS = 1078, * 0x0436 - Onion skins * + PSD_COUNT_INFO = 1080, * 0x0438 - Count information* + PSD_PRINT_INFO = 1082, * 0x043A - Print information added in ps CS5* + PSD_PRINT_STYLE = 1083, * 0x043B - Print style * + PSD_MAC_NSPRINTINFO = 1084, * 0x043C - Mac NSPrintInfo* + PSD_WIN_DEVMODE = 1085, * 0x043D - Windows DEVMODE * + PSD_AUTO_SAVE_PATH = 1086, * 0x043E - Auto save file path * + PSD_AUTO_SAVE_FORMAT = 1087, * 0x043F - Auto save format * + PSD_PATH_INFO_FIRST = 2000, Loaded * 0x07d0 - First path info block * + PSD_PATH_INFO_LAST = 2998, Loaded * 0x0bb6 - Last path info block * + PSD_CLIPPING_PATH = 2999, * 0x0bb7 - Name of clipping path * + PSD_PLUGIN_R_FIRST = 4000, * 0x0FA0 - First plugin resource * + PSD_PLUGIN_R_LAST = 4999, * 0x1387 - Last plugin resource * + PSD_IMAGEREADY_VARS = 7000, PS Only * 0x1B58 - Imageready variables * + PSD_IMAGEREADY_DATA = 7001, PS Only * 0x1B59 - Imageready data sets * + PSD_LIGHTROOM_WORK = 8000, PS Only * 0x1F40 - Lightroom workflow * + PSD_PRINT_FLAGS_2 = 10000 * 0x2710 - Print flags * +*/ + +#include "config.h" + +#include +#include + +#include +#include + +#include +#include + +#ifdef HAVE_IPTCDATA +#include +#endif /* HAVE_IPTCDATA */ + +#include "psd.h" +#include "psd-util.h" +#include "psd-image-res-load.h" + +#include "libgimp/stdplugins-intl.h" + +#define EXIF_HEADER_SIZE 8 + +/* Local function prototypes */ +static gint load_resource_unknown (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_ps_only (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1005 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1006 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1007 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1008 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1022 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1024 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1028 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1032 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +/* 1033 - Thumbnail needs special handling since it calls the jpeg library + * which needs a classic FILE. */ +static gint load_resource_1033 (const PSDimageres *res_a, + GimpImage *image, + GFile *file, + GInputStream *input, + GError **error); + +static gint load_resource_1039 (const PSDimageres *res_a, + PSDimage *img_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1045 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1046 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1053 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1058 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_1069 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_1077 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error); + +static gint load_resource_2000 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +static gint load_resource_2999 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + +/* Public Functions */ +gint +get_image_resource_header (PSDimageres *res_a, + GInputStream *input, + GError **error) +{ + gint32 read_len; + gint32 write_len; + gchar *name; + + if (psd_read (input, &res_a->type, 4, error) < 4 || + psd_read (input, &res_a->id, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + res_a->id = GUINT16_FROM_BE (res_a->id); + name = fread_pascal_string (&read_len, &write_len, 2, input, error); + if (*error) + return -1; + if (name != NULL) + g_strlcpy (res_a->name, name, write_len + 1); + else + res_a->name[0] = 0x0; + g_free (name); + if (psd_read (input, &res_a->data_len, 4, error) < 4) + { + psd_set_error (error); + return -1; + } + res_a->data_len = GUINT32_FROM_BE (res_a->data_len); + res_a->data_start = g_seekable_tell (G_SEEKABLE (input)); + + IFDBG(2) g_debug ("Type: %.4s, id: %d, start: %" G_GOFFSET_FORMAT ", len: %" G_GSIZE_FORMAT, + res_a->type, res_a->id, res_a->data_start, res_a->data_len); + + return 0; +} + +gint +load_image_resource (PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error) +{ + gint pad; + + /* Set file position to start of image resource data block */ + if (! psd_seek (input, res_a->data_start, G_SEEK_SET, error)) + { + psd_set_error (error); + return -1; + } + + /* Process image resource blocks */ + if (memcmp (res_a->type, "8BIM", 4) != 0 && + memcmp (res_a->type, "MeSa", 4) !=0) + { + IFDBG(1) g_debug ("Unknown image resource type signature %.4s", + res_a->type); + } + else + { + switch (res_a->id) + { + case PSD_PS2_IMAGE_INFO: + case PSD_PS2_COLOR_TAB: + case PSD_OBSOLETE_01: + case PSD_OBSOLETE_02: + case PSD_OBSOLETE_03: + /* Drop obsolete image resource blocks */ + IFDBG(2) g_debug ("Obsolete image resource block: %d", + res_a->id); + break; + + case PSD_THUMB_RES: + case PSD_THUMB_RES2: + /* Drop thumbnails from standard file load */ + IFDBG(2) g_debug ("Thumbnail resource block: %d", + res_a->id); + break; + + case PSD_MAC_PRINT_INFO: + case PSD_JPEG_QUAL: + /* Save photoshop resources with no meaning for GIMP + as image parasites */ + load_resource_ps_only (res_a, image, input, error); + break; + + case PSD_RESN_INFO: + if (! load_resource_1005 (res_a, image, input, error)) + *resolution_loaded = TRUE; + break; + + case PSD_ALPHA_NAMES: + load_resource_1006 (res_a, image, img_a, input, error); + break; + + case PSD_DISPLAY_INFO: + load_resource_1007 (res_a, image, img_a, input, error); + break; + + case PSD_CAPTION: + load_resource_1008 (res_a, image, input, error); + break; + + case PSD_QUICK_MASK: + if (! img_a->merged_image_only) + load_resource_1022 (res_a, image, img_a, input, error); + break; + + case PSD_LAYER_STATE: + if (! img_a->merged_image_only) + load_resource_1024 (res_a, image, img_a, input, error); + break; + + case PSD_WORKING_PATH: + if (! img_a->merged_image_only) + load_resource_2000 (res_a, image, input, error); + break; + + case PSD_IPTC_NAA_DATA: + load_resource_1028 (res_a, image, input, error); + break; + + case PSD_GRID_GUIDE: + if (! img_a->merged_image_only) + load_resource_1032 (res_a, image, input, error); + break; + + case PSD_ICC_PROFILE: + if (! load_resource_1039 (res_a, img_a, image, input, error)) + *profile_loaded = TRUE; + break; + + case PSD_ALPHA_NAMES_UNI: + load_resource_1045 (res_a, image, img_a, input, error); + break; + + case PSD_IDX_COL_TAB_CNT: + load_resource_1046 (res_a, image, input, error); + break; + + case PSD_ALPHA_ID: + if (! img_a->merged_image_only) + load_resource_1053 (res_a, image, img_a, input, error); + break; + + case PSD_EXIF_DATA: + load_resource_1058 (res_a, image, input, error); + break; + + case PSD_LAYER_SELECT_ID: + if (! img_a->merged_image_only) + load_resource_1069 (res_a, image, img_a, input, error); + break; + + case PSD_XMP_DATA: + break; + + case PSD_DISPLAY_INFO_NEW: + load_resource_1077 (res_a, image, img_a, input, error); + break; + + case PSD_CLIPPING_PATH: + load_resource_2999 (res_a, image, input, error); + break; + + case PSD_LAYER_COMPS: + img_a->unsupported_features->layer_comp = TRUE; + img_a->unsupported_features->show_gui = TRUE; + load_resource_unknown (res_a, image, input, error); + break; + + default: + if (res_a->id >= 2000 && + res_a->id < 2999) + load_resource_2000 (res_a, image, input, error); + else + load_resource_unknown (res_a, image, input, error); + } + } + + /* Image blocks are null padded to even length */ + if (res_a->data_len % 2 == 0) + pad = 0; + else + pad = 1; + + /* Set file position to end of image resource block */ + if (! psd_seek (input, res_a->data_start + res_a->data_len + pad, G_SEEK_SET, error)) + { + psd_set_error (error); + return -1; + } + + return 0; +} + +gint +load_thumbnail_resource (PSDimageres *res_a, + GimpImage *image, + GFile *file, + GInputStream *input, + GError **error) +{ + gint rtn = 0; + gint pad; + + /* Set file position to start of image resource data block */ + if (! psd_seek (input, res_a->data_start, G_SEEK_SET, error)) + { + psd_set_error (error); + return -1; + } + + /* Process image resource blocks */ + if (res_a->id == PSD_THUMB_RES + || res_a->id == PSD_THUMB_RES2) + { + /* Load thumbnails from standard file load */ + load_resource_1033 (res_a, image, file, input, error); + rtn = 1; + } + + /* Image blocks are null padded to even length */ + if (res_a->data_len % 2 == 0) + pad = 0; + else + pad = 1; + + /* Set file position to end of image resource block */ + if (psd_seek (input, res_a->data_start + res_a->data_len + pad, G_SEEK_SET, error)) + { + psd_set_error (error); + return -1; + } + + return rtn; +} + +/* Private Functions */ + +static gint +load_resource_unknown (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Unknown image resources attached as parasites to re-save later */ + GimpParasite *parasite; + gchar *data; + gchar *name; + + IFDBG(2) g_debug ("Process unknown image resource block: %d", res_a->id); + + data = g_malloc (res_a->data_len); + if (res_a->data_len > 0 && psd_read (input, data, res_a->data_len, error) < res_a->data_len) + { + psd_set_error (error); + g_free (data); + return -1; + } + + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(2) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, data); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + g_free (data); + g_free (name); + + return 0; +} + +static gint +load_resource_ps_only (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Save photoshop resources with no meaning for GIMP as image parasites + to re-save later */ + GimpParasite *parasite; + gchar *data; + gchar *name; + + IFDBG(3) g_debug ("Process image resource block: %d", res_a->id); + + data = g_malloc (res_a->data_len); + if (psd_read (input, data, res_a->data_len, error) < res_a->data_len) + { + psd_set_error (error); + g_free (data); + return -1; + } + + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(2) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, data); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + g_free (data); + g_free (name); + + return 0; +} + +static gint +load_resource_1005 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Load image resolution and unit of measure */ + + /* FIXME width unit and height unit unused at present */ + + ResolutionInfo res_info; + GimpUnit *image_unit; + + IFDBG(2) g_debug ("Process image resource block 1005: Resolution Info"); + + if (psd_read (input, &res_info.hRes, 4, error) < 4 || + psd_read (input, &res_info.hResUnit, 2, error) < 2 || + psd_read (input, &res_info.widthUnit, 2, error) < 2 || + psd_read (input, &res_info.vRes, 4, error) < 4 || + psd_read (input, &res_info.vResUnit, 2, error) < 2 || + psd_read (input, &res_info.heightUnit, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + res_info.hRes = GINT32_FROM_BE (res_info.hRes); + res_info.hResUnit = GINT16_FROM_BE (res_info.hResUnit); + res_info.widthUnit = GINT16_FROM_BE (res_info.widthUnit); + res_info.vRes = GINT32_FROM_BE (res_info.vRes); + res_info.vResUnit = GINT16_FROM_BE (res_info.vResUnit); + res_info.heightUnit = GINT16_FROM_BE (res_info.heightUnit); + + IFDBG(3) g_debug ("Resolution: %d, %d, %d, %d, %d, %d", + res_info.hRes, + res_info.hResUnit, + res_info.widthUnit, + res_info.vRes, + res_info.vResUnit, + res_info.heightUnit); + + /* Resolution always recorded as pixels / inch in a fixed point implied + decimal int32 with 16 bits before point and 16 after (i.e. cast as + double and divide resolution by 2^16 */ + gimp_image_set_resolution (image, + res_info.hRes / 65536.0, res_info.vRes / 65536.0); + + /* GIMP only has one display unit so use ps horizontal resolution unit */ + switch (res_info.hResUnit) + { + case PSD_RES_INCH: + image_unit = gimp_unit_inch (); + break; + case PSD_RES_CM: + image_unit = gimp_unit_mm (); + break; + default: + image_unit = gimp_unit_inch (); + } + + gimp_image_set_unit (image, image_unit); + + return 0; +} + +static gint +load_resource_1006 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load alpha channel names stored as a series of pascal strings + unpadded between strings */ + + gchar *str; + gint32 block_rem; + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process image resource block 1006: Alpha Channel Names"); + + if (img_a->alpha_names) + { + IFDBG(3) g_debug ("Alpha names loaded from unicode resource block"); + return 0; + } + + img_a->alpha_names = g_ptr_array_new (); + + block_rem = res_a->data_len; + while (block_rem > 1) + { + str = fread_pascal_string (&read_len, &write_len, 1, input, error); + if (*error) + return -1; + IFDBG(3) g_debug ("String: %s, %d, %d", str, read_len, write_len); + if (write_len >= 0) + { + g_ptr_array_add (img_a->alpha_names, (gpointer) str); + } + block_rem -= read_len; + } + + return 0; +} + +static gint +load_resource_1007 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load alpha channel display info */ + + DisplayInfo dsp_info; + CMColor ps_color; + gint16 tot_rec; + gint cidx; + + IFDBG(2) g_debug ("Process image resource block 1007: Display Info"); + tot_rec = res_a->data_len / 14; + if (tot_rec == 0) + return 0; + + img_a->alpha_display_info = g_new (PSDchanneldata *, tot_rec); + img_a->alpha_display_count = tot_rec; + for (cidx = 0; cidx < tot_rec; ++cidx) + { + GeglColor *color; + + if (psd_read (input, &dsp_info.colorSpace, 2, error) < 2 || + psd_read (input, &dsp_info.color, 8, error) < 8 || + psd_read (input, &dsp_info.opacity, 2, error) < 2 || + psd_read (input, &dsp_info.kind, 1, error) < 1 || + psd_read (input, &dsp_info.padding, 1, error) < 1) + { + psd_set_error (error); + return -1; + } + dsp_info.colorSpace = GINT16_FROM_BE (dsp_info.colorSpace); + ps_color.cmyk.cyan = GUINT16_FROM_BE (dsp_info.color[0]); + ps_color.cmyk.magenta = GUINT16_FROM_BE (dsp_info.color[1]); + ps_color.cmyk.yellow = GUINT16_FROM_BE (dsp_info.color[2]); + ps_color.cmyk.black = GUINT16_FROM_BE (dsp_info.color[3]); + dsp_info.opacity = GINT16_FROM_BE (dsp_info.opacity); + + color = gegl_color_new ("red"); + + switch (dsp_info.colorSpace) + { + case PSD_CS_RGB: + /* TODO: which space should we use? */ + gegl_color_set_rgba_with_space (color, + ps_color.rgb.red / 65535.0, + ps_color.rgb.green / 65535.0, + ps_color.rgb.blue / 65535.0, + 1.0, NULL); + break; + + case PSD_CS_HSB: + { + gfloat hsv[3] = + { + ps_color.hsv.hue / 65535.0f, + ps_color.hsv.saturation / 65535.0f, + ps_color.hsv.value / 65535.0f + }; + + gegl_color_set_pixel (color, babl_format ("HSV float"), hsv); + } + break; + + case PSD_CS_CMYK: + /* TODO: again, which space? */ + gegl_color_set_cmyk (color, + 1.0 - ps_color.cmyk.cyan / 65535.0, + 1.0 - ps_color.cmyk.magenta / 65535.0, + 1.0 - ps_color.cmyk.yellow / 65535.0, + 1.0 - ps_color.cmyk.black / 65535.0, + 1.0, NULL); + break; + + case PSD_CS_GRAYSCALE: + { + gdouble gray = ps_color.gray.gray / 10000.0; + + /* TODO: which space? */ + gegl_color_set_pixel (color, babl_format_with_space ("Y' double", NULL), &gray); + } + break; + + case PSD_CS_FOCOLTONE: + case PSD_CS_TRUMATCH: + case PSD_CS_HKS: + case PSD_CS_LAB: + case PSD_CS_PANTONE: + case PSD_CS_TOYO: + case PSD_CS_DIC: + case PSD_CS_ANPA: + default: + if (CONVERSION_WARNINGS) + g_message ("Unsupported color space: %d", + dsp_info.colorSpace); + } + + IFDBG(2) g_debug ("PS cSpace: %d, col: %d %d %d %d, opacity: %d, kind: %d", + dsp_info.colorSpace, ps_color.cmyk.cyan, ps_color.cmyk.magenta, + ps_color.cmyk.yellow, ps_color.cmyk.black, dsp_info.opacity, + dsp_info.kind); + + IFDBG(2) g_debug ("cSpace: %d, opacity: %d, kind: %d", + dsp_info.colorSpace, dsp_info.opacity, dsp_info.kind); + + img_a->alpha_display_info[cidx] = g_malloc0 (sizeof (PSDchanneldata)); + img_a->alpha_display_info[cidx]->gimp_color = color; + img_a->alpha_display_info[cidx]->opacity = dsp_info.opacity; + img_a->alpha_display_info[cidx]->ps_kind = dsp_info.kind; + img_a->alpha_display_info[cidx]->ps_cspace = dsp_info.colorSpace; + img_a->alpha_display_info[cidx]->ps_color = ps_color; + } + + return 0; +} + +static gint +load_resource_1008 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Load image caption */ + GimpParasite *parasite; + gchar *caption; + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process image resource block: 1008: Caption"); + caption = fread_pascal_string (&read_len, &write_len, 1, input, error); + if (*error) + return -1; + + IFDBG(3) g_debug ("Caption: %s", caption); + parasite = gimp_parasite_new (GIMP_PARASITE_COMMENT, GIMP_PARASITE_PERSISTENT, + write_len, caption); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + g_free (caption); + + return 0; +} + +static gint +load_resource_1022 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load quick mask info */ + gboolean quick_mask_empty = TRUE; /* Quick mask initially empty */ + + IFDBG(2) g_debug ("Process image resource block: 1022: Quick Mask"); + + if (psd_read (input, &img_a->quick_mask_id, 2, error) < 2 || + psd_read (input, &quick_mask_empty, 1, error) < 1) + { + psd_set_error (error); + return -1; + } + img_a->quick_mask_id = GUINT16_FROM_BE (img_a->quick_mask_id); + + IFDBG(3) g_debug ("Quick mask channel: %d, empty: %d", + img_a->quick_mask_id, + quick_mask_empty); + + return 0; +} + +static gint +load_resource_1024 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load image layer state - current active layer counting from bottom up */ + IFDBG(2) g_debug ("Process image resource block: 1024: Layer State"); + + if (psd_read (input, &img_a->layer_state, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + img_a->layer_state = GUINT16_FROM_BE (img_a->layer_state); + + return 0; +} + +static gint +load_resource_1028 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Load IPTC data block */ + +#ifdef HAVE_IPTCDATA + IptcData *iptc_data; + guchar *iptc_buf; + guint iptc_buf_len; +#else + gchar *name; +#endif /* HAVE_IPTCDATA */ + + GimpParasite *parasite; + gchar *res_data; + + IFDBG(2) g_debug ("Process image resource block: 1028: IPTC data"); + + res_data = g_malloc (res_a->data_len); + if (psd_read (input, res_data, res_a->data_len, error) < res_a->data_len) + { + psd_set_error (error); + g_free (res_data); + return -1; + } + +#ifdef HAVE_IPTCDATA + /* Load IPTC data structure */ + iptc_data = iptc_data_new_from_data (res_data, res_a->data_len); + IFDBG (3) iptc_data_dump (iptc_data, 0); + + /* Store resource data as a GIMP IPTC parasite */ + IFDBG (2) g_debug ("Processing IPTC data as GIMP IPTC parasite"); + /* Serialize IPTC data */ + iptc_data_save (iptc_data, &iptc_buf, &iptc_buf_len); + if (iptc_buf_len > 0) + { + parasite = gimp_parasite_new (GIMP_PARASITE_IPTC, + GIMP_PARASITE_PERSISTENT, + iptc_buf_len, iptc_buf); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + } + + iptc_data_unref (iptc_data); + g_free (iptc_buf); + +#else + /* Store resource data as a standard psd parasite */ + IFDBG (2) g_debug ("Processing IPTC data as psd parasite"); + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(3) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, res_data); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + g_free (name); + +#endif /* HAVE_IPTCDATA */ + + g_free (res_data); + return 0; +} + +static gint +load_resource_1032 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Load grid and guides */ + + /* Grid info is not used (CS2 or earlier) */ + + GuideHeader hdr; + GuideResource guide; + gint i; + + IFDBG(2) g_debug ("Process image resource block 1032: Grid and Guide Info"); + + if (psd_read (input, &hdr.fVersion, 4, error) < 4 || + psd_read (input, &hdr.fGridCycleV, 4, error) < 4 || + psd_read (input, &hdr.fGridCycleH, 4, error) < 4 || + psd_read (input, &hdr.fGuideCount, 4, error) < 4) + { + psd_set_error (error); + return -1; + } + hdr.fVersion = GUINT32_FROM_BE (hdr.fVersion); + hdr.fGridCycleV = GUINT32_FROM_BE (hdr.fGridCycleV); + hdr.fGridCycleH = GUINT32_FROM_BE (hdr.fGridCycleH); + hdr.fGuideCount = GUINT32_FROM_BE (hdr.fGuideCount); + + IFDBG(3) g_debug ("Grids & Guides: %d, %d, %d, %d", + hdr.fVersion, + hdr.fGridCycleV, + hdr.fGridCycleH, + hdr.fGuideCount); + + for (i = 0; i < hdr.fGuideCount; ++i) + { + if (psd_read (input, &guide.fLocation, 4, error) < 4 || + psd_read (input, &guide.fDirection, 1, error) < 1) + { + psd_set_error (error); + return -1; + } + guide.fLocation = GUINT32_FROM_BE (guide.fLocation); + guide.fLocation /= 32; + + IFDBG(3) g_debug ("Guide: %d px, %d", + guide.fLocation, + guide.fDirection); + + if (guide.fDirection == PSD_VERTICAL) + gimp_image_add_vguide (image, guide.fLocation); + else + gimp_image_add_hguide (image, guide.fLocation); + } + + return 0; +} + +static gint +load_resource_1033 (const PSDimageres *res_a, + GimpImage *image, + GFile *file, + GInputStream *input, + GError **error) +{ + /* Load thumbnail image */ + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + FILE *f; + ThumbnailInfo thumb_info; + GeglBuffer *buffer; + const Babl *format; + GimpLayer *layer; + guchar *buf; + guchar *rgb_buf; + guchar **rowbuf; + gint i; + + IFDBG(2) g_debug ("Process image resource block %d: Thumbnail Image", res_a->id); + + /* Read thumbnail resource header info */ + if (psd_read (input, &thumb_info.format, 4, error) < 4 || + psd_read (input, &thumb_info.width, 4, error) < 4 || + psd_read (input, &thumb_info.height, 4, error) < 4 || + psd_read (input, &thumb_info.widthbytes, 4, error) < 4 || + psd_read (input, &thumb_info.size, 4, error) < 4 || + psd_read (input, &thumb_info.compressedsize, 4, error) < 4 || + psd_read (input, &thumb_info.bitspixel, 2, error) < 2 || + psd_read (input, &thumb_info.planes, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + thumb_info.format = GINT32_FROM_BE (thumb_info.format); + thumb_info.width = GINT32_FROM_BE (thumb_info.width); + thumb_info.height = GINT32_FROM_BE (thumb_info.height); + thumb_info.widthbytes = GINT32_FROM_BE (thumb_info.widthbytes); + thumb_info.size = GINT32_FROM_BE (thumb_info.size); + thumb_info.compressedsize = GINT32_FROM_BE (thumb_info.compressedsize); + thumb_info.bitspixel = GINT16_FROM_BE (thumb_info.bitspixel); + thumb_info.planes = GINT16_FROM_BE (thumb_info.planes); + + IFDBG(2) g_debug ("\nThumbnail:\n" + "\tFormat: %d\n" + "\tDimensions: %d x %d\n", + thumb_info.format, + thumb_info.width, + thumb_info.height); + + if (thumb_info.format != 1) + { + IFDBG(1) g_debug ("Unknown thumbnail format %d", thumb_info.format); + return -1; + } + + /* Load Jpeg RGB thumbnail info */ + + /* Open input also as a FILE. */ + f = g_fopen (g_file_peek_path (file), "rb"); + + if (! f) + return -1; + + /* Now seek to the same position as we have in input. */ + fseek(f, g_seekable_tell (G_SEEKABLE (input)), SEEK_SET); + + /* Step 1: Allocate and initialize JPEG decompression object */ + cinfo.err = jpeg_std_error (&jerr); + jpeg_create_decompress (&cinfo); + + /* Step 2: specify data source (eg, a file) */ + jpeg_stdio_src(&cinfo, f); + + /* Step 3: read file parameters with jpeg_read_header() */ + jpeg_read_header (&cinfo, TRUE); + + /* Step 4: set parameters for decompression */ + + + /* Step 5: Start decompressor */ + jpeg_start_decompress (&cinfo); + + /* temporary buffers */ + buf = g_new (guchar, cinfo.output_height * cinfo.output_width + * cinfo.output_components); + if (res_a->id == PSD_THUMB_RES) + rgb_buf = g_new (guchar, cinfo.output_height * cinfo.output_width + * cinfo.output_components); + else + rgb_buf = NULL; + rowbuf = g_new (guchar *, cinfo.output_height); + + for (i = 0; i < cinfo.output_height; ++i) + rowbuf[i] = buf + cinfo.output_width * cinfo.output_components * i; + + /* Create image layer */ + gimp_image_resize (image, cinfo.output_width, cinfo.output_height, 0, 0); + layer = gimp_layer_new (image, _("Background"), + cinfo.output_width, + cinfo.output_height, + GIMP_RGB_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image)); + buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)); + format = babl_format ("R'G'B' u8"); + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + while (cinfo.output_scanline < cinfo.output_height) + { + jpeg_read_scanlines (&cinfo, + (JSAMPARRAY) &rowbuf[cinfo.output_scanline], 1); + } + + if (res_a->id == PSD_THUMB_RES) /* Order is BGR for resource 1033 */ + { + guchar *dst = rgb_buf; + guchar *src = buf; + + for (i = 0; i < gegl_buffer_get_width (buffer) * gegl_buffer_get_height (buffer); ++i) + { + guchar r, g, b; + + r = *(src++); + g = *(src++); + b = *(src++); + *(dst++) = b; + *(dst++) = g; + *(dst++) = r; + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, + gegl_buffer_get_width (buffer), + gegl_buffer_get_height (buffer)), + 0, format, rgb_buf ? rgb_buf : buf, GEGL_AUTO_ROWSTRIDE); + + /* Step 7: Finish decompression */ + jpeg_finish_decompress (&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + jpeg_destroy_decompress (&cinfo); + + /* free up the temporary buffers */ + g_free (rowbuf); + g_free (buf); + g_free (rgb_buf); + + /* Close FILE */ + fclose (f); + + /* At this point you may want to check to see whether any + * corrupt-data warnings occurred (test whether + * jerr.num_warnings is nonzero). + */ + gimp_image_insert_layer (image, layer, NULL, 0); + g_object_unref (buffer); + + return 0; +} + +static gint +load_resource_1039 (const PSDimageres *res_a, + PSDimage *img_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Load ICC profile */ + GimpColorProfile *profile; + gchar *icc_profile; + + IFDBG(2) g_debug ("Process image resource block: 1039: ICC Profile"); + + icc_profile = g_malloc (res_a->data_len); + if (psd_read (input, icc_profile, res_a->data_len, error) < res_a->data_len) + { + psd_set_error (error); + g_free (icc_profile); + return -1; + } + + profile = gimp_color_profile_new_from_icc_profile ((guint8 *) icc_profile, + res_a->data_len, + NULL); + if (profile) + { + if (img_a->color_mode == PSD_CMYK && + gimp_color_profile_is_cmyk (profile)) + { + img_a->cmyk_profile = profile; + /* Store CMYK profile in GimpImage if attached */ + gimp_image_set_simulation_profile (image, img_a->cmyk_profile); + } + else if (img_a->color_mode == PSD_LAB) + { + g_debug ("LAB color profile ignored."); + g_object_unref (profile); + } + else + { + gimp_image_set_color_profile (image, profile); + g_object_unref (profile); + } + } + + g_free (icc_profile); + + return 0; +} + +static gint +load_resource_1045 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load alpha channel names stored as a series of unicode strings + in a GPtrArray */ + + gchar *str; + gint32 block_rem; + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process image resource block 1045: Unicode Alpha Channel Names"); + + if (img_a->alpha_names) + { + gint i; + IFDBG(3) g_debug ("Deleting localised alpha channel names"); + for (i = 0; i < img_a->alpha_names->len; ++i) + { + str = g_ptr_array_index (img_a->alpha_names, i); + g_free (str); + } + g_ptr_array_free (img_a->alpha_names, TRUE); + } + + img_a->alpha_names = g_ptr_array_new (); + + block_rem = res_a->data_len; + while (block_rem > 1) + { + str = fread_unicode_string (&read_len, &write_len, 1, FALSE, input, + error); + if (*error) + return -1; + + IFDBG(3) g_debug ("String: %s, %d, %d", str, read_len, write_len); + if (write_len >= 0) + { + g_ptr_array_add (img_a->alpha_names, (gpointer) str); + } + block_rem -= read_len; + } + + return 0; +} + +static gint +load_resource_1046 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + /* Load indexed color table count */ + gint16 index_count = 0; + + IFDBG(2) g_debug ("Process image resource block: 1046: Indexed Color Table Count"); + + if (psd_read (input, &index_count, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + index_count = GINT16_FROM_BE (index_count); + + IFDBG(3) g_debug ("Indexed color table count: %d", index_count); + /* FIXME - check that we have indexed image */ + if (index_count && index_count < 256) + { + GimpPalette *palette; + + palette = gimp_image_get_palette (image); + if (palette) + while (index_count < gimp_palette_get_color_count (palette)) + gimp_palette_delete_entry (palette, index_count); + } + return 0; +} + +static gint +load_resource_1053 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load image alpha channel ids (tattoos) */ + gint16 tot_rec; + gint16 cidx; + + IFDBG(2) g_debug ("Process image resource block: 1053: Channel ID"); + + tot_rec = res_a->data_len / 4; + if (tot_rec ==0) + return 0; + + img_a->alpha_id = g_malloc (sizeof (img_a->alpha_id) * tot_rec); + img_a->alpha_id_count = tot_rec; + for (cidx = 0; cidx < tot_rec; ++cidx) + { + if (psd_read (input, &img_a->alpha_id[cidx], 4, error) < 4) + { + psd_set_error (error); + return -1; + } + img_a->alpha_id[cidx] = GUINT32_FROM_BE (img_a->alpha_id[cidx]); + + IFDBG(3) g_debug ("Channel id: %d", img_a->alpha_id[cidx]); + } + + return 0; +} + +static gint +load_resource_1058 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + gchar *name; + + GimpParasite *parasite; + gchar *res_data; + + IFDBG(2) g_debug ("Process image resource block: 1058: Exif data"); + + res_data = g_malloc (res_a->data_len); + if (psd_read (input, res_data, res_a->data_len, error) < res_a->data_len) + { + psd_set_error (error); + g_free (res_data); + return -1; + } + + /* Store resource data as a standard psd parasite */ + IFDBG (2) g_debug ("Processing exif data as psd parasite"); + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(3) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, res_data); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + g_free (name); + + g_free (res_data); + return 0; +} + +static gint +load_resource_1069 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + guint16 layer_count = 0; + gint i; + + IFDBG(2) g_debug ("Process image resource block: 1069: Layer Selection ID(s)"); + + if (psd_read (input, &layer_count, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + layer_count = GUINT16_FROM_BE (layer_count); + + /* This should probably not happen, but just in case the block is + * duplicated, let's just free the previous selection. + */ + g_list_free (img_a->layer_selection); + img_a->layer_selection = NULL; + + for (i = 0; i < layer_count; i++) + { + guint32 layer_id = 0; + + if (psd_read (input, &layer_id, 4, error) < 4) + { + psd_set_error (error); + return -1; + } + layer_id = GUINT32_FROM_BE (layer_id); + img_a->layer_selection = g_list_prepend (img_a->layer_selection, GINT_TO_POINTER (layer_id)); + } + + return 0; +} + +static gint +load_resource_1077 (const PSDimageres *res_a, + GimpImage *image, + PSDimage *img_a, + GInputStream *input, + GError **error) +{ + /* Load alpha channel display info */ + + DisplayInfoNew dsp_info; + CMColor ps_color; + gint16 tot_rec; + gint cidx; + + IFDBG(2) g_debug ("Process image resource block 1077: Display Info New"); + + /* For now, skip first 4 bytes since intention is unclear. Seems to be + a version number that is always one, but who knows. */ + if (! psd_seek (input, 4, G_SEEK_CUR, error)) + return -1; + + tot_rec = res_a->data_len / 13; + if (tot_rec == 0) + return 0; + + img_a->alpha_display_info = g_new (PSDchanneldata *, tot_rec); + img_a->alpha_display_count = tot_rec; + for (cidx = 0; cidx < tot_rec; ++cidx) + { + GeglColor *color; + + if (psd_read (input, &dsp_info.colorSpace, 2, error) < 2 || + psd_read (input, &dsp_info.color, 8, error) < 8 || + psd_read (input, &dsp_info.opacity, 2, error) < 2 || + psd_read (input, &dsp_info.mode, 1, error) < 1) + { + psd_set_error (error); + return -1; + } + dsp_info.colorSpace = GINT16_FROM_BE (dsp_info.colorSpace); + ps_color.cmyk.cyan = GUINT16_FROM_BE (dsp_info.color[0]); + ps_color.cmyk.magenta = GUINT16_FROM_BE (dsp_info.color[1]); + ps_color.cmyk.yellow = GUINT16_FROM_BE (dsp_info.color[2]); + ps_color.cmyk.black = GUINT16_FROM_BE (dsp_info.color[3]); + dsp_info.opacity = GINT16_FROM_BE (dsp_info.opacity); + + color = gegl_color_new ("red"); + + switch (dsp_info.colorSpace) + { + case PSD_CS_RGB: + /* TODO: which space? */ + gegl_color_set_rgba_with_space (color, + ps_color.rgb.red / 65535.0, + ps_color.rgb.green / 65535.0, + ps_color.rgb.blue / 65535.0, + 1.0, NULL); + break; + + case PSD_CS_HSB: + { + gfloat hsv[3] = + { + ps_color.hsv.hue / 65535.0f, + ps_color.hsv.saturation / 65535.0f, + ps_color.hsv.value / 65535.0f + }; + + gegl_color_set_pixel (color, babl_format ("HSV float"), hsv); + } + break; + + case PSD_CS_CMYK: + /* TODO: which space? */ + gegl_color_set_cmyk (color, + 1.0 - ps_color.cmyk.cyan / 65535.0, + 1.0 - ps_color.cmyk.magenta / 65535.0, + 1.0 - ps_color.cmyk.yellow / 65535.0, + 1.0 - ps_color.cmyk.black / 65535.0, + 1.0, NULL); + break; + + case PSD_CS_GRAYSCALE: + { + gdouble gray = ps_color.gray.gray / 10000.0; + + /* TODO: which space? */ + gegl_color_set_pixel (color, babl_format_with_space ("Y' double", NULL), &gray); + } + break; + + case PSD_CS_FOCOLTONE: + case PSD_CS_TRUMATCH: + case PSD_CS_HKS: + case PSD_CS_LAB: + case PSD_CS_PANTONE: + case PSD_CS_TOYO: + case PSD_CS_DIC: + case PSD_CS_ANPA: + default: + if (CONVERSION_WARNINGS) + g_message ("Unsupported color space: %d", + dsp_info.colorSpace); + } + + IFDBG(2) g_debug ("PS cSpace: %d, col: %d %d %d %d, opacity: %d, mode: %d", + dsp_info.colorSpace, ps_color.cmyk.cyan, ps_color.cmyk.magenta, + ps_color.cmyk.yellow, ps_color.cmyk.black, dsp_info.opacity, + dsp_info.mode); + + IFDBG(2) g_debug ("cSpace: %d, opacity: %d, mode: %d", + dsp_info.colorSpace, dsp_info.opacity, dsp_info.mode); + + img_a->alpha_display_info[cidx] = g_malloc0 (sizeof (PSDchanneldata)); + img_a->alpha_display_info[cidx]->gimp_color = color; + img_a->alpha_display_info[cidx]->opacity = dsp_info.opacity; + img_a->alpha_display_info[cidx]->ps_mode = dsp_info.mode; + img_a->alpha_display_info[cidx]->ps_cspace = dsp_info.colorSpace; + img_a->alpha_display_info[cidx]->ps_color = ps_color; + } + + return 0; +} + +static gint +load_resource_2000 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + gdouble *controlpoints; + gint32 x[3]; + gint32 y[3]; + GimpPath *path = NULL; + gint16 type = 0; + gint16 init_fill; + gint16 num_rec; + gint16 path_rec; + gint16 cntr; + gint image_width; + gint image_height; + gint i; + gboolean closed; + + /* Load path data from image resources 2000-2998 */ + + IFDBG(2) g_debug ("Process image resource block: %d :Path data", res_a->id); + path_rec = res_a->data_len / 26; + if (path_rec ==0) + return 0; + + if (psd_read (input, &type, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + type = GINT16_FROM_BE (type); + if (type != PSD_PATH_FILL_RULE) + { + IFDBG(1) g_debug ("Unexpected path record type: %d", type); + return -1; + } + + if (! psd_seek (input, 24, G_SEEK_CUR, error)) + { + psd_set_error (error); + return -1; + } + + path_rec--; + if (path_rec ==0) + return 0; + + image_width = gimp_image_get_width (image); + image_height = gimp_image_get_height (image); + + /* Create path */ + if (res_a->id == PSD_WORKING_PATH) + { + /* use "Working Path" for the path name to match the Photoshop display */ + path = gimp_path_new (image, "Working Path"); + } + else + { + /* Use the name stored in the PSD to name the path */ + path = gimp_path_new (image, res_a->name); + } + + gimp_image_insert_path (image, path, NULL, -1); + + while (path_rec > 0) + { + if (psd_read (input, &type, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + type = GINT16_FROM_BE (type); + IFDBG(3) g_debug ("Path record type %d", type); + + if (type == PSD_PATH_FILL_RULE) + { + if (! psd_seek (input, 24, G_SEEK_CUR, error)) + { + psd_set_error (error); + return -1; + } + } + + else if (type == PSD_PATH_FILL_INIT) + { + if (psd_read (input, &init_fill, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + + if (! psd_seek (input, 22, G_SEEK_CUR, error)) + { + psd_set_error (error); + return -1; + } + } + + else if (type == PSD_PATH_CL_LEN + || type == PSD_PATH_OP_LEN) + { + if (psd_read (input, &num_rec, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + num_rec = GINT16_FROM_BE (num_rec); + IFDBG(3) g_debug ("Num path records %d", num_rec); + if (num_rec > path_rec) + { + psd_set_error (error); + return - 1; + } + + if (type == PSD_PATH_CL_LEN) + closed = TRUE; + else + closed = FALSE; + cntr = 0; + controlpoints = g_malloc (sizeof (gdouble) * num_rec * 6); + if (! psd_seek (input, 22, G_SEEK_CUR, error)) + { + psd_set_error (error); + g_free (controlpoints); + return -1; + } + + while (num_rec > 0) + { + if (psd_read (input, &type, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + type = GINT16_FROM_BE (type); + IFDBG(3) g_debug ("Path record type %d", type); + + if (type == PSD_PATH_CL_LNK + || type == PSD_PATH_CL_UNLNK + || type == PSD_PATH_OP_LNK + || type == PSD_PATH_OP_UNLNK) + { + if (psd_read (input, &y[0], 4, error) < 4 || + psd_read (input, &x[0], 4, error) < 4 || + psd_read (input, &y[1], 4, error) < 4 || + psd_read (input, &x[1], 4, error) < 4 || + psd_read (input, &y[2], 4, error) < 4 || + psd_read (input, &x[2], 4, error) < 4) + { + psd_set_error (error); + return -1; + } + for (i = 0; i < 3; ++i) + { + x[i] = GINT32_FROM_BE (x[i]); + controlpoints[cntr] = x[i] / 16777216.0 * image_width; + cntr++; + y[i] = GINT32_FROM_BE (y[i]); + controlpoints[cntr] = y[i] / 16777216.0 * image_height; + cntr++; + } + IFDBG(3) g_debug ("Path points (%d,%d), (%d,%d), (%d,%d)", + x[0], y[0], x[1], y[1], x[2], y[2]); + } + else + { + IFDBG(1) g_debug ("Unexpected path type record %d", type); + if (! psd_seek (input, 24, G_SEEK_CUR, error)) + { + psd_set_error (error); + return -1; + } + } + path_rec--; + num_rec--; + } + /* Add sub-path */ + gimp_path_stroke_new_from_points (path, + GIMP_PATH_STROKE_TYPE_BEZIER, + cntr, controlpoints, closed); + g_free (controlpoints); + } + + else + { + if (! psd_seek (input, 24, G_SEEK_CUR, error)) + { + psd_set_error (error); + return -1; + } + } + + path_rec--; + } + + return 0; +} + +static gint +load_resource_2999 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + gchar *path_name; + gint16 path_flatness_int = 0; + gint16 path_flatness_fixed = 0; + gfloat path_flatness; + GimpParasite *parasite; + gint32 read_len; + gint32 write_len; + + path_name = fread_pascal_string (&read_len, &write_len, 2, input, error); + if (*error || ! path_name) + { + g_printerr ("psd-load: Unable to read clipping path name."); + return -1; + } + + /* Convert from fixed to floating point */ + if (psd_read (input, &path_flatness_int, 2, error) < 2 || + psd_read (input, &path_flatness_fixed, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + path_flatness_fixed = GINT16_FROM_BE (path_flatness_fixed); + path_flatness_int = GINT16_FROM_BE (path_flatness_int); + + /* Converting from Adobe fixed point value to float */ + path_flatness = (path_flatness_fixed - 0.5f) / 65536.0; + path_flatness += path_flatness_int; + + /* Adobe path flatness range is 0.2 to 100.0 */ + path_flatness = CLAMP (path_flatness, 0.2f, 100.0f); + + /* Save to image parasite */ + parasite = gimp_parasite_new (PSD_PARASITE_CLIPPING_PATH, 0, + read_len, path_name); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + parasite = gimp_parasite_new (PSD_PARASITE_PATH_FLATNESS, 0, + sizeof (gfloat), &path_flatness); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + /* Adobe says they ignore the last two bytes, the fill rule */ + if (psd_read (input, &path_flatness_fixed, 2, error) < 2) + { + psd_set_error (error); + return -1; + } + + g_free (path_name); + + return 0; +} diff -Nuar gimp-3.0.6.orig/plug-ins/file-psd/psd-load.c gimp-3.0.6/plug-ins/file-psd/psd-load.c --- gimp-3.0.6.orig/plug-ins/file-psd/psd-load.c 2025-10-05 13:14:02.000000000 -0400 +++ gimp-3.0.6/plug-ins/file-psd/psd-load.c 2026-02-19 13:50:59.614131781 -0500 @@ -796,7 +796,19 @@ if (img_a->num_layers < 0) { img_a->transparency = TRUE; - img_a->num_layers = -img_a->num_layers; + if (img_a->num_layers == G_MININT16) + { + /* FIXME After string freeze should be set translatable */ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Invalid value for number of layers: %d.", + img_a->num_layers); + img_a->num_layers = -1; + return NULL; + } + else + { + img_a->num_layers = abs (img_a->num_layers); + } } if (! img_a->merged_image_only && img_a->num_layers) diff -Nuar gimp-3.0.6.orig/plug-ins/file-psd/psd-util.c gimp-3.0.6/plug-ins/file-psd/psd-util.c --- gimp-3.0.6.orig/plug-ins/file-psd/psd-util.c 2025-10-05 13:14:02.000000000 -0400 +++ gimp-3.0.6/plug-ins/file-psd/psd-util.c 2026-02-19 13:50:11.791807551 -0500 @@ -274,7 +274,8 @@ return NULL; } - str = g_malloc (len); + str = g_malloc (len + 1); + str[len] = '\0'; if (psd_read (input, str, len, error) < len) { psd_set_error (error); @@ -435,7 +436,13 @@ return NULL; } - utf16_str = g_malloc (len * 2); + utf16_str = g_try_malloc (len * 2); + if (! utf16_str) + { + psd_set_error (error); + return NULL; + } + for (i = 0; i < len; ++i) { if (psd_read (input, &utf16_str[i], 2, error) < 2)