/* 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-utils.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 gboolean abr_supported (AbrHeader *abr_hdr,
GError **error);
static gboolean abr_reach_8bim_section (GDataInputStream *input,
const gchar *name,
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);
if (! gimp_data_input_stream_read_short (data_input, &abr_hdr.version, &my_error))
goto done;
/* sub-version for ABR v6 */
if (! gimp_data_input_stream_read_short (data_input, &abr_hdr.count, &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;
if (! gimp_data_input_stream_read_long (input, &sample_section_size, 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;
if (! gimp_data_input_stream_read_short (input, &abr_brush_hdr.type, error))
return NULL;
if (! gimp_data_input_stream_read_long (input, &abr_brush_hdr.size, 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 probably 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;
gchar compress;
if (! gimp_data_input_stream_read_long (input, &abr_sampled_brush_hdr.misc, error) ||
! gimp_data_input_stream_read_short (input, &abr_sampled_brush_hdr.spacing, error))
break;
if (abr_hdr->version ==
2)
{
if (! gimp_data_input_stream_read_ucs2_text (input, &sample_name, error))
break;
}
if (! gimp_data_input_stream_read_char (input, &abr_sampled_brush_hdr.antialiasing, error))
break;
for (i =
0; i <
4; i++)
{
if (! gimp_data_input_stream_read_short (input, &abr_sampled_brush_hdr.bounds[i], error))
break;
}
for (i =
0; i <
4; i++)
{
if (! gimp_data_input_stream_read_long (input, &abr_sampled_brush_hdr.bounds_long[i], error))
break;
}
if (! gimp_data_input_stream_read_short (input, &abr_sampled_brush_hdr.depth, 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 > GIMP_BRUSH_MAX_SIZE ||
height <
1 || height > GIMP_BRUSH_MAX_SIZE ||
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;
if (! gimp_data_input_stream_read_char (input, &compress, 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 (! gimp_data_input_stream_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;
if (! gimp_data_input_stream_read_long (input, &brush_size, 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;
}
if (! gimp_data_input_stream_read_long (input, &top, error) ||
! gimp_data_input_stream_read_long (input, &left, error) ||
! gimp_data_input_stream_read_long (input, &bottom, error) ||
! gimp_data_input_stream_read_long (input, &right, error) ||
! gimp_data_input_stream_read_short (input, &depth, error) ||
! gimp_data_input_stream_read_char (input, &compress, 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 > GIMP_BRUSH_MAX_SIZE ||
height <
1 || height > GIMP_BRUSH_MAX_SIZE ||
(compress && depth !=
1) ||
(! compress && (depth <
1 || depth >
2)) ||
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 (depth ==
1)
{
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 (depth ==
2)
{
/* TODO: For now, convert to 8 bit representation */
guchar *mask_f = g_new0 (guchar, width * height * depth);
if (! g_input_stream_read_all (G_INPUT_STREAM (input),
mask_f, size,
&bytes_read, NULL, error) ||
bytes_read != size)
{
g_object_unref (brush);
g_free (mask_f);
return NULL;
}
for (gint i =
0; i < size; i +=
2)
{
guint16 *temp = (guint16 *) &mask_f[i];
gfloat temp_f = (gfloat) GUINT16_FROM_LE (temp[
0]);
temp_f = CLAMP (temp_f / G_MAXUINT16,
0.
0,
1.
0);
mask[i /
2] = (guchar) (temp_f *
255);
}
g_free (mask_f);
}
}
else
{
if (! gimp_data_input_stream_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 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];
gint32 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;
if (! gimp_data_input_stream_read_long (input, §ion_size, error))
return FALSE;
if (! g_seekable_seek (G_SEEKABLE (input), section_size, G_SEEK_CUR,
NULL, error))
return FALSE;
}
return FALSE;
}