Properly abstract brush scaling:

2006-11-09  Michael Natterer  <mitch@gimp.org>

	Properly abstract brush scaling:

	* app/core/gimpbrush.[ch]: added virtual functions
	GimpBrush::scale_mask() and ::scale_pixmap() and default
	implementations which call the functions in base/brush-scale.c.

	* app/core/gimpbrushgenerated.c: factored out brush calculation to
	an internal utility function and call that function from ::dirty()
	and from the new ::scale_mask().

	* app/core/gimpbrushcore.c: use gimp_brush_scale_mask/pixmap()
	instead of using the lowlevel scale functions directly. Fixes the
	uglyness that we were scaling generated brushes instead of simply
	recalculating them in the right size.
This commit is contained in:
Michael Natterer 2006-11-09 14:54:49 +00:00 committed by Michael Natterer
parent 63e257c768
commit 1186dfd256
5 changed files with 236 additions and 108 deletions

View File

@ -1,3 +1,20 @@
2006-11-09 Michael Natterer <mitch@gimp.org>
Properly abstract brush scaling:
* app/core/gimpbrush.[ch]: added virtual functions
GimpBrush::scale_mask() and ::scale_pixmap() and default
implementations which call the functions in base/brush-scale.c.
* app/core/gimpbrushgenerated.c: factored out brush calculation to
an internal utility function and call that function from ::dirty()
and from the new ::scale_mask().
* app/core/gimpbrushcore.c: use gimp_brush_scale_mask/pixmap()
instead of using the lowlevel scale functions directly. Fixes the
uglyness that we were scaling generated brushes instead of simply
recalculating them in the right size.
2006-11-09 Michael Natterer <mitch@gimp.org>
* libgimpmodule/gimpmodule.h: fixed wrong include guard comment.

View File

@ -64,6 +64,10 @@ static GimpBrush * gimp_brush_real_select_brush (GimpBrush *brush,
static gboolean gimp_brush_real_want_null_motion (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords);
static TempBuf * gimp_brush_real_scale_mask (GimpBrush *brush,
gdouble scale);
static TempBuf * gimp_brush_real_scale_pixmap (GimpBrush *brush,
gdouble scale);
G_DEFINE_TYPE (GimpBrush, gimp_brush, GIMP_TYPE_DATA)
@ -103,6 +107,8 @@ gimp_brush_class_init (GimpBrushClass *klass)
klass->select_brush = gimp_brush_real_select_brush;
klass->want_null_motion = gimp_brush_real_want_null_motion;
klass->scale_mask = gimp_brush_real_scale_mask;
klass->scale_pixmap = gimp_brush_real_scale_pixmap;
klass->spacing_changed = NULL;
}
@ -281,6 +287,51 @@ gimp_brush_get_extension (GimpData *data)
return GIMP_BRUSH_FILE_EXTENSION;
}
static GimpBrush *
gimp_brush_real_select_brush (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords)
{
return brush;
}
static gboolean
gimp_brush_real_want_null_motion (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords)
{
return TRUE;
}
static TempBuf *
gimp_brush_real_scale_mask (GimpBrush *brush,
gdouble scale)
{
gint width;
gint height;
width = (gint) (brush->mask->width * scale + 0.5);
height = (gint) (brush->mask->height * scale + 0.5);
return brush_scale_mask (brush->mask, width, height);
}
static TempBuf *
gimp_brush_real_scale_pixmap (GimpBrush *brush,
gdouble scale)
{
gint width;
gint height;
width = (gint) (brush->pixmap->width * scale + 0.5);
height = (gint) (brush->pixmap->height * scale + 0.5);
return brush_scale_mask (brush->pixmap, width, height);
}
/* public functions */
GimpData *
gimp_brush_new (const gchar *name)
{
@ -338,20 +389,25 @@ gimp_brush_want_null_motion (GimpBrush *brush,
cur_coords);
}
static GimpBrush *
gimp_brush_real_select_brush (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords)
TempBuf *
gimp_brush_scale_mask (GimpBrush *brush,
gdouble scale)
{
return brush;
g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
g_return_val_if_fail (scale > 0.0, NULL);
return GIMP_BRUSH_GET_CLASS (brush)->scale_mask (brush, scale);
}
static gboolean
gimp_brush_real_want_null_motion (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords)
TempBuf *
gimp_brush_scale_pixmap (GimpBrush *brush,
gdouble scale)
{
return TRUE;
g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
g_return_val_if_fail (brush->pixmap != NULL, NULL);
g_return_val_if_fail (scale > 0.0, NULL);
return GIMP_BRUSH_GET_CLASS (brush)->scale_pixmap (brush, scale);
}
TempBuf *

