summaryrefslogtreecommitdiffstats
path: root/app/core/gimpbrush-load.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/core/gimpbrush-load.c1213
1 files changed, 1213 insertions, 0 deletions
diff --git a/app/core/gimpbrush-load.c b/app/core/gimpbrush-load.c
new file mode 100644
index 0000000..51bf1da
--- /dev/null
+++ b/app/core/gimpbrush-load.c
@@ -0,0 +1,1213 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-load.c
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpbrush.h"
+#include "gimpbrush-header.h"
+#include "gimpbrush-load.h"
+#include "gimpbrush-private.h"
+#include "gimppattern-header.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+/* stuff from abr2gbr Copyright (C) 2001 Marco Lamberto <lm@sunnyspot.org> */
+/* the above is GPL see http://the.sunnyspot.org/gimp/ */
+
+typedef struct _AbrHeader AbrHeader;
+typedef struct _AbrBrushHeader AbrBrushHeader;
+typedef struct _AbrSampledBrushHeader AbrSampledBrushHeader;
+
+struct _AbrHeader
+{
+ gint16 version;
+ gint16 count;
+};
+
+struct _AbrBrushHeader
+{
+ gint16 type;
+ gint32 size;
+};
+
+struct _AbrSampledBrushHeader
+{
+ gint32 misc;
+ gint16 spacing;
+ gchar antialiasing;
+ gint16 bounds[4];
+ gint32 bounds_long[4];
+ gint16 depth;
+ gboolean wide;
+};
+
+
+/* local function prototypes */
+
+static GList * gimp_brush_load_abr_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error);
+static GList * gimp_brush_load_abr_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error);
+static GimpBrush * gimp_brush_load_abr_brush_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint index,
+ GFile *file,
+ GError **error);
+static GimpBrush * gimp_brush_load_abr_brush_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint32 max_offset,
+ gint index,
+ GFile *file,
+ GError **error);
+
+static gchar abr_read_char (GDataInputStream *input,
+ GError **error);
+static gint16 abr_read_short (GDataInputStream *input,
+ GError **error);
+static gint32 abr_read_long (GDataInputStream *input,
+ GError **error);
+static gchar * abr_read_ucs2_text (GDataInputStream *input,
+ GError **error);
+static gboolean abr_supported (AbrHeader *abr_hdr,
+ GError **error);
+static gboolean abr_reach_8bim_section (GDataInputStream *input,
+ const gchar *name,
+ GError **error);
+static gboolean abr_rle_decode (GDataInputStream *input,
+ gchar *buffer,
+ gsize buffer_size,
+ gint32 height,
+ GError **error);
+
+
+/* public functions */
+
+GList *
+gimp_brush_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpBrush *brush;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ brush = gimp_brush_load_brush (context, file, input, error);
+ if (! brush)
+ return NULL;
+
+ return g_list_prepend (NULL, brush);
+}
+
+GimpBrush *
+gimp_brush_load_brush (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpBrush *brush;
+ gsize bn_size;
+ GimpBrushHeader header;
+ gchar *name = NULL;
+ guchar *mask;
+ gsize bytes_read;
+ gssize i, size;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* read the header */
+ if (! g_input_stream_read_all (input, &header, sizeof (header),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (header))
+ {
+ return NULL;
+ }
+
+ /* rearrange the bytes in each unsigned int */
+ header.header_size = g_ntohl (header.header_size);
+ header.version = g_ntohl (header.version);
+ header.width = g_ntohl (header.width);
+ header.height = g_ntohl (header.height);
+ header.bytes = g_ntohl (header.bytes);
+ header.magic_number = g_ntohl (header.magic_number);
+ header.spacing = g_ntohl (header.spacing);
+
+ /* Check for correct file format */
+
+ if (header.width == 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Width = 0."));
+ return NULL;
+ }
+
+ if (header.height == 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Height = 0."));
+ return NULL;
+ }
+
+ if (header.bytes == 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Bytes = 0."));
+ return NULL;
+ }
+
+ if (header.width > GIMP_BRUSH_MAX_SIZE ||
+ header.height > GIMP_BRUSH_MAX_SIZE ||
+ G_MAXSIZE / header.width / header.height / MAX (4, header.bytes) < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: %dx%d over max size."),
+ header.width, header.height);
+ return NULL;
+ }
+
+ switch (header.version)
+ {
+ case 1:
+ /* If this is a version 1 brush, set the fp back 8 bytes */
+ if (! g_seekable_seek (G_SEEKABLE (input), -8, G_SEEK_CUR,
+ NULL, error))
+ return NULL;
+
+ header.header_size += 8;
+ /* spacing is not defined in version 1 */
+ header.spacing = 25;
+ break;
+
+ case 3: /* cinepaint brush */
+ if (header.bytes == 18 /* FLOAT16_GRAY_GIMAGE */)
+ {
+ header.bytes = 2;
+ }
+ else
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Unknown depth %d."),
+ header.bytes);
+ return NULL;
+ }
+ /* fallthrough */
+
+ case 2:
+ if (header.magic_number == GIMP_BRUSH_MAGIC)
+ break;
+
+ default:
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Unknown version %d."),
+ header.version);
+ return NULL;
+ }
+
+ if (header.header_size < sizeof (GimpBrushHeader))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unsupported brush format"));
+ return NULL;
+ }
+
+ /* Read in the brush name */
+ if ((bn_size = (header.header_size - sizeof (header))))
+ {
+ gchar *utf8;
+
+ if (bn_size > GIMP_BRUSH_MAX_NAME)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid header data in '%s': "
+ "Brush name is too long: %lu"),
+ gimp_file_get_utf8_name (file),
+ (gulong) bn_size);
+ return NULL;
+ }
+
+ name = g_new0 (gchar, bn_size + 1);
+
+ if (! g_input_stream_read_all (input, name, bn_size,
+ &bytes_read, NULL, error) ||
+ bytes_read != bn_size)
+ {
+ g_free (name);
+ return NULL;
+ }
+
+ utf8 = gimp_any_to_utf8 (name, bn_size - 1,
+ _("Invalid UTF-8 string in brush file '%s'."),
+ gimp_file_get_utf8_name (file));
+ g_free (name);
+ name = utf8;
+ }
+
+ if (! name)
+ name = g_strdup (_("Unnamed"));
+
+ brush = g_object_new (GIMP_TYPE_BRUSH,
+ "name", name,
+ "mime-type", "image/x-gimp-gbr",
+ NULL);
+ g_free (name);
+
+ brush->priv->mask = gimp_temp_buf_new (header.width, header.height,
+ babl_format ("Y u8"));
+
+ mask = gimp_temp_buf_get_data (brush->priv->mask);
+ size = header.width * header.height * header.bytes;
+
+ switch (header.bytes)
+ {
+ case 1:
+ success = (g_input_stream_read_all (input, mask, size,
+ &bytes_read, NULL, error) &&
+ bytes_read == size);
+
+ /* For backwards-compatibility, check if a pattern follows.
+ * The obsolete .gpb format did it this way.
+ */
+ if (success)
+ {
+ GimpPatternHeader ph;
+ goffset rewind;
+
+ rewind = g_seekable_tell (G_SEEKABLE (input));
+
+ if (g_input_stream_read_all (input, &ph, sizeof (GimpPatternHeader),
+ &bytes_read, NULL, NULL) &&
+ bytes_read == sizeof (GimpPatternHeader))
+ {
+ /* rearrange the bytes in each unsigned int */
+ ph.header_size = g_ntohl (ph.header_size);
+ ph.version = g_ntohl (ph.version);
+ ph.width = g_ntohl (ph.width);
+ ph.height = g_ntohl (ph.height);
+ ph.bytes = g_ntohl (ph.bytes);
+ ph.magic_number = g_ntohl (ph.magic_number);
+
+ if (ph.magic_number == GIMP_PATTERN_MAGIC &&
+ ph.version == 1 &&
+ ph.header_size > sizeof (GimpPatternHeader) &&
+ ph.bytes == 3 &&
+ ph.width == header.width &&
+ ph.height == header.height &&
+ g_input_stream_skip (input,
+ ph.header_size -
+ sizeof (GimpPatternHeader),
+ NULL, NULL) ==
+ ph.header_size - sizeof (GimpPatternHeader))
+ {
+ guchar *pixmap;
+ gssize pixmap_size;
+
+ brush->priv->pixmap =
+ gimp_temp_buf_new (header.width, header.height,
+ babl_format ("R'G'B' u8"));
+
+ pixmap = gimp_temp_buf_get_data (brush->priv->pixmap);
+
+ pixmap_size = gimp_temp_buf_get_data_size (brush->priv->pixmap);
+
+ success = (g_input_stream_read_all (input, pixmap,
+ pixmap_size,
+ &bytes_read, NULL,
+ error) &&
+ bytes_read == pixmap_size);
+ }
+ else
+ {
+ /* seek back if pattern wasn't found */
+ success = g_seekable_seek (G_SEEKABLE (input),
+ rewind, G_SEEK_SET,
+ NULL, error);
+ }
+ }
+ }
+ break;
+
+ case 2: /* cinepaint brush, 16 bit floats */
+ {
+ guchar buf[8 * 1024];
+
+ for (i = 0; success && i < size;)
+ {
+ gssize bytes = MIN (size - i, sizeof (buf));
+
+ success = (g_input_stream_read_all (input, buf, bytes,
+ &bytes_read, NULL, error) &&
+ bytes_read == bytes);
+
+ if (success)
+ {
+ guint16 *b = (guint16 *) buf;
+
+ i += bytes;
+
+ for (; bytes > 0; bytes -= 2, mask++, b++)
+ {
+ union
+ {
+ guint16 u[2];
+ gfloat f;
+ } short_float;
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ short_float.u[0] = 0;
+ short_float.u[1] = GUINT16_FROM_BE (*b);
+#else
+ short_float.u[0] = GUINT16_FROM_BE (*b);
+ short_float.u[1] = 0;
+#endif
+
+ *mask = (guchar) (short_float.f * 255.0 + 0.5);
+ }
+ }
+ }
+ }
+ break;
+
+ case 4:
+ {
+ guchar *pixmap;
+ guchar buf[8 * 1024];
+
+ brush->priv->pixmap = gimp_temp_buf_new (header.width, header.height,
+ babl_format ("R'G'B' u8"));
+ pixmap = gimp_temp_buf_get_data (brush->priv->pixmap);
+
+ for (i = 0; success && i < size;)
+ {
+ gssize bytes = MIN (size - i, sizeof (buf));
+
+ success = (g_input_stream_read_all (input, buf, bytes,
+ &bytes_read, NULL, error) &&
+ bytes_read == bytes);
+
+ if (success)
+ {
+ guchar *b = buf;
+
+ i += bytes;
+
+ for (; bytes > 0; bytes -= 4, pixmap += 3, mask++, b += 4)
+ {
+ pixmap[0] = b[0];
+ pixmap[1] = b[1];
+ pixmap[2] = b[2];
+
+ mask[0] = b[3];
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ g_object_unref (brush);
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file:\n"
+ "Unsupported brush depth %d\n"
+ "GIMP brushes must be GRAY or RGBA."),
+ header.bytes);
+ return NULL;
+ }
+
+ if (! success)
+ {
+ g_object_unref (brush);
+ return NULL;
+ }
+
+ brush->priv->spacing = header.spacing;
+ brush->priv->x_axis.x = header.width / 2.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = header.height / 2.0;
+
+ return brush;
+}
+
+GList *
+gimp_brush_load_abr (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GDataInputStream *data_input;
+ AbrHeader abr_hdr;
+ GList *brush_list = NULL;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data_input = g_data_input_stream_new (input);
+
+ g_data_input_stream_set_byte_order (data_input,
+ G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
+
+ abr_hdr.version = abr_read_short (data_input, &my_error);
+ if (my_error)
+ goto done;
+
+ /* sub-version for ABR v6 */
+ abr_hdr.count = abr_read_short (data_input, &my_error);
+ if (my_error)
+ goto done;
+
+ if (abr_supported (&abr_hdr, &my_error))
+ {
+ switch (abr_hdr.version)
+ {
+ case 1:
+ case 2:
+ brush_list = gimp_brush_load_abr_v12 (data_input, &abr_hdr,
+ file, &my_error);
+ break;
+
+ case 10:
+ case 6:
+ brush_list = gimp_brush_load_abr_v6 (data_input, &abr_hdr,
+ file, &my_error);
+ break;
+ }
+ }
+
+ done:
+
+ g_object_unref (data_input);
+
+ if (! brush_list)
+ {
+ if (! my_error)
+ g_set_error (&my_error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unable to decode abr format version %d."),
+ abr_hdr.version);
+ }
+
+ if (my_error)
+ g_propagate_error (error, my_error);
+
+ return g_list_reverse (brush_list);
+}
+
+
+/* private functions */
+
+static GList *
+gimp_brush_load_abr_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error)
+{
+ GList *brush_list = NULL;
+ gint i;
+
+ for (i = 0; i < abr_hdr->count; i++)
+ {
+ GimpBrush *brush;
+ GError *my_error = NULL;
+
+ brush = gimp_brush_load_abr_brush_v12 (input, abr_hdr, i,
+ file, &my_error);
+
+ /* a NULL brush without an error means an unsupported brush
+ * type was encountered, silently skip it and try the next one
+ */
+
+ if (brush)
+ {
+ brush_list = g_list_prepend (brush_list, brush);
+ }
+ else if (my_error)
+ {
+ g_propagate_error (error, my_error);
+ break;
+ }
+ }
+
+ return brush_list;
+}
+
+static GList *
+gimp_brush_load_abr_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error)
+{
+ GList *brush_list = NULL;
+ gint32 sample_section_size;
+ goffset sample_section_end;
+ gint i = 1;
+
+ if (! abr_reach_8bim_section (input, "samp", error))
+ return brush_list;
+
+ sample_section_size = abr_read_long (input, error);
+ if (error && *error)
+ return brush_list;
+
+ sample_section_end = (sample_section_size +
+ g_seekable_tell (G_SEEKABLE (input)));
+
+ while (g_seekable_tell (G_SEEKABLE (input)) < sample_section_end)
+ {
+ GimpBrush *brush;
+ GError *my_error = NULL;
+
+ brush = gimp_brush_load_abr_brush_v6 (input, abr_hdr, sample_section_end,
+ i, file, &my_error);
+
+ /* a NULL brush without an error means an unsupported brush
+ * type was encountered, silently skip it and try the next one
+ */
+
+ if (brush)
+ {
+ brush_list = g_list_prepend (brush_list, brush);
+ }
+ else if (my_error)
+ {
+ g_propagate_error (error, my_error);
+ break;
+ }
+
+ i++;
+ }
+
+ return brush_list;
+}
+
+static GimpBrush *
+gimp_brush_load_abr_brush_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint index,
+ GFile *file,
+ GError **error)
+{
+ GimpBrush *brush = NULL;
+ AbrBrushHeader abr_brush_hdr;
+
+ abr_brush_hdr.type = abr_read_short (input, error);
+ if (error && *error)
+ return NULL;
+
+ abr_brush_hdr.size = abr_read_long (input, error);
+ if (error && *error)
+ return NULL;
+
+ if (abr_brush_hdr.size < 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush size value corrupt."));
+ return NULL;
+ }
+
+ /* g_print(" + BRUSH\n | << type: %i block size: %i bytes\n",
+ * abr_brush_hdr.type, abr_brush_hdr.size);
+ */
+
+ switch (abr_brush_hdr.type)
+ {
+ case 1: /* computed brush */
+ /* FIXME: support it!
+ *
+ * We can probabaly feed the info into the generated brush code
+ * and get a usable brush back. It seems to support the same
+ * types -akl
+ */
+ g_printerr ("WARNING: computed brush unsupported, skipping.\n");
+ g_seekable_seek (G_SEEKABLE (input), abr_brush_hdr.size,
+ G_SEEK_CUR, NULL, NULL);
+ break;
+
+ case 2: /* sampled brush */
+ {
+ AbrSampledBrushHeader abr_sampled_brush_hdr;
+ gint width, height;
+ gint bytes;
+ gint size;
+ guchar *mask;
+ gint i;
+ gchar *name;
+ gchar *sample_name = NULL;
+ gchar *tmp;
+ gshort compress;
+
+ abr_sampled_brush_hdr.misc = abr_read_long (input, error);
+ if (error && *error)
+ break;
+
+ abr_sampled_brush_hdr.spacing = abr_read_short (input, error);
+ if (error && *error)
+ break;
+
+ if (abr_hdr->version == 2)
+ {
+ sample_name = abr_read_ucs2_text (input, error);
+ if (error && *error)
+ break;
+ }
+
+ abr_sampled_brush_hdr.antialiasing = abr_read_char (input, error);
+ if (error && *error)
+ break;
+
+ for (i = 0; i < 4; i++)
+ {
+ abr_sampled_brush_hdr.bounds[i] = abr_read_short (input, error);
+ if (error && *error)
+ break;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ abr_sampled_brush_hdr.bounds_long[i] = abr_read_long (input, error);
+ if (error && *error)
+ break;
+ }
+
+ abr_sampled_brush_hdr.depth = abr_read_short (input, error);
+ if (error && *error)
+ break;
+
+ height = (abr_sampled_brush_hdr.bounds_long[2] -
+ abr_sampled_brush_hdr.bounds_long[0]); /* bottom - top */
+ width = (abr_sampled_brush_hdr.bounds_long[3] -
+ abr_sampled_brush_hdr.bounds_long[1]); /* right - left */
+ bytes = abr_sampled_brush_hdr.depth >> 3;
+
+ /* g_print ("width %i height %i bytes %i\n", width, height, bytes); */
+
+ if (width < 1 || width > 10000 ||
+ height < 1 || height > 10000 ||
+ bytes < 1 || bytes > 1 ||
+ G_MAXSIZE / width / height / bytes < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush dimensions out of range."));
+ break;
+ }
+
+ abr_sampled_brush_hdr.wide = height > 16384;
+
+ if (abr_sampled_brush_hdr.wide)
+ {
+ /* FIXME: support wide brushes */
+
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Wide brushes are not supported."));
+ break;
+ }
+
+ tmp = g_path_get_basename (gimp_file_get_utf8_name (file));
+ if (! sample_name)
+ {
+ /* build name from filename and index */
+ name = g_strdup_printf ("%s-%03d", tmp, index);
+ }
+ else
+ {
+ /* build name from filename and sample name */
+ name = g_strdup_printf ("%s-%s", tmp, sample_name);
+ g_free (sample_name);
+ }
+ g_free (tmp);
+
+ brush = g_object_new (GIMP_TYPE_BRUSH,
+ "name", name,
+ /* FIXME: MIME type!! */
+ "mime-type", "application/x-photoshop-abr",
+ NULL);
+
+ g_free (name);
+
+ brush->priv->spacing = abr_sampled_brush_hdr.spacing;
+ brush->priv->x_axis.x = width / 2.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = height / 2.0;
+ brush->priv->mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y u8"));
+
+ mask = gimp_temp_buf_get_data (brush->priv->mask);
+ size = width * height * bytes;
+
+ compress = abr_read_char (input, error);
+ if (error && *error)
+ {
+ g_object_unref (brush);
+ brush = NULL;
+ break;
+ }
+
+ /* g_print(" | << size: %dx%d %d bit (%d bytes) %s\n",
+ * width, height, abr_sampled_brush_hdr.depth, size,
+ * comppres ? "compressed" : "raw");
+ */
+
+ if (! compress)
+ {
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ mask, size,
+ &bytes_read, NULL, error) ||
+ bytes_read != size)
+ {
+ g_object_unref (brush);
+ brush = NULL;
+ break;
+ }
+ }
+ else
+ {
+ if (! abr_rle_decode (input, (gchar *) mask, size, height, error))
+ {
+ g_object_unref (brush);
+ brush = NULL;
+ break;
+ }
+ }
+ }
+ break;
+
+ default:
+ g_printerr ("WARNING: unknown brush type, skipping.\n");
+ g_seekable_seek (G_SEEKABLE (input), abr_brush_hdr.size,
+ G_SEEK_CUR, NULL, NULL);
+ break;
+ }
+
+ return brush;
+}
+
+static GimpBrush *
+gimp_brush_load_abr_brush_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint32 max_offset,
+ gint index,
+ GFile *file,
+ GError **error)
+{
+ GimpBrush *brush = NULL;
+ guchar *mask;
+
+ gint32 brush_size;
+ gint32 brush_end;
+ goffset next_brush;
+
+ gint32 top, left, bottom, right;
+ gint16 depth;
+ gchar compress;
+
+ gint32 width, height;
+ gint32 size;
+
+ gchar *tmp;
+ gchar *name;
+ gboolean r;
+
+ brush_size = abr_read_long (input, error);
+ if (error && *error)
+ return NULL;
+
+ if (brush_size < 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush size value corrupt."));
+ return NULL;
+ }
+
+ brush_end = brush_size;
+
+ /* complement to 4 */
+ while (brush_end % 4 != 0)
+ brush_end++;
+
+ next_brush = (brush_end + g_seekable_tell (G_SEEKABLE (input)));
+
+ if (abr_hdr->count == 1)
+ {
+ /* discard key and short coordinates and unknown short */
+ r = g_seekable_seek (G_SEEKABLE (input), 47, G_SEEK_CUR,
+ NULL, error);
+ }
+ else
+ {
+ /* discard key and unknown bytes */
+ r = g_seekable_seek (G_SEEKABLE (input), 301, G_SEEK_CUR,
+ NULL, error);
+ }
+
+ if (! r)
+ {
+ g_prefix_error (error,
+ _("Fatal parse error in brush file: "
+ "File appears truncated: "));
+ return NULL;
+ }
+
+ top = abr_read_long (input, error); if (error && *error) return NULL;
+ left = abr_read_long (input, error); if (error && *error) return NULL;
+ bottom = abr_read_long (input, error); if (error && *error) return NULL;
+ right = abr_read_long (input, error); if (error && *error) return NULL;
+ depth = abr_read_short (input, error); if (error && *error) return NULL;
+ compress = abr_read_char (input, error); if (error && *error) return NULL;
+
+ depth = depth >> 3;
+
+ width = right - left;
+ height = bottom - top;
+ size = width * depth * height;
+
+#if 0
+ g_printerr ("width %i height %i depth %i compress %i\n",
+ width, height, depth, compress);
+#endif
+
+ if (width < 1 || width > 10000 ||
+ height < 1 || height > 10000 ||
+ depth < 1 || depth > 1 ||
+ G_MAXSIZE / width / height / depth < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush dimensions out of range."));
+ return NULL;
+ }
+
+ if (compress < 0 || compress > 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Unknown compression method."));
+ return NULL;
+ }
+
+ tmp = g_path_get_basename (gimp_file_get_utf8_name (file));
+ name = g_strdup_printf ("%s-%03d", tmp, index);
+ g_free (tmp);
+
+ brush = g_object_new (GIMP_TYPE_BRUSH,
+ "name", name,
+ /* FIXME: MIME type!! */
+ "mime-type", "application/x-photoshop-abr",
+ NULL);
+
+ g_free (name);
+
+ brush->priv->spacing = 25; /* real value needs 8BIMdesc section parser */
+ brush->priv->x_axis.x = width / 2.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = height / 2.0;
+ brush->priv->mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y u8"));
+
+ mask = gimp_temp_buf_get_data (brush->priv->mask);
+
+ /* data decoding */
+ if (! compress)
+ {
+ /* not compressed - read raw bytes as brush data */
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ mask, size,
+ &bytes_read, NULL, error) ||
+ bytes_read != size)
+ {
+ g_object_unref (brush);
+ return NULL;
+ }
+ }
+ else
+ {
+ if (! abr_rle_decode (input, (gchar *) mask, size, height, error))
+ {
+ g_object_unref (brush);
+ return NULL;
+ }
+ }
+
+ if (g_seekable_tell (G_SEEKABLE (input)) <= next_brush)
+ g_seekable_seek (G_SEEKABLE (input), next_brush, G_SEEK_SET,
+ NULL, NULL);
+
+ return brush;
+}
+
+static gchar
+abr_read_char (GDataInputStream *input,
+ GError **error)
+{
+ return g_data_input_stream_read_byte (input, NULL, error);
+}
+
+static gint16
+abr_read_short (GDataInputStream *input,
+ GError **error)
+{
+ return g_data_input_stream_read_int16 (input, NULL, error);
+}
+
+static gint32
+abr_read_long (GDataInputStream *input,
+ GError **error)
+{
+ return g_data_input_stream_read_int32 (input, NULL, error);
+}
+
+static gchar *
+abr_read_ucs2_text (GDataInputStream *input,
+ GError **error)
+{
+ gchar *name_ucs2;
+ gchar *name_utf8;
+ gint len;
+ gint i;
+
+ /* two-bytes characters encoded (UCS-2)
+ * format:
+ * long : number of characters in string
+ * data : zero terminated UCS-2 string
+ */
+
+ len = 2 * abr_read_long (input, error);
+ if (len <= 0)
+ return NULL;
+
+ name_ucs2 = g_new (gchar, len);
+
+ for (i = 0; i < len; i++)
+ {
+ name_ucs2[i] = abr_read_char (input, error);
+ if (error && *error)
+ {
+ g_free (name_ucs2);
+ return NULL;
+ }
+ }
+
+ name_utf8 = g_convert (name_ucs2, len,
+ "UTF-8", "UCS-2BE",
+ NULL, NULL, NULL);
+
+ g_free (name_ucs2);
+
+ return name_utf8;
+}
+
+static gboolean
+abr_supported (AbrHeader *abr_hdr,
+ GError **error)
+{
+ switch (abr_hdr->version)
+ {
+ case 1:
+ case 2:
+ return TRUE;
+ break;
+
+ case 10:
+ case 6:
+ /* in this case, count contains format sub-version */
+ if (abr_hdr->count == 1 || abr_hdr->count == 2)
+ return TRUE;
+
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Unable to decode abr format version %d."),
+
+ /* horrid subversion display, but better than
+ * having yet another translatable string for
+ * this
+ */
+ abr_hdr->version * 10 + abr_hdr->count);
+ break;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+abr_reach_8bim_section (GDataInputStream *input,
+ const gchar *name,
+ GError **error)
+{
+ while (TRUE)
+ {
+ gchar tag[4];
+ gchar tagname[5];
+ guint32 section_size;
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ tag, 4,
+ &bytes_read, NULL, error) ||
+ bytes_read != 4)
+ return FALSE;
+
+ if (strncmp (tag, "8BIM", 4))
+ return FALSE;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ tagname, 4,
+ &bytes_read, NULL, error) ||
+ bytes_read != 4)
+ return FALSE;
+
+ tagname[4] = '\0';
+
+ if (! strncmp (tagname, name, 4))
+ return TRUE;
+
+ section_size = abr_read_long (input, error);
+ if (error && *error)
+ return FALSE;
+
+ if (! g_seekable_seek (G_SEEKABLE (input), section_size, G_SEEK_CUR,
+ NULL, error))
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+abr_rle_decode (GDataInputStream *input,
+ gchar *buffer,
+ gsize buffer_size,
+ gint32 height,
+ GError **error)
+{
+ gint i, j;
+ gshort *cscanline_len = NULL;
+ gchar *cdata = NULL;
+ gchar *data = buffer;
+
+ /* read compressed size foreach scanline */
+ cscanline_len = gegl_scratch_new (gshort, height);
+ for (i = 0; i < height; i++)
+ {
+ cscanline_len[i] = abr_read_short (input, error);
+ if ((error && *error) || cscanline_len[i] <= 0)
+ goto err;
+ }
+
+ /* unpack each scanline data */
+ for (i = 0; i < height; i++)
+ {
+ gint len;
+ gsize bytes_read;
+
+ len = cscanline_len[i];
+
+ cdata = gegl_scratch_alloc (len);
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ cdata, len,
+ &bytes_read, NULL, error) ||
+ bytes_read != len)
+ {
+ goto err;
+ }
+
+ for (j = 0; j < len;)
+ {
+ gint32 n = cdata[j++];
+
+ if (n >= 128) /* force sign */
+ n -= 256;
+
+ if (n < 0)
+ {
+ /* copy the following char -n + 1 times */
+
+ if (n == -128) /* it's a nop */
+ continue;
+
+ n = -n + 1;
+
+ if (j + 1 > len || (data - buffer) + n > buffer_size)
+ goto err;
+
+ memset (data, cdata[j], n);
+
+ j += 1;
+ data += n;
+ }
+ else
+ {
+ /* read the following n + 1 chars (no compr) */
+
+ n = n + 1;
+
+ if (j + n > len || (data - buffer) + n > buffer_size)
+ goto err;
+
+ memcpy (data, &cdata[j], n);
+
+ j += n;
+ data += n;
+ }
+ }
+
+ g_clear_pointer (&cdata, gegl_scratch_free);
+ }
+
+ g_clear_pointer (&cscanline_len, gegl_scratch_free);
+
+ return TRUE;
+
+err:
+ g_clear_pointer (&cdata, gegl_scratch_free);
+ g_clear_pointer (&cscanline_len, gegl_scratch_free);
+ if (error && ! *error)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "RLE compressed brush data corrupt."));
+ }
+ return FALSE;
+}