From 8cb4d6070b50a5a5939c3228c4d2002b660d53cc Mon Sep 17 00:00:00 2001 From: Michael Natterer Date: Mon, 4 Jun 2007 10:43:31 +0000 Subject: [PATCH] applied patch from Eric Lamarque which adds support for ABR v6 brushes. 2007-06-04 Michael Natterer * app/core/gimpbrush-load.c: applied patch from Eric Lamarque which adds support for ABR v6 brushes. Did some minor cleanups in the patch, reordered functions and generally fixed error handling of the ABR parsers. Fixes bug #377016. svn path=/trunk/; revision=22699 --- ChangeLog | 7 + app/core/gimpbrush-load.c | 464 +++++++++++++++++++++++++++++--------- 2 files changed, 369 insertions(+), 102 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6b8b87da9c..04799bf167 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2007-06-04 Michael Natterer + + * app/core/gimpbrush-load.c: applied patch from Eric Lamarque + which adds support for ABR v6 brushes. Did some minor cleanups in + the patch, reordered functions and generally fixed error handling + of the ABR parsers. Fixes bug #377016. + 2007-06-04 Michael Natterer * libgimpconfig/gimpconfig-deserialize.c: be forward-compatible by diff --git a/app/core/gimpbrush-load.c b/app/core/gimpbrush-load.c index d5420aa289..e35db87de5 100644 --- a/app/core/gimpbrush-load.c +++ b/app/core/gimpbrush-load.c @@ -1,6 +1,8 @@ /* 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 2 of the License, or @@ -86,15 +88,36 @@ struct _AbrSampledBrushHeader /* local function prototypes */ -static GimpBrush * gimp_brush_load_abr_brush (FILE *file, - AbrHeader *abr_hdr, - gint index, - const gchar *filename, - GError **error); -static gchar abr_read_char (FILE *file); -static gint16 abr_read_short (FILE *file); -static gint32 abr_read_long (FILE *file); -static gchar * abr_v2_read_brush_name (FILE *file); +static GList * gimp_brush_load_abr_v12 (FILE *file, + AbrHeader *abr_hdr, + const gchar *filename, + GError **error); +static GList * gimp_brush_load_abr_v6 (FILE *file, + AbrHeader *abr_hdr, + const gchar *filename, + GError **error); +static GimpBrush * gimp_brush_load_abr_brush_v12 (FILE *file, + AbrHeader *abr_hdr, + gint index, + const gchar *filename, + GError **error); +static GimpBrush * gimp_brush_load_abr_brush_v6 (FILE *file, + AbrHeader *abr_hdr, + gint32 max_offset, + gint index, + const gchar *filename, + GError **error); + +static gchar abr_read_char (FILE *file); +static gint16 abr_read_short (FILE *file); +static gint32 abr_read_long (FILE *file); +static gchar * abr_read_ucs2_text (FILE *file); +static gboolean abr_supported (AbrHeader *abr_hdr); +static gboolean abr_reach_8bim_section (FILE *abr, + const gchar *name); +static gint32 abr_rle_decode (FILE *file, + gchar *buffer, + gint32 height); /* public functions */ @@ -400,54 +423,124 @@ gimp_brush_load_abr (const gchar *filename, } abr_hdr.version = abr_read_short (file); - abr_hdr.count = abr_read_short (file); + abr_hdr.count = abr_read_short (file); /* sub-version for ABR v6 */ - /* g_print("version: %d count: %d\n", abr_hdr.version, abr_hdr.count); */ - - if (abr_hdr.version > 2) + if (abr_supported (&abr_hdr)) { - g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ, - _("Fatal parse error in brush file '%s': " - "unable to decode abr format version %d."), - gimp_filename_to_utf8 (filename), abr_hdr.version); - } - else - { - gint i; - - for (i = 0; i < abr_hdr.count; i++) + switch (abr_hdr.version) { - GimpBrush *brush; + case 1: + case 2: + brush_list = gimp_brush_load_abr_v12 (file, &abr_hdr, + filename, error); + break; - brush = gimp_brush_load_abr_brush (file, &abr_hdr, i, - filename, error); - - if (! brush) - { - g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ, - _("Fatal parse error in brush file '%s'"), - gimp_filename_to_utf8 (filename)); - break; - } - - brush_list = g_list_prepend (brush_list, brush); + case 6: + brush_list = gimp_brush_load_abr_v6 (file, &abr_hdr, + filename, error); } } fclose (file); + if (! brush_list && (error && ! *error)) + g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ, + _("Fatal parse error in brush file '%s': " + "unable to decode abr format version %d."), + gimp_filename_to_utf8 (filename), abr_hdr.version); + return g_list_reverse (brush_list); } /* private functions */ +static GList * +gimp_brush_load_abr_v12 (FILE *file, + AbrHeader *abr_hdr, + const gchar *filename, + 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 (file, abr_hdr, i, + filename, &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 (FILE *file, + AbrHeader *abr_hdr, + const gchar *filename, + GError **error) +{ + GList *brush_list = NULL; + gint32 sample_section_size; + gint32 sample_section_end; + gint i = 1; + + if (! abr_reach_8bim_section (file, "samp")) + return brush_list; + + sample_section_size = abr_read_long (file); + sample_section_end = sample_section_size + ftell (file); + + while (ftell (file) < sample_section_end) + { + GimpBrush *brush; + GError *my_error = NULL; + + brush = gimp_brush_load_abr_brush_v6 (file, abr_hdr, sample_section_end, + i, filename, &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 (FILE *file, - AbrHeader *abr_hdr, - gint index, - const gchar *filename, - GError **error) +gimp_brush_load_abr_brush_v12 (FILE *file, + AbrHeader *abr_hdr, + gint index, + const gchar *filename, + GError **error) { GimpBrush *brush = NULL; AbrBrushHeader abr_brush_hdr; @@ -483,7 +576,8 @@ gimp_brush_load_abr_brush (FILE *file, gint size; guchar *mask; gint i; - gchar *name = NULL; + gchar *name; + gchar *sample_name = NULL; gchar *tmp; gshort compress; @@ -491,7 +585,7 @@ gimp_brush_load_abr_brush (FILE *file, abr_sampled_brush_hdr.spacing = abr_read_short (file); if (abr_hdr->version == 2) - name = abr_v2_read_brush_name (file); + sample_name = abr_read_ucs2_text (file); abr_sampled_brush_hdr.antialiasing = abr_read_char (file); @@ -517,16 +611,26 @@ gimp_brush_load_abr_brush (FILE *file, { /* FIXME: support wide brushes */ - g_printerr ("WARING: wide brushes not supported\n"); + g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ, + _("Fatal parse error in brush file '%s': " + "Wide brushes are not supported."), + gimp_filename_to_utf8 (filename)); return NULL; } - if (! name) + tmp = g_filename_display_basename (filename); + if (! sample_name) { - tmp = g_filename_display_basename (filename); + /* build name from filename and index */ name = g_strdup_printf ("%s-%03d", tmp, index); - g_free (tmp); } + 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, @@ -554,59 +658,9 @@ gimp_brush_load_abr_brush (FILE *file, */ if (! compress) - { - fread (mask, size, 1, file); - } + fread (mask, size, 1, file); else - { - gint16 *cscanline_len; - gint32 n; - gchar ch; - gint i, j, c; - - /* read compressed size foreach scanline */ - cscanline_len = g_new0 (gshort, height); - - for (i = 0; i < height; i++) - cscanline_len[i] = abr_read_short (file); - - /* unpack each scanline data */ - for (i = 0; i < height; i++) - { - for (j = 0; j < cscanline_len[i];) - { - n = abr_read_char (file); - 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; - ch = abr_read_char (file); - j++; - - for (c = 0; c < n; c++, mask++) - *mask = ch; - } - else - { - /* read the following n + 1 chars (no compr) */ - - for (c = 0; c < n + 1; c++, j++, mask++) - *mask = (guchar) abr_read_char (file); - } - } - } - - g_free (cscanline_len); - } + abr_rle_decode (file, (gchar *) mask, height); } break; @@ -619,6 +673,100 @@ gimp_brush_load_abr_brush (FILE *file, return brush; } +static GimpBrush * +gimp_brush_load_abr_brush_v6 (FILE *file, + AbrHeader *abr_hdr, + gint32 max_offset, + gint index, + const gchar *filename, + GError **error) +{ + GimpBrush *brush = NULL; + guchar *mask; + + gint32 brush_size; + gint32 brush_end; + gint32 complement_to_4; + gint32 next_brush; + + gint32 top, left, bottom, right; + gint16 depth; + gchar compress; + + gint32 width, height; + gint32 size; + + gchar *tmp; + gchar *name; + gint r; + + brush_size = abr_read_long (file); + brush_end = brush_size; + /* complement to 4 */ + while (brush_end % 4 != 0) brush_end++; + complement_to_4 = brush_end - brush_size; + next_brush = ftell (file) + brush_end; + + if (abr_hdr->count == 1) + /* discard key and short coordinates and unknown short */ + r = fseek (file, 47, SEEK_CUR); + else + /* discard key and unknown bytes */ + r = fseek (file, 301, SEEK_CUR); + + if (r == -1) + { + g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ, + _("Fatal parse error in brush file '%s': " + "File appears truncated."), + gimp_filename_to_utf8 (filename)); + return NULL; + } + + top = abr_read_long (file); + left = abr_read_long (file); + bottom = abr_read_long (file); + right = abr_read_long (file); + depth = abr_read_short (file); + compress = abr_read_char (file); + + width = right - left; + height = bottom - top; + size = width * (depth >> 3) * height; + + tmp = g_filename_display_basename (filename); + 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->spacing = 25; /* real value needs 8BIMdesc section parser */ + brush->x_axis.x = width / 2.0; + brush->x_axis.y = 0.0; + brush->y_axis.x = 0.0; + brush->y_axis.y = height / 2.0; + brush->mask = temp_buf_new (width, height, 1, 0, 0, NULL); + + mask = temp_buf_data (brush->mask); + + /* data decoding */ + if (! compress) + /* not compressed - read raw bytes as brush data */ + fread (mask, size, 1, file); + else + abr_rle_decode (file, (gchar *) mask, height); + + fseek (file, next_brush, SEEK_SET); + + return brush; +} + static gchar abr_read_char (FILE *file) { @@ -646,7 +794,7 @@ abr_read_long (FILE *file) } static gchar * -abr_v2_read_brush_name (FILE *file) +abr_read_ucs2_text (FILE *file) { gchar *name_ucs2; gchar *name_utf8; @@ -666,13 +814,125 @@ abr_v2_read_brush_name (FILE *file) name_ucs2 = g_new (gchar, len); for (i = 0; i < len; i++) - name_ucs2[i] = abr_read_char (file); + name_ucs2[i] = abr_read_char (file); name_utf8 = g_convert (name_ucs2, len, "UTF-8", "UCS-2BE", NULL, NULL, NULL); - g_free(name_ucs2); + g_free (name_ucs2); return name_utf8; } + +static gboolean +abr_supported (AbrHeader *abr_hdr) +{ + switch (abr_hdr->version) + { + case 1: + case 2: + return TRUE; + break; + + case 6: + /* in this case, count contains format sub-version */ + if (abr_hdr->count == 1 || abr_hdr->count == 2) + return TRUE; + break; + } + + return FALSE; +} + +static gboolean +abr_reach_8bim_section (FILE *abr, + const gchar *name) +{ + gchar tag[4]; + gchar tagname[5]; + gint32 section_size; + gint r; + + while (! feof (abr)) + { + r = fread (&tag, 1, 4, abr); + if (r != 4) + return FALSE; + + if (strncmp (tag, "8BIM", 4)) + return FALSE; + + r = fread (&tagname, 1, 4, abr); + if (r != 4) + return FALSE; + + tagname[4] = '\0'; + + if (! strncmp (tagname, name, 4)) + return TRUE; + + section_size = abr_read_long (abr); + r = fseek (abr, section_size, SEEK_CUR); + if (r == -1) + return FALSE; + } + + return FALSE; +} + +static gint32 +abr_rle_decode (FILE *file, + gchar *buffer, + gint32 height) +{ + gchar ch; + gint i, j, c; + gshort *cscanline_len; + gchar *data = buffer; + + /* read compressed size foreach scanline */ + cscanline_len = g_new0 (gshort, height); + for (i = 0; i < height; i++) + cscanline_len[i] = abr_read_short (file); + + /* unpack each scanline data */ + for (i = 0; i < height; i++) + { + for (j = 0; j < cscanline_len[i];) + { + gint32 n = abr_read_char (file); + + 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; + ch = abr_read_char (file); + j++; + + for (c = 0; c < n; c++, data++) + *data = ch; + } + else + { + /* read the following n + 1 chars (no compr) */ + + for (c = 0; c < n + 1; c++, j++, data++) + *data = abr_read_char (file); + } + } + } + + g_free (cscanline_len); + + return 0; +}