2003-06-28 Dave Neary <bolsh@gimp.org>
* plug-ins/common/pat.c: Save patterns with alpha
channels, and remove warning while loading patterns
with an alpha channel.
* app/core/gimppattern.c
* app/core/gimpdrawable-bucket-fill.c
* app/paint/gimpclone.c: Make cloning from a pattern
source, and bucket filling with a pattern, work when
there's an alpha channel present in the pattern.
I'm not particularly happy with this, because the only
way to tell whether there's an alpha channel or not is
by the number of bytes in the TempBuf the clone and
bucketfill routines get passed, which is rather
restrictive. It would be nice if a TempBuf had a
_has_alpha () method.
567 lines
16 KiB
C
567 lines
16 KiB
C
/* 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "paint-types.h"
|
|
|
|
#include "base/pixel-region.h"
|
|
#include "base/temp-buf.h"
|
|
|
|
#include "paint-funcs/paint-funcs.h"
|
|
|
|
#include "core/gimp.h"
|
|
#include "core/gimpbrush.h"
|
|
#include "core/gimpdrawable.h"
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimppattern.h"
|
|
|
|
#include "gimpclone.h"
|
|
#include "gimpcloneoptions.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
static void gimp_clone_class_init (GimpCloneClass *klass);
|
|
static void gimp_clone_init (GimpClone *clone);
|
|
|
|
static void gimp_clone_paint (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintCoreState paint_state);
|
|
static void gimp_clone_motion (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options);
|
|
|
|
static void gimp_clone_line_image (GimpImage *dest,
|
|
GimpImage *src,
|
|
GimpDrawable *d_drawable,
|
|
GimpDrawable *s_drawable,
|
|
guchar *s,
|
|
guchar *d,
|
|
gint has_alpha,
|
|
gint src_bytes,
|
|
gint dest_bytes,
|
|
gint width);
|
|
static void gimp_clone_line_pattern (GimpImage *dest,
|
|
GimpDrawable *drawable,
|
|
GimpPattern *pattern,
|
|
guchar *d,
|
|
gint x,
|
|
gint y,
|
|
gint bytes,
|
|
gint width);
|
|
|
|
static void gimp_clone_set_src_drawable (GimpClone *clone,
|
|
GimpDrawable *drawable);
|
|
|
|
|
|
static GimpPaintCoreClass *parent_class = NULL;
|
|
|
|
|
|
void
|
|
gimp_clone_register (Gimp *gimp,
|
|
GimpPaintRegisterCallback callback)
|
|
{
|
|
(* callback) (gimp,
|
|
GIMP_TYPE_CLONE,
|
|
GIMP_TYPE_CLONE_OPTIONS,
|
|
_("Clone"));
|
|
}
|
|
|
|
GType
|
|
gimp_clone_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (! type)
|
|
{
|
|
static const GTypeInfo info =
|
|
{
|
|
sizeof (GimpCloneClass),
|
|
(GBaseInitFunc) NULL,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) gimp_clone_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GimpClone),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) gimp_clone_init,
|
|
};
|
|
|
|
type = g_type_register_static (GIMP_TYPE_PAINT_CORE,
|
|
"GimpClone",
|
|
&info, 0);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
static void
|
|
gimp_clone_class_init (GimpCloneClass *klass)
|
|
{
|
|
GimpPaintCoreClass *paint_core_class;
|
|
|
|
paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
paint_core_class->paint = gimp_clone_paint;
|
|
}
|
|
|
|
static void
|
|
gimp_clone_init (GimpClone *clone)
|
|
{
|
|
GimpPaintCore *paint_core;
|
|
|
|
paint_core = GIMP_PAINT_CORE (clone);
|
|
|
|
paint_core->flags |= CORE_HANDLES_CHANGING_BRUSH;
|
|
paint_core->flags |= CORE_TRACES_ON_WINDOW;
|
|
|
|
clone->set_source = FALSE;
|
|
|
|
clone->src_drawable = NULL;
|
|
clone->src_x = 0.0;
|
|
clone->src_y = 0.0;
|
|
|
|
clone->offset_x = 0;
|
|
clone->offset_y = 0;
|
|
clone->first_stroke = TRUE;
|
|
|
|
clone->init_callback = NULL;
|
|
clone->finish_callback = NULL;
|
|
clone->pretrace_callback = NULL;
|
|
clone->posttrace_callback = NULL;
|
|
clone->callback_data = NULL;
|
|
}
|
|
|
|
static void
|
|
gimp_clone_paint (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintCoreState paint_state)
|
|
{
|
|
static gint orig_src_x = 0;
|
|
static gint orig_src_y = 0;
|
|
|
|
GimpClone *clone;
|
|
GimpCloneOptions *options;
|
|
GimpContext *context;
|
|
|
|
clone = GIMP_CLONE (paint_core);
|
|
options = GIMP_CLONE_OPTIONS (paint_options);
|
|
context = GIMP_CONTEXT (paint_options);
|
|
|
|
switch (paint_state)
|
|
{
|
|
case PRETRACE_PAINT:
|
|
if (clone->pretrace_callback)
|
|
clone->pretrace_callback (clone, clone->callback_data);
|
|
break;
|
|
|
|
case POSTTRACE_PAINT:
|
|
if (clone->posttrace_callback)
|
|
clone->posttrace_callback (clone, clone->callback_data);
|
|
break;
|
|
|
|
case INIT_PAINT:
|
|
if (clone->set_source)
|
|
{
|
|
gimp_clone_set_src_drawable (clone, drawable);
|
|
|
|
clone->src_x = paint_core->cur_coords.x;
|
|
clone->src_y = paint_core->cur_coords.y;
|
|
|
|
clone->first_stroke = TRUE;
|
|
}
|
|
else if (options->align_mode == GIMP_CLONE_ALIGN_NO)
|
|
{
|
|
orig_src_x = clone->src_x;
|
|
orig_src_y = clone->src_y;
|
|
|
|
clone->first_stroke = TRUE;
|
|
}
|
|
|
|
if (clone->init_callback)
|
|
clone->init_callback (clone, clone->callback_data);
|
|
|
|
if (options->clone_type == GIMP_PATTERN_CLONE)
|
|
if (! gimp_context_get_pattern (context))
|
|
g_message (_("No patterns available for this operation."));
|
|
break;
|
|
|
|
case MOTION_PAINT:
|
|
if (clone->set_source)
|
|
{
|
|
/* If the control key is down, move the src target and return */
|
|
|
|
clone->src_x = paint_core->cur_coords.x;
|
|
clone->src_y = paint_core->cur_coords.y;
|
|
|
|
clone->first_stroke = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* otherwise, update the target */
|
|
|
|
gint dest_x;
|
|
gint dest_y;
|
|
|
|
dest_x = paint_core->cur_coords.x;
|
|
dest_y = paint_core->cur_coords.y;
|
|
|
|
if (options->align_mode == GIMP_CLONE_ALIGN_REGISTERED)
|
|
{
|
|
clone->offset_x = 0;
|
|
clone->offset_y = 0;
|
|
}
|
|
else if (clone->first_stroke)
|
|
{
|
|
clone->offset_x = clone->src_x - dest_x;
|
|
clone->offset_y = clone->src_y - dest_y;
|
|
|
|
clone->first_stroke = FALSE;
|
|
}
|
|
|
|
clone->src_x = dest_x + clone->offset_x;
|
|
clone->src_y = dest_y + clone->offset_y;
|
|
|
|
gimp_clone_motion (paint_core, drawable, paint_options);
|
|
}
|
|
break;
|
|
|
|
case FINISH_PAINT:
|
|
if (clone->finish_callback)
|
|
clone->finish_callback (clone, clone->callback_data);
|
|
|
|
if (options->align_mode == GIMP_CLONE_ALIGN_NO && ! clone->first_stroke)
|
|
{
|
|
clone->src_x = orig_src_x;
|
|
clone->src_y = orig_src_y;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_clone_motion (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options)
|
|
{
|
|
GimpClone *clone;
|
|
GimpCloneOptions *options;
|
|
GimpPressureOptions *pressure_options;
|
|
GimpImage *gimage;
|
|
GimpImage *src_gimage = NULL;
|
|
GimpContext *context;
|
|
guchar *s;
|
|
guchar *d;
|
|
TempBuf *area;
|
|
gpointer pr = NULL;
|
|
gint y;
|
|
gint x1, y1, x2, y2;
|
|
gint has_alpha = -1;
|
|
PixelRegion srcPR, destPR;
|
|
GimpPattern *pattern = NULL;
|
|
gdouble opacity;
|
|
gdouble scale;
|
|
gint offset_x;
|
|
gint offset_y;
|
|
|
|
clone = GIMP_CLONE (paint_core);
|
|
options = GIMP_CLONE_OPTIONS (paint_options);
|
|
context = GIMP_CONTEXT (paint_options);
|
|
|
|
pressure_options = paint_options->pressure_options;
|
|
|
|
/* make local copies because we change them */
|
|
offset_x = clone->offset_x;
|
|
offset_y = clone->offset_y;
|
|
|
|
/* Make sure we still have a source if we are doing image cloning */
|
|
if (options->clone_type == GIMP_IMAGE_CLONE)
|
|
{
|
|
if (! clone->src_drawable)
|
|
return;
|
|
|
|
if (! (src_gimage = gimp_item_get_image (GIMP_ITEM (clone->src_drawable))))
|
|
return;
|
|
|
|
/* Determine whether the source image has an alpha channel */
|
|
has_alpha = gimp_drawable_has_alpha (clone->src_drawable);
|
|
}
|
|
|
|
/* We always need a destination image */
|
|
if (! (gimage = gimp_item_get_image (GIMP_ITEM (drawable))))
|
|
return;
|
|
|
|
if (pressure_options->size)
|
|
scale = paint_core->cur_coords.pressure;
|
|
else
|
|
scale = 1.0;
|
|
|
|
/* Get a region which can be used to paint to */
|
|
if (! (area = gimp_paint_core_get_paint_area (paint_core, drawable, scale)))
|
|
return;
|
|
|
|
switch (options->clone_type)
|
|
{
|
|
case GIMP_IMAGE_CLONE:
|
|
/* Set the paint area to transparent */
|
|
temp_buf_data_clear (area);
|
|
|
|
x1 = CLAMP (area->x + offset_x,
|
|
0, gimp_item_width (GIMP_ITEM (clone->src_drawable)));
|
|
y1 = CLAMP (area->y + offset_y,
|
|
0, gimp_item_height (GIMP_ITEM (clone->src_drawable)));
|
|
x2 = CLAMP (area->x + offset_x + area->width,
|
|
0, gimp_item_width (GIMP_ITEM (clone->src_drawable)));
|
|
y2 = CLAMP (area->y + offset_y + area->height,
|
|
0, gimp_item_height (GIMP_ITEM (clone->src_drawable)));
|
|
|
|
if (!(x2 - x1) || !(y2 - y1))
|
|
return;
|
|
|
|
/* If the source gimage is different from the destination,
|
|
* then we should copy straight from the destination image
|
|
* to the canvas.
|
|
* Otherwise, we need a call to get_orig_image to make sure
|
|
* we get a copy of the unblemished (offset) image
|
|
*/
|
|
if (clone->src_drawable != drawable)
|
|
{
|
|
pixel_region_init (&srcPR, gimp_drawable_data (clone->src_drawable),
|
|
x1, y1, (x2 - x1), (y2 - y1), FALSE);
|
|
}
|
|
else
|
|
{
|
|
TempBuf *orig;
|
|
|
|
/* get the original image */
|
|
orig = gimp_paint_core_get_orig_image (paint_core, clone->src_drawable,
|
|
x1, y1, x2, y2);
|
|
|
|
srcPR.bytes = orig->bytes;
|
|
srcPR.x = 0;
|
|
srcPR.y = 0;
|
|
srcPR.w = x2 - x1;
|
|
srcPR.h = y2 - y1;
|
|
srcPR.rowstride = srcPR.bytes * orig->width;
|
|
srcPR.data = temp_buf_data (orig);
|
|
}
|
|
|
|
offset_x = x1 - (area->x + offset_x);
|
|
offset_y = y1 - (area->y + offset_y);
|
|
|
|
/* configure the destination */
|
|
destPR.bytes = area->bytes;
|
|
destPR.x = 0;
|
|
destPR.y = 0;
|
|
destPR.w = srcPR.w;
|
|
destPR.h = srcPR.h;
|
|
destPR.rowstride = destPR.bytes * area->width;
|
|
destPR.data = (temp_buf_data (area) +
|
|
offset_y * destPR.rowstride +
|
|
offset_x * destPR.bytes);
|
|
|
|
pr = pixel_regions_register (2, &srcPR, &destPR);
|
|
break;
|
|
|
|
case GIMP_PATTERN_CLONE:
|
|
pattern = gimp_context_get_pattern (context);
|
|
|
|
if (!pattern)
|
|
return;
|
|
|
|
/* configure the destination */
|
|
destPR.bytes = area->bytes;
|
|
destPR.x = 0;
|
|
destPR.y = 0;
|
|
destPR.w = area->width;
|
|
destPR.h = area->height;
|
|
destPR.rowstride = destPR.bytes * area->width;
|
|
destPR.data = temp_buf_data (area);
|
|
|
|
pr = pixel_regions_register (1, &destPR);
|
|
break;
|
|
}
|
|
|
|
for (; pr != NULL; pr = pixel_regions_process (pr))
|
|
{
|
|
s = srcPR.data;
|
|
d = destPR.data;
|
|
|
|
for (y = 0; y < destPR.h; y++)
|
|
{
|
|
switch (options->clone_type)
|
|
{
|
|
case GIMP_IMAGE_CLONE:
|
|
gimp_clone_line_image (gimage, src_gimage,
|
|
drawable, clone->src_drawable,
|
|
s, d, has_alpha,
|
|
srcPR.bytes, destPR.bytes, destPR.w);
|
|
s += srcPR.rowstride;
|
|
break;
|
|
|
|
case GIMP_PATTERN_CLONE:
|
|
gimp_clone_line_pattern (gimage, drawable,
|
|
pattern, d,
|
|
area->x + offset_x,
|
|
area->y + y + offset_y,
|
|
destPR.bytes, destPR.w);
|
|
break;
|
|
}
|
|
|
|
d += destPR.rowstride;
|
|
}
|
|
}
|
|
|
|
opacity = gimp_context_get_opacity (context);
|
|
|
|
if (pressure_options->opacity)
|
|
opacity = opacity * 2.0 * paint_core->cur_coords.pressure;
|
|
|
|
/* paste the newly painted canvas to the gimage which is being worked on */
|
|
gimp_paint_core_paste_canvas (paint_core, drawable,
|
|
MIN (opacity, GIMP_OPACITY_OPAQUE),
|
|
gimp_context_get_opacity (context),
|
|
gimp_context_get_paint_mode (context),
|
|
(pressure_options->pressure ?
|
|
GIMP_BRUSH_PRESSURE : GIMP_BRUSH_SOFT),
|
|
scale,
|
|
GIMP_PAINT_CONSTANT);
|
|
}
|
|
|
|
static void
|
|
gimp_clone_line_image (GimpImage *dest,
|
|
GimpImage *src,
|
|
GimpDrawable *d_drawable,
|
|
GimpDrawable *s_drawable,
|
|
guchar *s,
|
|
guchar *d,
|
|
gint has_alpha,
|
|
gint src_bytes,
|
|
gint dest_bytes,
|
|
gint width)
|
|
{
|
|
guchar rgb[3];
|
|
gint src_alpha, dest_alpha;
|
|
|
|
src_alpha = src_bytes - 1;
|
|
dest_alpha = dest_bytes - 1;
|
|
|
|
while (width--)
|
|
{
|
|
gimp_image_get_color (src, gimp_drawable_type (s_drawable), rgb, s);
|
|
gimp_image_transform_color (dest, d_drawable, rgb, d, GIMP_RGB);
|
|
|
|
if (has_alpha)
|
|
d[dest_alpha] = s[src_alpha];
|
|
else
|
|
d[dest_alpha] = OPAQUE_OPACITY;
|
|
|
|
s += src_bytes;
|
|
d += dest_bytes;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_clone_line_pattern (GimpImage *dest,
|
|
GimpDrawable *drawable,
|
|
GimpPattern *pattern,
|
|
guchar *d,
|
|
gint x,
|
|
gint y,
|
|
gint bytes,
|
|
gint width)
|
|
{
|
|
guchar *pat, *p;
|
|
GimpImageBaseType color_type;
|
|
gint alpha;
|
|
gint i;
|
|
|
|
/* Make sure x, y are positive */
|
|
while (x < 0)
|
|
x += pattern->mask->width;
|
|
while (y < 0)
|
|
y += pattern->mask->height;
|
|
|
|
/* Get a pointer to the appropriate scanline of the pattern buffer */
|
|
pat = temp_buf_data (pattern->mask) +
|
|
(y % pattern->mask->height) * pattern->mask->width * pattern->mask->bytes;
|
|
|
|
color_type = (pattern->mask->bytes == 3 ||
|
|
pattern->mask->bytes == 4) ? GIMP_RGB : GIMP_GRAY;
|
|
|
|
alpha = bytes - 1;
|
|
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
p = pat + ((i + x) % pattern->mask->width) * pattern->mask->bytes;
|
|
|
|
gimp_image_transform_color (dest, drawable, p, d, color_type);
|
|
|
|
if (pattern->mask->bytes == 2 || pattern->mask->bytes == 4)
|
|
d[alpha] = p[alpha];
|
|
else
|
|
d[alpha] = OPAQUE_OPACITY;
|
|
|
|
d += bytes;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_clone_src_drawable_disconnect_cb (GimpDrawable *drawable,
|
|
GimpClone *clone)
|
|
{
|
|
if (drawable == clone->src_drawable)
|
|
{
|
|
clone->src_drawable = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_clone_set_src_drawable (GimpClone *clone,
|
|
GimpDrawable *drawable)
|
|
{
|
|
if (clone->src_drawable == drawable)
|
|
return;
|
|
|
|
if (clone->src_drawable)
|
|
g_signal_handlers_disconnect_by_func (clone->src_drawable,
|
|
gimp_clone_src_drawable_disconnect_cb,
|
|
clone);
|
|
|
|
clone->src_drawable = drawable;
|
|
|
|
if (clone->src_drawable)
|
|
{
|
|
g_signal_connect (clone->src_drawable, "disconnect",
|
|
G_CALLBACK (gimp_clone_src_drawable_disconnect_cb),
|
|
clone);
|
|
}
|
|
}
|