gimp/app/core/gimpbrush-load.c

938 lines
25 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef _O_BINARY
#define _O_BINARY 0
#endif
#include <glib-object.h>
#include <glib/gstdio.h>
#include "libgimpbase/gimpbase.h"
#ifdef G_OS_WIN32
#include "libgimpbase/gimpwin32-io.h"
#endif
#include "core-types.h"
#include "base/temp-buf.h"
#include "gimpbrush.h"
#include "gimpbrush-header.h"
#include "gimpbrush-load.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 (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 */
GList *
gimp_brush_load (const gchar *filename,
GError **error)
{
GimpBrush *brush;
gint fd;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (g_path_is_absolute (filename), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
fd = g_open (filename, O_RDONLY | _O_BINARY, 0);
if (fd == -1)
{
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_OPEN,
_("Could not open '%s' for reading: %s"),
gimp_filename_to_utf8 (filename), g_strerror (errno));
return NULL;
}
brush = gimp_brush_load_brush (fd, filename, error);
close (fd);
if (! brush)
return NULL;
return g_list_prepend (NULL, brush);
}
GimpBrush *
gimp_brush_load_brush (gint fd,
const gchar *filename,
GError **error)
{
GimpBrush *brush;
gint bn_size;
BrushHeader header;
gchar *name = NULL;
guchar *pixmap;
guchar *mask;
gssize i, size;
gboolean success = TRUE;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (fd != -1, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* Read in the header size */
if (read (fd, &header, sizeof (header)) != sizeof (header))
{
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
_("Could not read %d bytes from '%s': %s"),
(gint) sizeof (header),
gimp_filename_to_utf8 (filename), g_strerror (errno));
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 '%s': "
"Width = 0."),
gimp_filename_to_utf8 (filename));
return NULL;
}
if (header.height == 0)
{
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
_("Fatal parse error in brush file '%s': "
"Height = 0."),
gimp_filename_to_utf8 (filename));
return NULL;
}
if (header.bytes == 0)
{
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
_("Fatal parse error in brush file '%s': "
"Bytes = 0."),
gimp_filename_to_utf8 (filename));
return NULL;
}
switch (header.version)
{
case 1:
/* If this is a version 1 brush, set the fp back 8 bytes */
lseek (fd, -8, SEEK_CUR);
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 '%s': "
"Unknown depth %d."),
gimp_filename_to_utf8 (filename), header.bytes);
return NULL;
}
/* fallthrough */
case 2:
if (header.magic_number == GBRUSH_MAGIC)
break;
default:
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
_("Fatal parse error in brush file '%s': "
"Unknown version %d."),
gimp_filename_to_utf8 (filename), header.version);
return NULL;
}
/* Read in the brush name */
if ((bn_size = (header.header_size - sizeof (header))))
{
gchar *utf8;
name = g_new (gchar, bn_size);
if ((read (fd, name, bn_size)) < bn_size)
{
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));
g_free (name);
return NULL;
}
utf8 = gimp_any_to_utf8 (name, -1,
_("Invalid UTF-8 string in brush file '%s'."),
gimp_filename_to_utf8 (filename));
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->mask = temp_buf_new (header.width, header.height, 1, 0, 0, NULL);
mask = temp_buf_get_data (brush->mask);
size = header.width * header.height * header.bytes;
switch (header.bytes)
{
case 1:
success = (read (fd, mask, size) == size);
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 = (read (fd, buf, bytes) == 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 buf[8 * 1024];
brush->pixmap = temp_buf_new (header.width, header.height,
3, 0, 0, NULL);
pixmap = temp_buf_get_data (brush->pixmap);
for (i = 0; success && i < size;)
{
gssize bytes = MIN (size - i, sizeof (buf));
success = (read (fd, buf, bytes) == 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 '%s': "
"Unsupported brush depth %d\n"
"GIMP brushes must be GRAY or RGBA."),
gimp_filename_to_utf8 (filename), header.bytes);
return NULL;
}
if (! success)
{
g_object_unref (brush);
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;
}
brush->spacing = header.spacing;
brush->x_axis.x = header.width / 2.0;
brush->x_axis.y = 0.0;
brush->y_axis.x = 0.0;
brush->y_axis.y = header.height / 2.0;
return brush;
}
GList *
gimp_brush_load_abr (const gchar *filename,
GError **error)
{
FILE *file;
AbrHeader abr_hdr;
GList *brush_list = NULL;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (g_path_is_absolute (filename), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
file = g_fopen (filename, "rb");
if (! file)
{
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_OPEN,
_("Could not open '%s' for reading: %s"),
gimp_filename_to_utf8 (filename), g_strerror (errno));
return NULL;
}
abr_hdr.version = abr_read_short (file);
abr_hdr.count = abr_read_short (file); /* sub-version for ABR v6 */
if (abr_supported (&abr_hdr))
{
switch (abr_hdr.version)
{
case 1:
case 2:
brush_list = gimp_brush_load_abr_v12 (file, &abr_hdr,
filename, error);
break;
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_v12 (FILE *file,
AbrHeader *abr_hdr,
gint index,
const gchar *filename,
GError **error)
{
GimpBrush *brush = NULL;
AbrBrushHeader abr_brush_hdr;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
abr_brush_hdr.type = abr_read_short (file);
abr_brush_hdr.size = abr_read_long (file);
/* 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 useable brush back. It seems to support the same
* types -akl
*/
g_printerr ("WARNING: computed brush unsupported, skipping.\n");
fseek (file, abr_brush_hdr.size, SEEK_CUR);
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 (file);
abr_sampled_brush_hdr.spacing = abr_read_short (file);
if (abr_hdr->version == 2)
sample_name = abr_read_ucs2_text (file);
abr_sampled_brush_hdr.antialiasing = abr_read_char (file);
for (i = 0; i < 4; i++)
abr_sampled_brush_hdr.bounds[i] = abr_read_short (file);
for (i = 0; i < 4; i++)
abr_sampled_brush_hdr.bounds_long[i] = abr_read_long (file);
abr_sampled_brush_hdr.depth = abr_read_short (file);
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\n", width, height); */
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 '%s': "
"Wide brushes are not supported."),
gimp_filename_to_utf8 (filename));
return NULL;
}
tmp = g_filename_display_basename (filename);
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->spacing = abr_sampled_brush_hdr.spacing;
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_get_data (brush->mask);
size = width * height * bytes;
compress = abr_read_char (file);
/* g_print(" | << size: %dx%d %d bit (%d bytes) %s\n",
* width, height, abr_sampled_brush_hdr.depth, size,
* comppres ? "compressed" : "raw");
*/
if (! compress)
fread (mask, size, 1, file);
else
abr_rle_decode (file, (gchar *) mask, height);
}
break;
default:
g_printerr ("WARNING: unknown brush type, skipping.\n");
fseek (file, abr_brush_hdr.size, SEEK_CUR);
break;
}
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_get_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)
{
return fgetc (file);
}
static gint16
abr_read_short (FILE *file)
{
gint16 val;
fread (&val, sizeof (val), 1, file);
return GINT16_FROM_BE (val);
}
static gint32
abr_read_long (FILE *file)
{
gint32 val;
fread (&val, sizeof (val), 1, file);
return GINT32_FROM_BE (val);
}
static gchar *
abr_read_ucs2_text (FILE *file)
{
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 (file);
if (len <= 0)
return NULL;
name_ucs2 = g_new (gchar, len);
for (i = 0; i < len; i++)
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);
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;
}