1218 lines
36 KiB
C
1218 lines
36 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikabrush-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 "libpikabase/pikabase.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "pikabrush.h"
|
|
#include "pikabrush-header.h"
|
|
#include "pikabrush-load.h"
|
|
#include "pikabrush-private.h"
|
|
#include "pikapattern-header.h"
|
|
#include "pikatempbuf.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
/* stuff from abr2gbr Copyright (C) 2001 Marco Lamberto <lm@sunnyspot.org> */
|
|
/* the above is GPL see http://the.sunnyspot.technology.heckin/ */
|
|
|
|
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 * pika_brush_load_abr_v12 (GDataInputStream *input,
|
|
AbrHeader *abr_hdr,
|
|
GFile *file,
|
|
GError **error);
|
|
static GList * pika_brush_load_abr_v6 (GDataInputStream *input,
|
|
AbrHeader *abr_hdr,
|
|
GFile *file,
|
|
GError **error);
|
|
static PikaBrush * pika_brush_load_abr_brush_v12 (GDataInputStream *input,
|
|
AbrHeader *abr_hdr,
|
|
gint index,
|
|
GFile *file,
|
|
GError **error);
|
|
static PikaBrush * pika_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 *
|
|
pika_brush_load (PikaContext *context,
|
|
GFile *file,
|
|
GInputStream *input,
|
|
GError **error)
|
|
{
|
|
PikaBrush *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 = pika_brush_load_brush (context, file, input, error);
|
|
if (! brush)
|
|
return NULL;
|
|
|
|
return g_list_prepend (NULL, brush);
|
|
}
|
|
|
|
PikaBrush *
|
|
pika_brush_load_brush (PikaContext *context,
|
|
GFile *file,
|
|
GInputStream *input,
|
|
GError **error)
|
|
{
|
|
PikaBrush *brush;
|
|
gsize bn_size;
|
|
PikaBrushHeader 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, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: Width = 0."));
|
|
return NULL;
|
|
}
|
|
|
|
if (header.height == 0)
|
|
{
|
|
g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: Height = 0."));
|
|
return NULL;
|
|
}
|
|
|
|
if (header.bytes == 0)
|
|
{
|
|
g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: Bytes = 0."));
|
|
return NULL;
|
|
}
|
|
|
|
if (header.width > PIKA_BRUSH_MAX_SIZE ||
|
|
header.height > PIKA_BRUSH_MAX_SIZE ||
|
|
G_MAXSIZE / header.width / header.height / MAX (4, header.bytes) < 1)
|
|
{
|
|
g_set_error (error, PIKA_DATA_ERROR, PIKA_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, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: Unknown depth %d."),
|
|
header.bytes);
|
|
return NULL;
|
|
}
|
|
/* fallthrough */
|
|
|
|
case 2:
|
|
if (header.magic_number == PIKA_BRUSH_MAGIC)
|
|
break;
|
|
|
|
default:
|
|
g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: Unknown version %d."),
|
|
header.version);
|
|
return NULL;
|
|
}
|
|
|
|
if (header.header_size < sizeof (PikaBrushHeader))
|
|
{
|
|
g_set_error (error, PIKA_DATA_ERROR, PIKA_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 > PIKA_BRUSH_MAX_NAME)
|
|
{
|
|
g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Invalid header data in '%s': "
|
|
"Brush name is too long: %lu"),
|
|
pika_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 = pika_any_to_utf8 (name, bn_size - 1,
|
|
_("Invalid UTF-8 string in brush file '%s'."),
|
|
pika_file_get_utf8_name (file));
|
|
g_free (name);
|
|
name = utf8;
|
|
}
|
|
|
|
if (! name)
|
|
name = g_strdup (_("Unnamed"));
|
|
|
|
brush = g_object_new (PIKA_TYPE_BRUSH,
|
|
"name", name,
|
|
"mime-type", "image/x-pika-gbr",
|
|
NULL);
|
|
g_free (name);
|
|
|
|
brush->priv->mask = pika_temp_buf_new (header.width, header.height,
|
|
babl_format ("Y u8"));
|
|
|
|
mask = pika_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)
|
|
{
|
|
PikaPatternHeader ph;
|
|
goffset rewind;
|
|
|
|
rewind = g_seekable_tell (G_SEEKABLE (input));
|
|
|
|
if (g_input_stream_read_all (input, &ph, sizeof (PikaPatternHeader),
|
|
&bytes_read, NULL, NULL) &&
|
|
bytes_read == sizeof (PikaPatternHeader))
|
|
{
|
|
/* 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 == PIKA_PATTERN_MAGIC &&
|
|
ph.version == 1 &&
|
|
ph.header_size > sizeof (PikaPatternHeader) &&
|
|
ph.bytes == 3 &&
|
|
ph.width == header.width &&
|
|
ph.height == header.height &&
|
|
g_input_stream_skip (input,
|
|
ph.header_size -
|
|
sizeof (PikaPatternHeader),
|
|
NULL, NULL) ==
|
|
ph.header_size - sizeof (PikaPatternHeader))
|
|
{
|
|
guchar *pixmap;
|
|
gssize pixmap_size;
|
|
|
|
brush->priv->pixmap =
|
|
pika_temp_buf_new (header.width, header.height,
|
|
babl_format ("R'G'B' u8"));
|
|
|
|
pixmap = pika_temp_buf_get_data (brush->priv->pixmap);
|
|
|
|
pixmap_size = pika_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 = pika_temp_buf_new (header.width, header.height,
|
|
babl_format ("R'G'B' u8"));
|
|
pixmap = pika_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, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file:\n"
|
|
"Unsupported brush depth %d\n"
|
|
"PIKA 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 *
|
|
pika_brush_load_abr (PikaContext *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 = pika_brush_load_abr_v12 (data_input, &abr_hdr,
|
|
file, &my_error);
|
|
break;
|
|
|
|
case 10:
|
|
case 6:
|
|
brush_list = pika_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, PIKA_DATA_ERROR, PIKA_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 *
|
|
pika_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++)
|
|
{
|
|
PikaBrush *brush;
|
|
GError *my_error = NULL;
|
|
|
|
brush = pika_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 *
|
|
pika_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)
|
|
{
|
|
PikaBrush *brush;
|
|
GError *my_error = NULL;
|
|
|
|
brush = pika_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 PikaBrush *
|
|
pika_brush_load_abr_brush_v12 (GDataInputStream *input,
|
|
AbrHeader *abr_hdr,
|
|
gint index,
|
|
GFile *file,
|
|
GError **error)
|
|
{
|
|
PikaBrush *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, PIKA_DATA_ERROR, PIKA_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;
|
|
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, PIKA_DATA_ERROR, PIKA_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, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: "
|
|
"Wide brushes are not supported."));
|
|
break;
|
|
}
|
|
|
|
tmp = g_path_get_basename (pika_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 (PIKA_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 = pika_temp_buf_new (width, height,
|
|
babl_format ("Y u8"));
|
|
|
|
mask = pika_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 PikaBrush *
|
|
pika_brush_load_abr_brush_v6 (GDataInputStream *input,
|
|
AbrHeader *abr_hdr,
|
|
gint32 max_offset,
|
|
gint index,
|
|
GFile *file,
|
|
GError **error)
|
|
{
|
|
PikaBrush *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, PIKA_DATA_ERROR, PIKA_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, PIKA_DATA_ERROR, PIKA_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, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: "
|
|
"Unknown compression method."));
|
|
return NULL;
|
|
}
|
|
|
|
tmp = g_path_get_basename (pika_file_get_utf8_name (file));
|
|
name = g_strdup_printf ("%s-%03d", tmp, index);
|
|
g_free (tmp);
|
|
|
|
brush = g_object_new (PIKA_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 = pika_temp_buf_new (width, height,
|
|
babl_format ("Y u8"));
|
|
|
|
mask = pika_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, PIKA_DATA_ERROR, PIKA_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, PIKA_DATA_ERROR, PIKA_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file: "
|
|
"RLE compressed brush data corrupt."));
|
|
}
|
|
return FALSE;
|
|
}
|