View File

@ -56,6 +56,10 @@ struct _GimpBrushClass
gboolean (* want_null_motion) (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords);
TempBuf * (* scale_mask) (GimpBrush *brush,
gdouble scale);
TempBuf * (* scale_pixmap) (GimpBrush *brush,
gdouble scale);
/* signals */
void (* spacing_changed) (GimpBrush *brush);
@ -74,6 +78,11 @@ gboolean gimp_brush_want_null_motion (GimpBrush *brush,
GimpCoords *last_coords,
GimpCoords *cur_coords);
TempBuf * gimp_brush_scale_mask (GimpBrush *brush,
gdouble scale);
TempBuf * gimp_brush_scale_pixmap (GimpBrush *brush,
gdouble scale);
TempBuf * gimp_brush_get_mask (const GimpBrush *brush);
TempBuf * gimp_brush_get_pixmap (const GimpBrush *brush);

View File

@ -62,6 +62,8 @@ static void gimp_brush_generated_get_property (GObject *object,
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);
static TempBuf * gimp_brush_generated_scale_mask (GimpBrush *gbrush,
gdouble scale);
G_DEFINE_TYPE (GimpBrushGenerated, gimp_brush_generated, GIMP_TYPE_BRUSH)
@ -72,8 +74,9 @@ G_DEFINE_TYPE (GimpBrushGenerated, gimp_brush_generated, GIMP_TYPE_BRUSH)
static void
gimp_brush_generated_class_init (GimpBrushGeneratedClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
GimpBrushClass *brush_class = GIMP_BRUSH_CLASS (klass);
object_class->set_property = gimp_brush_generated_set_property;
object_class->get_property = gimp_brush_generated_get_property;
@ -83,6 +86,8 @@ gimp_brush_generated_class_init (GimpBrushGeneratedClass *klass)
data_class->get_extension = gimp_brush_generated_get_extension;
data_class->duplicate = gimp_brush_generated_duplicate;
brush_class->scale_mask = gimp_brush_generated_scale_mask;
g_object_class_install_property (object_class, PROP_SHAPE,
g_param_spec_enum ("shape", NULL, NULL,
GIMP_TYPE_BRUSH_GENERATED_SHAPE,
@ -232,85 +237,89 @@ gauss (gdouble f)
return (2.0 * f*f);
}
static void
gimp_brush_generated_dirty (GimpData *data)
static TempBuf *
gimp_brush_generated_calc (GimpBrushGenerated *brush,
GimpBrushGeneratedShape shape,
gfloat radius,
gint spikes,
gfloat hardness,
gfloat aspect_ratio,
gfloat angle,
GimpVector2 *xaxis,
GimpVector2 *yaxis)
{
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];
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];
GimpVector2 x_axis;
GimpVector2 y_axis;
TempBuf *mask;
if (gbrush->mask)
temp_buf_free (gbrush->mask);
s = sin (gimp_deg_to_rad (angle));
c = cos (gimp_deg_to_rad (angle));
s = sin (gimp_deg_to_rad (brush->angle));
c = cos (gimp_deg_to_rad (brush->angle));
short_radius = radius / aspect_ratio;
short_radius = brush->radius / brush->aspect_ratio;
x_axis.x = c * radius;
x_axis.y = -1.0 * s * radius;
y_axis.x = s * short_radius;
y_axis.y = c * short_radius;
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)
switch (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));
width = ceil (sqrt (x_axis.x * x_axis.x + y_axis.x * y_axis.x));
height = ceil (sqrt (x_axis.y * x_axis.y + y_axis.y * 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));
width = ceil (fabs (x_axis.x) + fabs (y_axis.x));
height = ceil (fabs (x_axis.y) + fabs (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)));
width = ceil (MAX (fabs (x_axis.x), fabs (y_axis.x)));
height = ceil (MAX (fabs (x_axis.y), fabs (y_axis.y)));
break;
default:
g_return_if_reached ();
g_return_val_if_reached (NULL);
}
if (brush->spikes > 2)
if (spikes > 2)
{
/* could be optimized by respecting the angle */
width = height = ceil (sqrt (brush->radius * brush->radius +
width = height = ceil (sqrt (radius * radius +
short_radius * short_radius));
gbrush->y_axis.x = s * brush->radius;
gbrush->y_axis.y = c * brush->radius;
y_axis.x = s * radius;
y_axis.y = c * radius;
}
gbrush->mask = temp_buf_new (width * 2 + 1,
height * 2 + 1,
1, width, height, NULL);
mask = temp_buf_new (width * 2 + 1,
height * 2 + 1,
1, width, height, NULL);
centerp = temp_buf_data (gbrush->mask) + height * gbrush->mask->width + width;
centerp = temp_buf_data (mask) + height * mask->width + width;
/* set up lookup table */
length = OVERSAMPLING * ceil (1 + sqrt (2 *
ceil (brush->radius + 1.0) *
ceil (brush->radius + 1.0)));
ceil (radius + 1.0) *
ceil (radius + 1.0)));
if ((1.0 - brush->hardness) < 0.0000004)
if ((1.0 - hardness) < 0.0000004)
exponent = 1000000.0;
else
exponent = 0.4 / (1.0 - brush->hardness);
exponent = 0.4 / (1.0 - hardness);
lookup = g_malloc (length);
sum = 0.0;
@ -319,22 +328,22 @@ gimp_brush_generated_dirty (GimpData *data)
{
d = fabs ((x + 0.5) / OVERSAMPLING - 0.5);
if (d > brush->radius)
if (d > radius)
buffer[x] = 0.0;
else
buffer[x] = gauss (pow (d / brush->radius, exponent));
buffer[x] = gauss (pow (d / radius, exponent));
sum += buffer[x];
}
for (x = 0; d < brush->radius || sum > 0.00001; d += 1.0 / OVERSAMPLING)
for (x = 0; d < radius || sum > 0.00001; d += 1.0 / OVERSAMPLING)
{
sum -= buffer[x % OVERSAMPLING];
if (d > brush->radius)
if (d > radius)
buffer[x % OVERSAMPLING] = 0.0;
else
buffer[x % OVERSAMPLING] = gauss (pow (d / brush->radius, exponent));
buffer[x % OVERSAMPLING] = gauss (pow (d / radius, exponent));
sum += buffer[x % OVERSAMPLING];
lookup[x++] = RINT (sum * (255.0 / OVERSAMPLING));
@ -345,11 +354,11 @@ gimp_brush_generated_dirty (GimpData *data)
lookup[x++] = 0;
}
cs = cos (- 2 * G_PI / brush->spikes);
ss = sin (- 2 * G_PI / brush->spikes);
cs = cos (- 2 * G_PI / spikes);
ss = sin (- 2 * G_PI / spikes);
/* for an even number of spikes compute one half and mirror it */
for (y = (brush->spikes % 2 ? -height : 0); y <= height; y++)
for (y = (spikes % 2 ? -height : 0); y <= height; y++)
{
for (x = -width; x <= width; x++)
{
@ -358,23 +367,23 @@ gimp_brush_generated_dirty (GimpData *data)
tx = c*x - s*y;
ty = fabs (s*x + c*y);
if (brush->spikes > 2)
if (spikes > 2)
{
angle = atan2 (ty, tx);
while (angle > G_PI / brush->spikes)
while (angle > G_PI / spikes)
{
gdouble sx = tx, sy = ty;
tx = cs * sx - ss * sy;
ty = ss * sx + cs * sy;
angle -= 2 * G_PI / brush->spikes;
angle -= 2 * G_PI / spikes;
}
}
ty *= brush->aspect_ratio;
switch (brush->shape)
ty *= aspect_ratio;
switch (shape)
{
case GIMP_BRUSH_GENERATED_CIRCLE:
d = sqrt (tx*tx + ty*ty);
@ -387,23 +396,64 @@ gimp_brush_generated_dirty (GimpData *data)
break;
}
if (d < brush->radius + 1)
if (d < radius + 1)
a = lookup[(gint) RINT (d * OVERSAMPLING)];
else
a = 0;
centerp[ y * gbrush->mask->width + x] = a;
centerp[ y * mask->width + x] = a;
if (brush->spikes % 2 == 0)
centerp[-1 * y * gbrush->mask->width - x] = a;
if (spikes % 2 == 0)
centerp[-1 * y * mask->width - x] = a;
}
}
g_free (lookup);
if (xaxis) *xaxis = x_axis;
if (yaxis) *yaxis = y_axis;
return mask;
}
static void
gimp_brush_generated_dirty (GimpData *data)
{
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
GimpBrush *gbrush = GIMP_BRUSH (brush);
if (gbrush->mask)
temp_buf_free (gbrush->mask);
gbrush->mask = gimp_brush_generated_calc (brush,
brush->shape,
brush->radius,
brush->spikes,
brush->hardness,
brush->aspect_ratio,
brush->angle,
&gbrush->x_axis,
&gbrush->y_axis);
GIMP_DATA_CLASS (parent_class)->dirty (data);
}
static TempBuf *
gimp_brush_generated_scale_mask (GimpBrush *gbrush,
gdouble scale)
{
GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (gbrush);
return gimp_brush_generated_calc (brush,
brush->shape,
brush->radius * scale,
brush->spikes,
brush->hardness,
brush->aspect_ratio,
brush->angle,
NULL, NULL);
}
GimpData *
gimp_brush_generated_new (const gchar *name,
GimpBrushGeneratedShape shape,

View File

@ -24,7 +24,6 @@
#include "paint-types.h"
#include "base/brush-scale.h"
#include "base/pixel-region.h"
#include "base/temp-buf.h"
@ -81,7 +80,7 @@ static TempBuf *gimp_brush_core_get_paint_area (GimpPaintCore *paint_core,
static void gimp_brush_core_real_set_brush (GimpBrushCore *core,
GimpBrush *brush);
static void gimp_brush_core_calc_brush_size (GimpBrushCore *core,
static gdouble gimp_brush_core_calc_brush_size (GimpBrushCore *core,
MaskBuf *mask,
gdouble scale,
gint *width,
@ -102,10 +101,10 @@ static MaskBuf * gimp_brush_core_solidify_mask (GimpBrushCore *core,
gdouble x,
gdouble y);
static MaskBuf * gimp_brush_core_scale_mask (GimpBrushCore *core,
MaskBuf *brush_mask,
GimpBrush *brush,
gdouble scale);
static MaskBuf * gimp_brush_core_scale_pixmap (GimpBrushCore *core,
MaskBuf *brush_mask,
GimpBrush *brush,
gdouble scale);
static MaskBuf * gimp_brush_core_get_brush_mask (GimpBrushCore *core,
@ -833,13 +832,15 @@ gimp_brush_core_invalidate_cache (GimpBrush *brush,
* LOCAL FUNCTION DEFINITIONS *
************************************************************/
static void
static gdouble
gimp_brush_core_calc_brush_size (GimpBrushCore *core,
MaskBuf *mask,
gdouble scale,
gint *width,
gint *height)
{
gdouble ratio = 1.0;
scale = CLAMP (scale, 0.0, 1.0);
if (! GIMP_PAINT_CORE (core)->use_pressure)
@ -849,8 +850,6 @@ gimp_brush_core_calc_brush_size (GimpBrushCore *core,
}
else
{
gdouble ratio;
if (scale < 1 / 256)
ratio = 1 / 16;
else
@ -859,6 +858,8 @@ gimp_brush_core_calc_brush_size (GimpBrushCore *core,
*width = MAX ((gint) (mask->width * ratio + 0.5), 1);
*height = MAX ((gint) (mask->height * ratio + 0.5), 1);
}
return ratio;
}
static inline void
@ -1192,7 +1193,7 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
static MaskBuf *
gimp_brush_core_scale_mask (GimpBrushCore *core,
MaskBuf *brush_mask,
GimpBrush *brush,
gdouble scale)
{
gint dest_width;
@ -1204,13 +1205,13 @@ gimp_brush_core_scale_mask (GimpBrushCore *core,
return NULL;
if (scale == 1.0)
return brush_mask;
return brush->mask;
gimp_brush_core_calc_brush_size (core, brush_mask, scale,
&dest_width, &dest_height);
scale = gimp_brush_core_calc_brush_size (core, brush->mask, scale,
&dest_width, &dest_height);
if (! core->cache_invalid &&
brush_mask == core->last_scale_brush &&
brush->mask == core->last_scale_brush &&
core->scale_brush &&
dest_width == core->last_scale_width &&
dest_height == core->last_scale_height)
@ -1218,15 +1219,14 @@ gimp_brush_core_scale_mask (GimpBrushCore *core,
return core->scale_brush;
}
core->last_scale_brush = brush_mask;
core->last_scale_brush = brush->mask;
core->last_scale_width = dest_width;
core->last_scale_height = dest_height;
if (core->scale_brush)
mask_buf_free (core->scale_brush);
core->scale_brush = brush_scale_mask (brush_mask,
dest_width, dest_height);
core->scale_brush = gimp_brush_scale_mask (brush, scale);
core->cache_invalid = TRUE;
core->solid_cache_invalid = TRUE;
@ -1236,7 +1236,7 @@ gimp_brush_core_scale_mask (GimpBrushCore *core,
static MaskBuf *
gimp_brush_core_scale_pixmap (GimpBrushCore *core,
MaskBuf *brush_mask,
GimpBrush *brush,
gdouble scale)
{
gint dest_width;
@ -1248,13 +1248,13 @@ gimp_brush_core_scale_pixmap (GimpBrushCore *core,
return NULL;
if (scale == 1.0)
return brush_mask;
return brush->pixmap;
gimp_brush_core_calc_brush_size (core, brush_mask, scale,
&dest_width, &dest_height);
scale = gimp_brush_core_calc_brush_size (core, brush->pixmap, scale,
&dest_width, &dest_height);
if (! core->cache_invalid &&
brush_mask == core->last_scale_pixmap &&
brush->pixmap == core->last_scale_pixmap &&
core->scale_pixmap &&
dest_width == core->last_scale_pixmap_width &&
dest_height == core->last_scale_pixmap_height)
@ -1262,15 +1262,14 @@ gimp_brush_core_scale_pixmap (GimpBrushCore *core,
return core->scale_pixmap;
}
core->last_scale_pixmap = brush_mask;
core->last_scale_pixmap = brush->pixmap;
core->last_scale_pixmap_width = dest_width;
core->last_scale_pixmap_height = dest_height;
if (core->scale_pixmap)
mask_buf_free (core->scale_pixmap);
core->scale_pixmap = brush_scale_pixmap (brush_mask,
dest_width, dest_height);
core->scale_pixmap = gimp_brush_scale_pixmap (brush, scale);
core->cache_invalid = TRUE;
@ -1286,7 +1285,7 @@ gimp_brush_core_get_brush_mask (GimpBrushCore *core,
MaskBuf *mask;
if (paint_core->use_pressure)
mask = gimp_brush_core_scale_mask (core, core->brush->mask, scale);
mask = gimp_brush_core_scale_mask (core, core->brush, scale);
else
mask = core->brush->mask;
@ -1357,16 +1356,13 @@ gimp_brush_core_color_area_with_pixmap (GimpBrushCore *core,
g_return_if_fail (core->brush->pixmap != NULL);
/* scale the brushes */
pixmap_mask = gimp_brush_core_scale_pixmap (core,
core->brush->pixmap,
scale);
pixmap_mask = gimp_brush_core_scale_pixmap (core, core->brush, scale);
if (! pixmap_mask)
return;
if (mode != GIMP_BRUSH_HARD)
brush_mask = gimp_brush_core_scale_mask (core,
core->brush->mask,
scale);
brush_mask = gimp_brush_core_scale_mask (core, core->brush, scale);
else
brush_mask = NULL;