/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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 * (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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifndef _O_BINARY #define _O_BINARY 0 #endif #include #include #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 */ /* 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 GimpBrush * gimp_brush_load_abr_brush (FILE *file, 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); /* public functions */ GList * gimp_brush_load (const gchar *filename, gboolean stingy_memory_use, 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; /* Swap the brush to disk (if we're being stingy with memory) */ if (stingy_memory_use) { temp_buf_swap (brush->mask); if (brush->pixmap) temp_buf_swap (brush->pixmap); } 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, NULL); g_free (name); brush->mask = temp_buf_new (header.width, header.height, 1, 0, 0, NULL); mask = temp_buf_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_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, gboolean stingy_memory_use, 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); /* g_print("version: %d count: %d\n", abr_hdr.version, abr_hdr.count); */ if (abr_hdr.version > 1) { 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++) { GimpBrush *brush; brush = gimp_brush_load_abr_brush (file, 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; } /* Swap the brush to disk (if we're being stingy with memory) */ if (stingy_memory_use) temp_buf_swap (brush->mask); brush_list = g_list_prepend (brush_list, brush); } } fclose (file); return g_list_reverse (brush_list); } /* private functions */ static GimpBrush * gimp_brush_load_abr_brush (FILE *file, 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 *tmp; gshort compress; abr_sampled_brush_hdr.misc = abr_read_long (file); abr_sampled_brush_hdr.spacing = abr_read_short (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_printerr ("WARING: wide brushes not supported\n"); return NULL; } 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, 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_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 { 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); } } break; default: g_printerr ("WARNING: unknown brush type, skipping.\n"); fseek (file, abr_brush_hdr.size, SEEK_CUR); break; } 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); }