2004-08-04 Simon Budig <simon@gimp.org> * app/core/gimpbrushgenerated.c: Enhanced the range of the hardness parameter to make more soft brushes possible. Please note that this makes existing generated brushes look more soft. But since people apparently rarely use more than one or two generated brushes and these get changed frequently I guess it should be OK.
918 lines
26 KiB
C
918 lines
26 KiB
C
/* The GIMP -- an image manipulation program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* gimp_brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
|
|
*
|
|
* 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 <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "base/temp-buf.h"
|
|
|
|
#include "gimpbrushgenerated.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
#define OVERSAMPLING 5
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_SHAPE,
|
|
PROP_RADIUS,
|
|
PROP_SPIKES,
|
|
PROP_HARDNESS,
|
|
PROP_ASPECT_RATIO,
|
|
PROP_ANGLE
|
|
};
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void gimp_brush_generated_class_init (GimpBrushGeneratedClass *klass);
|
|
static void gimp_brush_generated_init (GimpBrushGenerated *brush);
|
|
|
|
static void gimp_brush_generated_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void gimp_brush_generated_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static gboolean gimp_brush_generated_save (GimpData *data,
|
|
GError **error);
|
|
static void gimp_brush_generated_dirty (GimpData *data);
|
|
static gchar * gimp_brush_generated_get_extension (GimpData *data);
|
|
static GimpData * gimp_brush_generated_duplicate (GimpData *data,
|
|
gboolean stingy_memory_use);
|
|
|
|
|
|
static GimpBrushClass *parent_class = NULL;
|
|
|
|
|
|
GType
|
|
gimp_brush_generated_get_type (void)
|
|
{
|
|
static GType brush_type = 0;
|
|
|
|
if (! brush_type)
|
|
{
|
|
static const GTypeInfo brush_info =
|
|
{
|
|
sizeof (GimpBrushGeneratedClass),
|
|
(GBaseInitFunc) NULL,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) gimp_brush_generated_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GimpBrushGenerated),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) gimp_brush_generated_init,
|
|
};
|
|
|
|
brush_type = g_type_register_static (GIMP_TYPE_BRUSH,
|
|
"GimpBrushGenerated",
|
|
&brush_info, 0);
|
|
}
|
|
|
|
return brush_type;
|
|
}
|
|
|
|
static void
|
|
gimp_brush_generated_class_init (GimpBrushGeneratedClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
object_class->set_property = gimp_brush_generated_set_property;
|
|
object_class->get_property = gimp_brush_generated_get_property;
|
|
|
|
data_class->save = gimp_brush_generated_save;
|
|
data_class->dirty = gimp_brush_generated_dirty;
|
|
data_class->get_extension = gimp_brush_generated_get_extension;
|
|
data_class->duplicate = gimp_brush_generated_duplicate;
|
|
|
|
g_object_class_install_property (object_class, PROP_SHAPE,
|
|
g_param_spec_enum ("shape", NULL, NULL,
|
|
GIMP_TYPE_BRUSH_GENERATED_SHAPE,
|
|
GIMP_BRUSH_GENERATED_CIRCLE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (object_class, PROP_RADIUS,
|
|
g_param_spec_double ("radius", NULL, NULL,
|
|
1.0, 1000.0, 5.0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (object_class, PROP_SPIKES,
|
|
g_param_spec_int ("spikes", NULL, NULL,
|
|
2, 20, 2,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (object_class, PROP_HARDNESS,
|
|
g_param_spec_double ("hardness", NULL, NULL,
|
|
0.0, 1.0, 0.0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (object_class, PROP_ASPECT_RATIO,
|
|
g_param_spec_double ("aspect-ratio",
|
|
NULL, NULL,
|
|
1.0, 20.0, 1.0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
g_object_class_install_property (object_class, PROP_ANGLE,
|
|
g_param_spec_double ("angle", NULL, NULL,
|
|
0.0, 180.0, 0.0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
}
|
|
|
|
static void
|
|
gimp_brush_generated_init (GimpBrushGenerated *brush)
|
|
{
|
|
brush->shape = GIMP_BRUSH_GENERATED_CIRCLE;
|
|
brush->radius = 5.0;
|
|
brush->hardness = 0.0;
|
|
brush->aspect_ratio = 1.0;
|
|
brush->angle = 0.0;
|
|
}
|
|
|
|
static void
|
|
gimp_brush_generated_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_SHAPE:
|
|
gimp_brush_generated_set_shape (brush, g_value_get_enum (value));
|
|
break;
|
|
case PROP_RADIUS:
|
|
gimp_brush_generated_set_radius (brush, g_value_get_double (value));
|
|
break;
|
|
case PROP_SPIKES:
|
|
gimp_brush_generated_set_spikes (brush, g_value_get_int (value));
|
|
break;
|
|
case PROP_HARDNESS:
|
|
gimp_brush_generated_set_hardness (brush, g_value_get_double (value));
|
|
break;
|
|
case PROP_ASPECT_RATIO:
|
|
gimp_brush_generated_set_aspect_ratio (brush, g_value_get_double (value));
|
|
break;
|
|
case PROP_ANGLE:
|
|
gimp_brush_generated_set_angle (brush, g_value_get_double (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_brush_generated_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_SHAPE:
|
|
g_value_set_enum (value, brush->shape);
|
|
break;
|
|
case PROP_RADIUS:
|
|
g_value_set_double (value, brush->radius);
|
|
break;
|
|
case PROP_SPIKES:
|
|
g_value_set_int (value, brush->spikes);
|
|
break;
|
|
case PROP_HARDNESS:
|
|
g_value_set_double (value, brush->hardness);
|
|
break;
|
|
case PROP_ASPECT_RATIO:
|
|
g_value_set_double (value, brush->aspect_ratio);
|
|
break;
|
|
case PROP_ANGLE:
|
|
g_value_set_double (value, brush->angle);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gimp_brush_generated_save (GimpData *data,
|
|
GError **error)
|
|
{
|
|
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
|
|
FILE *file;
|
|
gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
|
|
gboolean have_shape = FALSE;
|
|
|
|
file = fopen (data->filename, "wb");
|
|
|
|
if (! file)
|
|
{
|
|
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_OPEN,
|
|
_("Could not open '%s' for writing: %s"),
|
|
gimp_filename_to_utf8 (data->filename),
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
/* write magic header */
|
|
fprintf (file, "GIMP-VBR\n");
|
|
|
|
/* write version */
|
|
if (brush->shape != GIMP_BRUSH_GENERATED_CIRCLE || brush->spikes > 2)
|
|
{
|
|
fprintf (file, "1.5\n");
|
|
have_shape = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fprintf (file, "1.0\n");
|
|
}
|
|
|
|
/* write name */
|
|
fprintf (file, "%.255s\n", GIMP_OBJECT (brush)->name);
|
|
|
|
if (have_shape)
|
|
{
|
|
GEnumClass *enum_class;
|
|
GEnumValue *shape_val;
|
|
|
|
enum_class = g_type_class_peek (GIMP_TYPE_BRUSH_GENERATED_SHAPE);
|
|
|
|
/* write shape */
|
|
shape_val = g_enum_get_value (enum_class, brush->shape);
|
|
fprintf (file, "%s\n", shape_val->value_nick);
|
|
}
|
|
|
|
/* write brush spacing */
|
|
fprintf (file, "%s\n",
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f",
|
|
GIMP_BRUSH (brush)->spacing));
|
|
|
|
/* write brush radius */
|
|
fprintf (file, "%s\n",
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f",
|
|
brush->radius));
|
|
|
|
if (have_shape)
|
|
{
|
|
/* write brush spikes */
|
|
fprintf (file, "%d\n", brush->spikes);
|
|
}
|
|
|
|
/* write brush hardness */
|
|
fprintf (file, "%s\n",
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f",
|
|
brush->hardness));
|
|
|
|
/* write brush aspect_ratio */
|
|
fprintf (file, "%s\n",
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f",
|
|
brush->aspect_ratio));
|
|
|
|
/* write brush angle */
|
|
fprintf (file, "%s\n",
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f",
|
|
brush->angle));
|
|
|
|
fclose (file);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
gimp_brush_generated_get_extension (GimpData *data)
|
|
{
|
|
return GIMP_BRUSH_GENERATED_FILE_EXTENSION;
|
|
}
|
|
|
|
static GimpData *
|
|
gimp_brush_generated_duplicate (GimpData *data,
|
|
gboolean stingy_memory_use)
|
|
{
|
|
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
|
|
|
|
return gimp_brush_generated_new (GIMP_OBJECT (brush)->name,
|
|
brush->shape,
|
|
brush->radius,
|
|
brush->spikes,
|
|
brush->hardness,
|
|
brush->aspect_ratio,
|
|
brush->angle,
|
|
stingy_memory_use);
|
|
}
|
|
|
|
static gdouble
|
|
gauss (gdouble f)
|
|
{
|
|
/* this aint' a real gauss function */
|
|
if (f < -0.5)
|
|
{
|
|
f = -1.0 - f;
|
|
return (2.0 * f*f);
|
|
}
|
|
|
|
if (f < 0.5)
|
|
return (1.0 - 2.0 * f*f);
|
|
|
|
f = 1.0 - f;
|
|
return (2.0 * f*f);
|
|
}
|
|
|
|
static void
|
|
gimp_brush_generated_dirty (GimpData *data)
|
|
{
|
|
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
|
|
GimpBrush *gbrush = GIMP_BRUSH (brush);
|
|
gint x, y;
|
|
guchar *centerp;
|
|
gdouble d;
|
|
gdouble exponent;
|
|
guchar a;
|
|
gint length;
|
|
gint width = 0;
|
|
gint height = 0;
|
|
guchar *lookup;
|
|
gdouble sum;
|
|
gdouble c, s, cs, ss;
|
|
gdouble short_radius;
|
|
gdouble buffer[OVERSAMPLING];
|
|
|
|
if (gbrush->mask)
|
|
temp_buf_free (gbrush->mask);
|
|
|
|
s = sin (gimp_deg_to_rad (brush->angle));
|
|
c = cos (gimp_deg_to_rad (brush->angle));
|
|
|
|
short_radius = brush->radius / brush->aspect_ratio;
|
|
|
|
gbrush->x_axis.x = c * brush->radius;
|
|
gbrush->x_axis.y = -1.0 * s * brush->radius;
|
|
gbrush->y_axis.x = s * short_radius;
|
|
gbrush->y_axis.y = c * short_radius;
|
|
|
|
switch (brush->shape)
|
|
{
|
|
case GIMP_BRUSH_GENERATED_CIRCLE:
|
|
width = ceil (sqrt (gbrush->x_axis.x * gbrush->x_axis.x +
|
|
gbrush->y_axis.x * gbrush->y_axis.x));
|
|
height = ceil (sqrt (gbrush->x_axis.y * gbrush->x_axis.y +
|
|
gbrush->y_axis.y * gbrush->y_axis.y));
|
|
break;
|
|
|
|
case GIMP_BRUSH_GENERATED_SQUARE:
|
|
width = ceil (fabs (gbrush->x_axis.x) + fabs (gbrush->y_axis.x));
|
|
height = ceil (fabs (gbrush->x_axis.y) + fabs (gbrush->y_axis.y));
|
|
break;
|
|
|
|
case GIMP_BRUSH_GENERATED_DIAMOND:
|
|
width = ceil (MAX (fabs (gbrush->x_axis.x), fabs (gbrush->y_axis.x)));
|
|
height = ceil (MAX (fabs (gbrush->x_axis.y), fabs (gbrush->y_axis.y)));
|
|
break;
|
|
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
if (brush->spikes > 2)
|
|
{
|
|
/* could be optimized by respecting the angle */
|
|
width = height = ceil (sqrt (brush->radius * brush->radius +
|
|
short_radius * short_radius));
|
|
gbrush->y_axis.x = s * brush->radius;
|
|
gbrush->y_axis.y = c * brush->radius;
|
|
}
|
|
|
|
gbrush->mask = temp_buf_new (width * 2 + 1,
|
|
height * 2 + 1,
|
|
1, width, height, 0);
|
|
|
|
centerp = temp_buf_data (gbrush->mask) + height * gbrush->mask->width + width;
|
|
|
|
/* set up lookup table */
|
|
length = OVERSAMPLING * ceil (1 + sqrt (2 *
|
|
ceil (brush->radius + 1.0) *
|
|
ceil (brush->radius + 1.0)));
|
|
|
|
if ((1.0 - brush->hardness) < 0.0000004)
|
|
exponent = 1000000.0;
|
|
else
|
|
exponent = 0.4 / (1.0 - brush->hardness);
|
|
|
|
lookup = g_malloc (length);
|
|
sum = 0.0;
|
|
|
|
for (x = 0; x < OVERSAMPLING; x++)
|
|
{
|
|
d = fabs ((x + 0.5) / OVERSAMPLING - 0.5);
|
|
|
|
if (d > brush->radius)
|
|
buffer[x] = 0.0;
|
|
else
|
|
buffer[x] = gauss (pow (d / brush->radius, exponent));
|
|
|
|
sum += buffer[x];
|
|
}
|
|
|
|
for (x = 0; d < brush->radius || sum > 0.00001; d += 1.0 / OVERSAMPLING)
|
|
{
|
|
sum -= buffer[x % OVERSAMPLING];
|
|
|
|
if (d > brush->radius)
|
|
buffer[x % OVERSAMPLING] = 0.0;
|
|
else
|
|
buffer[x % OVERSAMPLING] = gauss (pow (d / brush->radius, exponent));
|
|
|
|
sum += buffer[x % OVERSAMPLING];
|
|
lookup[x++] = RINT (sum * (255.0 / OVERSAMPLING));
|
|
}
|
|
|
|
while (x < length)
|
|
{
|
|
lookup[x++] = 0;
|
|
}
|
|
|
|
cs = cos (- 2 * G_PI / brush->spikes);
|
|
ss = sin (- 2 * G_PI / brush->spikes);
|
|
|
|
/* for an even number of spikes compute one half and mirror it */
|
|
for (y = (brush->spikes % 2 ? -height : 0); y <= height; y++)
|
|
{
|
|
for (x = -width; x <= width; x++)
|
|
{
|
|
gdouble tx, ty, angle;
|
|
|
|
tx = c*x - s*y;
|
|
ty = fabs (s*x + c*y);
|
|
|
|
if (brush->spikes > 2)
|
|
{
|
|
angle = atan2 (ty, tx);
|
|
|
|
while (angle > G_PI / brush->spikes)
|
|
{
|
|
gdouble sx = tx, sy = ty;
|
|
|
|
tx = cs * sx - ss * sy;
|
|
ty = ss * sx + cs * sy;
|
|
|
|
angle -= 2 * G_PI / brush->spikes;
|
|
}
|
|
}
|
|
|
|
ty *= brush->aspect_ratio;
|
|
switch (brush->shape)
|
|
{
|
|
case GIMP_BRUSH_GENERATED_CIRCLE:
|
|
d = sqrt (tx*tx + ty*ty);
|
|
break;
|
|
case GIMP_BRUSH_GENERATED_SQUARE:
|
|
d = MAX (fabs (tx), fabs (ty));
|
|
break;
|
|
case GIMP_BRUSH_GENERATED_DIAMOND:
|
|
d = fabs (tx) + fabs (ty);
|
|
break;
|
|
}
|
|
|
|
if (d < brush->radius + 1)
|
|
a = lookup[(gint) RINT (d * OVERSAMPLING)];
|
|
else
|
|
a = 0;
|
|
|
|
centerp[ y * gbrush->mask->width + x] = a;
|
|
|
|
if (brush->spikes % 2 == 0)
|
|
centerp[-1 * y * gbrush->mask->width - x] = a;
|
|
}
|
|
}
|
|
|
|
g_free (lookup);
|
|
|
|
if (GIMP_DATA_CLASS (parent_class)->dirty)
|
|
GIMP_DATA_CLASS (parent_class)->dirty (data);
|
|
}
|
|
|
|
GimpData *
|
|
gimp_brush_generated_new (const gchar *name,
|
|
GimpBrushGeneratedShape shape,
|
|
gfloat radius,
|
|
gint spikes,
|
|
gfloat hardness,
|
|
gfloat aspect_ratio,
|
|
gfloat angle,
|
|
gboolean stingy_memory_use)
|
|
{
|
|
GimpBrushGenerated *brush;
|
|
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
|
|
brush = g_object_new (GIMP_TYPE_BRUSH_GENERATED,
|
|
"shape", shape,
|
|
"name", name,
|
|
"radius", radius,
|
|
"spikes", spikes,
|
|
"hardness", hardness,
|
|
"aspect-ratio", aspect_ratio,
|
|
"angle", angle,
|
|
NULL);
|
|
|
|
GIMP_BRUSH (brush)->spacing = 20;
|
|
|
|
/* render brush mask */
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
|
|
if (stingy_memory_use)
|
|
temp_buf_swap (GIMP_BRUSH (brush)->mask);
|
|
|
|
return GIMP_DATA (brush);
|
|
}
|
|
|
|
GList *
|
|
gimp_brush_generated_load (const gchar *filename,
|
|
gboolean stingy_memory_use,
|
|
GError **error)
|
|
{
|
|
GimpBrushGenerated *brush;
|
|
FILE *file;
|
|
gchar string[256];
|
|
gchar *name = NULL;
|
|
GimpBrushGeneratedShape shape = GIMP_BRUSH_GENERATED_CIRCLE;
|
|
gboolean have_shape = FALSE;
|
|
gdouble spacing;
|
|
gdouble radius;
|
|
gint spikes = 2;
|
|
gdouble hardness;
|
|
gdouble aspect_ratio;
|
|
gdouble angle;
|
|
|
|
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 = 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;
|
|
}
|
|
|
|
/* make sure the file we are reading is the right type */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
|
|
if (strncmp (string, "GIMP-VBR", 8) != 0)
|
|
{
|
|
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file '%s': "
|
|
"Not a GIMP brush file."),
|
|
gimp_filename_to_utf8 (filename));
|
|
return NULL;
|
|
}
|
|
|
|
/* make sure we are reading a compatible version */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
|
|
if (strncmp (string, "1.0", 3))
|
|
{
|
|
if (strncmp (string, "1.5", 3))
|
|
{
|
|
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file '%s': "
|
|
"Unknown GIMP brush version."),
|
|
gimp_filename_to_utf8 (filename));
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
have_shape = TRUE;
|
|
}
|
|
}
|
|
|
|
/* read name */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
|
|
g_strstrip (string);
|
|
name = gimp_any_to_utf8 (string, -1,
|
|
_("Invalid UTF-8 string in brush file '%s'."),
|
|
gimp_filename_to_utf8 (filename));
|
|
|
|
if (have_shape)
|
|
{
|
|
GEnumClass *enum_class;
|
|
GEnumValue *shape_val;
|
|
|
|
enum_class = g_type_class_peek (GIMP_TYPE_BRUSH_GENERATED_SHAPE);
|
|
|
|
/* read shape */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
|
|
g_strstrip (string);
|
|
shape_val = g_enum_get_value_by_nick (enum_class, string);
|
|
|
|
if (!shape_val)
|
|
{
|
|
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
|
_("Fatal parse error in brush file '%s': "
|
|
"Unknown GIMP brush shape."),
|
|
gimp_filename_to_utf8 (filename));
|
|
g_free (name);
|
|
return NULL;
|
|
}
|
|
|
|
shape = shape_val->value;
|
|
}
|
|
|
|
/* read brush spacing */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
spacing = g_ascii_strtod (string, NULL);
|
|
|
|
/* read brush radius */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
radius = g_ascii_strtod (string, NULL);
|
|
|
|
if (have_shape)
|
|
{
|
|
/* read brush radius */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
spikes = CLAMP (atoi (string), 2, 20);
|
|
}
|
|
|
|
/* read brush hardness */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
hardness = g_ascii_strtod (string, NULL);
|
|
|
|
/* read brush aspect_ratio */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
aspect_ratio = g_ascii_strtod (string, NULL);
|
|
|
|
/* read brush angle */
|
|
errno = 0;
|
|
if (! fgets (string, sizeof (string), file))
|
|
goto failed;
|
|
angle = g_ascii_strtod (string, NULL);
|
|
|
|
fclose (file);
|
|
|
|
/* create new brush */
|
|
brush = g_object_new (GIMP_TYPE_BRUSH_GENERATED,
|
|
"name", name,
|
|
"shape", shape,
|
|
"radius", radius,
|
|
"spikes", spikes,
|
|
"hardness", hardness,
|
|
"aspect-ratio", aspect_ratio,
|
|
"angle", angle,
|
|
NULL);
|
|
g_free (name);
|
|
|
|
GIMP_BRUSH (brush)->spacing = spacing;
|
|
|
|
/* render brush mask */
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
|
|
if (stingy_memory_use)
|
|
temp_buf_swap (GIMP_BRUSH (brush)->mask);
|
|
|
|
return g_list_prepend (NULL, brush);
|
|
|
|
failed:
|
|
|
|
if (name)
|
|
g_free (name);
|
|
|
|
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
|
_("Error while reading brush file '%s': %s"),
|
|
gimp_filename_to_utf8 (filename),
|
|
errno ? g_strerror (errno) : _("File is truncated"));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GimpBrushGeneratedShape
|
|
gimp_brush_generated_set_shape (GimpBrushGenerated *brush,
|
|
GimpBrushGeneratedShape shape)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush),
|
|
GIMP_BRUSH_GENERATED_CIRCLE);
|
|
|
|
if (brush->shape != shape)
|
|
{
|
|
brush->shape = shape;
|
|
|
|
g_object_notify (G_OBJECT (brush), "shape");
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
}
|
|
|
|
return brush->shape;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_set_radius (GimpBrushGenerated *brush,
|
|
gfloat radius)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
radius = CLAMP (radius, 0.0, 32767.0);
|
|
|
|
if (brush->radius != radius)
|
|
{
|
|
brush->radius = radius;
|
|
|
|
g_object_notify (G_OBJECT (brush), "radius");
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
}
|
|
|
|
return brush->radius;
|
|
}
|
|
|
|
gint
|
|
gimp_brush_generated_set_spikes (GimpBrushGenerated *brush,
|
|
gint spikes)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1);
|
|
|
|
spikes = CLAMP (spikes, 2, 20);
|
|
|
|
if (brush->spikes != spikes)
|
|
{
|
|
brush->spikes = spikes;
|
|
|
|
g_object_notify (G_OBJECT (brush), "spikes");
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
}
|
|
|
|
return brush->spikes;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_set_hardness (GimpBrushGenerated *brush,
|
|
gfloat hardness)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
hardness = CLAMP (hardness, 0.0, 1.0);
|
|
|
|
if (brush->hardness != hardness)
|
|
{
|
|
brush->hardness = hardness;
|
|
|
|
g_object_notify (G_OBJECT (brush), "hardness");
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
}
|
|
|
|
return brush->hardness;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_set_aspect_ratio (GimpBrushGenerated *brush,
|
|
gfloat ratio)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
ratio = CLAMP (ratio, 1.0, 1000.0);
|
|
|
|
if (brush->aspect_ratio != ratio)
|
|
{
|
|
brush->aspect_ratio = ratio;
|
|
|
|
g_object_notify (G_OBJECT (brush), "aspect-ratio");
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
}
|
|
|
|
return brush->aspect_ratio;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_set_angle (GimpBrushGenerated *brush,
|
|
gfloat angle)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
if (angle < 0.0)
|
|
angle = -1.0 * fmod (angle, 180.0);
|
|
else if (angle > 180.0)
|
|
angle = fmod (angle, 180.0);
|
|
|
|
if (brush->angle != angle)
|
|
{
|
|
brush->angle = angle;
|
|
|
|
g_object_notify (G_OBJECT (brush), "angle");
|
|
gimp_data_dirty (GIMP_DATA (brush));
|
|
}
|
|
|
|
return brush->angle;
|
|
}
|
|
|
|
GimpBrushGeneratedShape
|
|
gimp_brush_generated_get_shape (const GimpBrushGenerated *brush)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush),
|
|
GIMP_BRUSH_GENERATED_CIRCLE);
|
|
|
|
return brush->shape;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_get_radius (const GimpBrushGenerated *brush)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
return brush->radius;
|
|
}
|
|
|
|
gint
|
|
gimp_brush_generated_get_spikes (const GimpBrushGenerated *brush)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1);
|
|
|
|
return brush->spikes;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_get_hardness (const GimpBrushGenerated *brush)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
return brush->hardness;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_get_aspect_ratio (const GimpBrushGenerated *brush)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
return brush->aspect_ratio;
|
|
}
|
|
|
|
gfloat
|
|
gimp_brush_generated_get_angle (const GimpBrushGenerated *brush)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
|
|
|
|
return brush->angle;
|
|
}
|