gimp/app/tools/transform_core.c
Michael Natterer 002aa905db app/Makefile.am app/gimphelp.[ch] new files
1999-09-27  Michael Natterer  <mitch@gimp.org>

	* app/Makefile.am
	* app/gimphelp.[ch]
	* app/gimpui.[ch]: new files

	* app/interface.[ch]
	* app/preferences_dialog.[ch]

	The GIMP Help System part 1: Press "F1" in any dialog to pop up
	the help page for this dialog.

	Moved the widget constructors from preferences_dialog.[ch] and the
	query boxes from interface.[ch] to gimpui.[ch].

	The dialog constructors take a help_func and a help_data
	parameter and install the "F1" accelerator which emits the new
	"help" signal.

	The "help" signal callback calls help_func(help_data) which finally
	has to call gimp_help() which in turn invokes the help browser.

	Still have to find a proper way to (1) prevent "F1" being assigned
	to some menu item and (2) to catch "F1" while browsing the menu
	trees in order to pop up the help for the selected item.

	* app/menus.c: a <Toolbox>/File/Help... menu item.
	* app/commands.[ch]: a command callback for the "Help..." menu item.

	* app/gimprc.[ch]: new boolean gimprc variable "use_help".

	* app/info_dialog.[ch]: pass a help function and data to the info
	dialog constructor.

	* app/tools.[ch]: store the tools help page names in the tool info
	structure. Export a special tools_help_func() which shows the help
	page for the active tool.

	* app/[all files calling a dialog constructor]: pass the dialog's
	help page to the constructor.

	Most dialogs are now created by gimp_dialog_new() which also sets
	up the action_area and the WM delete event callback, so I removed
	the resp. code from these files.

	Fixed some minor bugs and did some other stuff but didn't change
	any logic except dialog creation.

	* plug-ins/helpbrowser/helpbrowser.c: don't try to call a running
	help browser and don't install any menu path (all done in
	app/gimphelp.[ch] now).
1999-09-27 17:58:10 +00:00

1629 lines
46 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 "appenv.h"
#include "actionarea.h"
#include "cursorutil.h"
#include "drawable.h"
#include "errors.h"
#include "floating_sel.h"
#include "general.h"
#include "gdisplay.h"
#include "gimage_mask.h"
#include "gimprc.h"
#include "info_dialog.h"
#include "interface.h"
#include "palette.h"
#include "transform_core.h"
#include "transform_tool.h"
#include "temp_buf.h"
#include "tools.h"
#include "undo.h"
#include "layer_pvt.h"
#include "drawable_pvt.h"
#include "tile_manager_pvt.h"
#include "tile.h" /* ick. */
#include "libgimp/gimpintl.h"
#include "libgimp/gimpmath.h"
#define BILINEAR(jk,j1k,jk1,j1k1,dx,dy) \
((1-dy) * (jk + dx * (j1k - jk)) + \
dy * (jk1 + dx * (j1k1 - jk1)))
/* variables */
static TranInfo old_trans_info;
InfoDialog * transform_info = NULL;
static gboolean transform_info_inited = FALSE;
/* forward function declarations */
static int transform_core_bounds (Tool *, void *);
static void * transform_core_recalc (Tool *, void *);
static void transform_core_doit (Tool *, gpointer);
static double cubic (double, int, int, int, int);
static void transform_core_setup_grid (Tool *);
static void transform_core_grid_recalc (TransformCore *);
/* Hmmm... Should be in a headerfile but which? */
void paths_draw_current(GDisplay *,DrawCore *,GimpMatrix);
#define BILINEAR(jk,j1k,jk1,j1k1,dx,dy) \
((1-dy) * (jk + dx * (j1k - jk)) + \
dy * (jk1 + dx * (j1k1 - jk1)))
/* access interleaved pixels */
#define CUBIC_ROW(dx, row, step) \
cubic(dx, (row)[0], (row)[step], (row)[step+step], (row)[step+step+step])
#define CUBIC_SCALED_ROW(dx, row, step, i) \
cubic(dx, (row)[0] * (row)[i], \
(row)[step] * (row)[step + i], \
(row)[step+step]* (row)[step+step + i], \
(row)[step+step+step] * (row)[step+step+step + i])
#define REF_TILE(i,x,y) \
tile[i] = tile_manager_get_tile (float_tiles, x, y, TRUE, FALSE); \
src[i] = tile_data_pointer (tile[i], (x) % TILE_WIDTH, (y) % TILE_HEIGHT);
/* This should be migrated to pixel_region or similar... */
/* PixelSurround describes a (read-only)
* region around a pixel in a tile manager
*/
typedef struct _PixelSurround {
Tile* tile;
TileManager* mgr;
unsigned char* buff;
int buff_size;
int bpp;
int w;
int h;
unsigned char bg[MAX_CHANNELS];
int row_stride;
} PixelSurround;
static void
pixel_surround_init (PixelSurround *ps,
TileManager *t,
int w,
int h,
guchar bg[MAX_CHANNELS])
{
int i;
for (i = 0; i < MAX_CHANNELS; ++i) {
ps->bg[i] = bg[i];
}
ps->tile = 0;
ps->mgr = t;
ps->bpp = tile_manager_level_bpp(t);
ps->w = w;
ps->h = h;
/* make sure buffer is big enough */
ps->buff_size = w * h * ps->bpp;
ps->buff = g_malloc(ps->buff_size);
ps->row_stride = 0;
}
/* return a pointer to a buffer which contains all the surrounding pixels */
/* strategy: if we are in the middle of a tile, use the tile storage */
/* otherwise just copy into out own malloced buffer and return that */
static guchar *
pixel_surround_lock (PixelSurround *ps,
int x,
int y)
{
int i, j;
unsigned char* k;
unsigned char* ptr;
ps->tile = tile_manager_get_tile(ps->mgr, x, y, TRUE, FALSE);
i = x % TILE_WIDTH;
j = y % TILE_HEIGHT;
/* do we have the whole region? */
if (ps->tile && (i < (tile_ewidth(ps->tile) - ps->w)) &&
(j < (tile_eheight(ps->tile) - ps->h))) {
ps->row_stride = tile_ewidth(ps->tile) * ps->bpp;
/* is this really the correct way? */
return tile_data_pointer(ps->tile, i, j);
}
/* nope, do this the hard way (for now) */
if (ps->tile) {
tile_release(ps->tile, FALSE);
ps->tile = 0;
}
/* copy pixels, one by one */
/* no, this is not the best way, but it's much better than before */
ptr = ps->buff;
for (j = y; j < y+ps->h; ++j) {
for (i = x; i < x+ps->w; ++i) {
Tile* tile = tile_manager_get_tile (ps->mgr, i, j, TRUE, FALSE);
if (tile) {
unsigned char* buff = tile_data_pointer (tile, i % TILE_WIDTH, j % TILE_HEIGHT);
for (k = buff; k < buff+ps->bpp; ++k, ++ptr) {
*ptr = *k;
}
tile_release(tile, FALSE);
} else {
for (k = ps->bg; k < ps->bg+ps->bpp; ++k, ++ptr) {
*ptr = *k;
}
}
}
}
ps->row_stride = ps->w * ps->bpp;
return ps->buff;
}
static int
pixel_surround_rowstride (PixelSurround *ps)
{
return ps->row_stride;
}
static void
pixel_surround_release (PixelSurround *ps)
{
/* always get new tile (for now), so release the old one */
if (ps->tile)
{
tile_release(ps->tile, FALSE);
ps->tile = 0;
}
}
static void
pixel_surround_clear (PixelSurround *ps)
{
if (ps->buff)
{
g_free (ps->buff);
ps->buff = 0;
ps->buff_size = 0;
}
}
static void
transform_ok_callback (GtkWidget *w,
gpointer client_data)
{
Tool *tool;
tool = (Tool *) client_data;
transform_core_doit (tool, tool->gdisp_ptr);
}
static void
transform_reset_callback (GtkWidget *w,
gpointer client_data)
{
Tool *tool;
TransformCore *transform_core;
int i;
tool = (Tool *) client_data;
transform_core = (TransformCore *) tool->private;
/* stop the current tool drawing process */
draw_core_pause (transform_core->core, tool);
/* Restore the previous transformation info */
for (i = 0; i < TRAN_INFO_SIZE; i++)
transform_core->trans_info [i] = old_trans_info [i];
/* recalculate the tool's transformation matrix */
transform_core_recalc (tool, tool->gdisp_ptr);
/* resume drawing the current tool */
draw_core_resume (transform_core->core, tool);
}
static ActionAreaItem action_items[] =
{
{ NULL, transform_ok_callback, NULL, NULL },
{ N_("Reset"), transform_reset_callback, NULL, NULL },
};
static gint n_action_items = sizeof (action_items) / sizeof (action_items[0]);
static const char *action_labels[] =
{
N_("Rotate"),
N_("Scale"),
N_("Shear"),
N_("Transform")
};
void
transform_core_button_press (Tool *tool,
GdkEventButton *bevent,
gpointer gdisp_ptr)
{
TransformCore * transform_core;
GDisplay * gdisp;
Layer * layer;
GimpDrawable * drawable;
int dist;
int closest_dist;
int x, y;
int i;
int off_x, off_y;
gdisp = (GDisplay *) gdisp_ptr;
transform_core = (TransformCore *) tool->private;
transform_core->bpressed = TRUE; /* ALT */
drawable = gimage_active_drawable (gdisp->gimage);
if (transform_core->function == CREATING && tool->state == ACTIVE)
{
/* Save the current transformation info */
for (i = 0; i < TRAN_INFO_SIZE; i++)
old_trans_info [i] = transform_core->trans_info [i];
}
/* if we have already displayed the bounding box and handles,
* check to make sure that the display which currently owns the
* tool is the one which just received the button pressed event
*/
if ((gdisp == tool->gdisp_ptr) && transform_core->interactive)
{
/* start drawing the bounding box and handles... */
draw_core_start (transform_core->core, gdisp->canvas->window, tool);
x = bevent->x;
y = bevent->y;
closest_dist = SQR (x - transform_core->sx1) + SQR (y - transform_core->sy1);
transform_core->function = HANDLE_1;
dist = SQR (x - transform_core->sx2) + SQR (y - transform_core->sy2);
if (dist < closest_dist)
{
closest_dist = dist;
transform_core->function = HANDLE_2;
}
dist = SQR (x - transform_core->sx3) + SQR (y - transform_core->sy3);
if (dist < closest_dist)
{
closest_dist = dist;
transform_core->function = HANDLE_3;
}
dist = SQR (x - transform_core->sx4) + SQR (y - transform_core->sy4);
if (dist < closest_dist)
{
closest_dist = dist;
transform_core->function = HANDLE_4;
}
if (tool->type == ROTATE
&& (SQR (x - transform_core->scx) +
SQR (y - transform_core->scy)) <= 100)
{
transform_core->function = HANDLE_CENTER;
}
/* Save the current pointer position */
gdisplay_untransform_coords (gdisp, bevent->x, bevent->y,
&transform_core->startx,
&transform_core->starty, TRUE, 0);
transform_core->lastx = transform_core->startx;
transform_core->lasty = transform_core->starty;
gdk_pointer_grab (gdisp->canvas->window, FALSE,
GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
NULL, NULL, bevent->time);
tool->state = ACTIVE;
return;
}
/* Initialisation stuff: if the cursor is clicked inside the current
* selection, show the bounding box and handles... */
gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &x, &y, FALSE, FALSE);
if ((layer = gimage_get_active_layer (gdisp->gimage)))
{
drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y);
if (x >= off_x && y >= off_y &&
x < (off_x + drawable_width (GIMP_DRAWABLE(layer))) &&
y < (off_y + drawable_height (GIMP_DRAWABLE(layer))))
if (gimage_mask_is_empty (gdisp->gimage) ||
gimage_mask_value (gdisp->gimage, x, y))
{
if (layer->mask != NULL && GIMP_DRAWABLE (layer->mask))
{
g_message (_("Transformations do not work on\nlayers that contain layer masks."));
tool->state = INACTIVE;
return;
}
/* If the tool is already active, clear the current state
* and reset */
if (tool->state == ACTIVE)
transform_core_reset (tool, gdisp_ptr);
/* Set the pointer to the active display */
tool->gdisp_ptr = gdisp;
tool->drawable = drawable;
tool->state = ACTIVE;
/* Grab the pointer if we're in non-interactive mode */
if (!transform_core->interactive)
gdk_pointer_grab (gdisp->canvas->window, FALSE,
(GDK_POINTER_MOTION_HINT_MASK |
GDK_BUTTON1_MOTION_MASK |
GDK_BUTTON_RELEASE_MASK),
NULL, NULL, bevent->time);
/* Find the transform bounds for some tools (like scale,
* perspective) that actually need the bounds for
* initializing */
transform_core_bounds (tool, gdisp_ptr);
/* Calculate the grid line endpoints */
if (transform_tool_show_grid ())
transform_core_setup_grid (tool);
/* Initialize the transform tool */
(* transform_core->trans_func) (tool, gdisp_ptr, INIT);
if (transform_info != NULL && !transform_info_inited)
{
action_items[0].label = action_labels[tool->type - ROTATE];
action_items[0].user_data = tool;
action_items[1].user_data = tool;
build_action_area (GTK_DIALOG (transform_info->shell),
action_items, n_action_items, 0);
transform_info_inited = TRUE;
}
/* Recalculate the transform tool */
transform_core_recalc (tool, gdisp_ptr);
/* recall this function to find which handle we're dragging */
if (transform_core->interactive)
transform_core_button_press (tool, bevent, gdisp_ptr);
}
}
}
void
transform_core_button_release (Tool *tool,
GdkEventButton *bevent,
gpointer gdisp_ptr)
{
GDisplay *gdisp;
TransformCore *transform_core;
int i;
gdisp = (GDisplay *) gdisp_ptr;
transform_core = (TransformCore *) tool->private;
transform_core->bpressed = FALSE; /* ALT */
/* if we are creating, there is nothing to be done...exit */
if (transform_core->function == CREATING && transform_core->interactive)
return;
/* release of the pointer grab */
gdk_pointer_ungrab (bevent->time);
gdk_flush ();
/* if the 3rd button isn't pressed, transform the selected mask */
if (! (bevent->state & GDK_BUTTON3_MASK))
{
/* Shift-clicking is another way to approve the transform */
if ((bevent->state & GDK_SHIFT_MASK) || (tool->type == FLIP))
{
transform_core_doit (tool, gdisp_ptr);
}
else
{
/* Only update the paths preview */
paths_transform_current_path(gdisp->gimage,transform_core->transform,TRUE);
}
}
else
{
/* stop the current tool drawing process */
draw_core_pause (transform_core->core, tool);
/* Restore the previous transformation info */
for (i = 0; i < TRAN_INFO_SIZE; i++)
transform_core->trans_info [i] = old_trans_info [i];
/* recalculate the tool's transformation matrix */
transform_core_recalc (tool, gdisp_ptr);
/* resume drawing the current tool */
draw_core_resume (transform_core->core, tool);
/* Update the paths preview */
paths_transform_current_path(gdisp->gimage,transform_core->transform,TRUE);
}
/* if this tool is non-interactive, make it inactive after use */
if (!transform_core->interactive)
tool->state = INACTIVE;
}
void
transform_core_doit (Tool *tool,
gpointer gdisp_ptr)
{
GDisplay *gdisp;
void *pundo;
TransformCore *transform_core;
TileManager *new_tiles;
TransformUndo *tu;
int new_layer;
int i, x, y;
gimp_add_busy_cursors();
gdisp = (GDisplay *) gdisp_ptr;
transform_core = (TransformCore *) tool->private;
/* undraw the tool before we muck around with the transform matrix */
draw_core_pause (transform_core->core, tool);
/* We're going to dirty this image, but we want to keep the tool
* around
*/
tool->preserve = TRUE;
/* Start a transform undo group */
undo_push_group_start (gdisp->gimage, TRANSFORM_CORE_UNDO);
/* With the old UI, if original is NULL, then this is the
first transformation. In the new UI, it is always so, yes? */
g_assert (transform_core->original == NULL);
/* If we're in interactive mode, we need to copy the current
* selection to the transform tool's private selection pointer, so
* that the original source can be repeatedly modified.
*/
transform_core->original = transform_core_cut (gdisp->gimage,
gimage_active_drawable (gdisp->gimage),
&new_layer);
pundo = paths_transform_start_undo(gdisp->gimage);
/* Send the request for the transformation to the tool...
*/
new_tiles = (* transform_core->trans_func) (tool, gdisp_ptr, FINISH);
(* transform_core->trans_func) (tool, gdisp_ptr, INIT);
transform_core_recalc (tool, gdisp_ptr);
if (new_tiles)
{
/* paste the new transformed image to the gimage...also implement
* undo...
*/
transform_core_paste (gdisp->gimage, gimage_active_drawable (gdisp->gimage),
new_tiles, new_layer);
/* create and initialize the transform_undo structure */
tu = (TransformUndo *) g_malloc (sizeof (TransformUndo));
tu->tool_ID = tool->ID;
tu->tool_type = tool->type;
for (i = 0; i < TRAN_INFO_SIZE; i++)
tu->trans_info[i] = old_trans_info[i];
tu->original = NULL;
tu->path_undo = pundo;
/* Make a note of the new current drawable (since we may have
* a floating selection, etc now.
*/
tool->drawable = gimage_active_drawable (gdisp->gimage);
undo_push_transform (gdisp->gimage, (void *) tu);
}
/* push the undo group end */
undo_push_group_end (gdisp->gimage);
/* We're done dirtying the image, and would like to be restarted
* if the image gets dirty while the tool exists
*/
tool->preserve = FALSE;
/* Flush the gdisplays */
if (gdisp->disp_xoffset || gdisp->disp_yoffset)
{
gdk_window_get_size (gdisp->canvas->window, &x, &y);
if (gdisp->disp_yoffset)
{
gdisplay_expose_area (gdisp, 0, 0, gdisp->disp_width,
gdisp->disp_yoffset);
gdisplay_expose_area (gdisp, 0, gdisp->disp_yoffset + y,
gdisp->disp_width, gdisp->disp_height);
}
if (gdisp->disp_xoffset)
{
gdisplay_expose_area (gdisp, 0, 0, gdisp->disp_xoffset,
gdisp->disp_height);
gdisplay_expose_area (gdisp, gdisp->disp_xoffset + x, 0,
gdisp->disp_width, gdisp->disp_height);
}
}
gimp_remove_busy_cursors(NULL);
gdisplays_flush ();
transform_core_reset (tool, gdisp_ptr);
/* if this tool is non-interactive, make it inactive after use */
if (!transform_core->interactive)
tool->state = INACTIVE;
}
void
transform_core_motion (Tool *tool,
GdkEventMotion *mevent,
gpointer gdisp_ptr)
{
GDisplay *gdisp;
TransformCore *transform_core;
gdisp = (GDisplay *) gdisp_ptr;
transform_core = (TransformCore *) tool->private;
if(transform_core->bpressed == FALSE)
{
/* hey we have not got the button press yet
* so go away.
*/
return;
}
/* if we are creating or this tool is non-interactive, there is
* nothing to be done so exit.
*/
if (transform_core->function == CREATING || !transform_core->interactive)
return;
/* stop the current tool drawing process */
draw_core_pause (transform_core->core, tool);
gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &transform_core->curx,
&transform_core->cury, TRUE, 0);
transform_core->state = mevent->state;
/* recalculate the tool's transformation matrix */
(* transform_core->trans_func) (tool, gdisp_ptr, MOTION);
transform_core->lastx = transform_core->curx;
transform_core->lasty = transform_core->cury;
/* resume drawing the current tool */
draw_core_resume (transform_core->core, tool);
}
void
transform_core_cursor_update (Tool *tool,
GdkEventMotion *mevent,
gpointer gdisp_ptr)
{
GDisplay *gdisp;
TransformCore *transform_core;
Layer *layer;
int use_transform_cursor = FALSE;
GdkCursorType ctype = GDK_TOP_LEFT_ARROW;
int x, y;
gdisp = (GDisplay *) gdisp_ptr;
transform_core = (TransformCore *) tool->private;
gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, FALSE, FALSE);
if ((layer = gimage_get_active_layer (gdisp->gimage)))
if (x >= GIMP_DRAWABLE(layer)->offset_x && y >= GIMP_DRAWABLE(layer)->offset_y &&
x < (GIMP_DRAWABLE(layer)->offset_x + GIMP_DRAWABLE(layer)->width) &&
y < (GIMP_DRAWABLE(layer)->offset_y + GIMP_DRAWABLE(layer)->height))
{
if (gimage_mask_is_empty (gdisp->gimage) ||
gimage_mask_value (gdisp->gimage, x, y))
use_transform_cursor = TRUE;
}
if (use_transform_cursor)
/* ctype based on transform tool type */
switch (tool->type)
{
case ROTATE: ctype = GDK_EXCHANGE; break;
case SCALE: ctype = GDK_SIZING; break;
case SHEAR: ctype = GDK_TCROSS; break;
case PERSPECTIVE: ctype = GDK_TCROSS; break;
default: break;
}
gdisplay_install_tool_cursor (gdisp, ctype);
}
void
transform_core_control (Tool *tool,
ToolAction action,
gpointer gdisp_ptr)
{
TransformCore * transform_core;
transform_core = (TransformCore *) tool->private;
switch (action)
{
case PAUSE:
draw_core_pause (transform_core->core, tool);
break;
case RESUME:
if (transform_core_recalc (tool, gdisp_ptr))
draw_core_resume (transform_core->core, tool);
else
{
info_dialog_popdown (transform_info);
tool->state = INACTIVE;
}
break;
case HALT:
transform_core_reset (tool, gdisp_ptr);
break;
default:
break;
}
}
void
transform_core_no_draw (Tool *tool)
{
return;
}
void
transform_core_draw (Tool *tool)
{
int x1, y1, x2, y2, x3, y3, x4, y4;
TransformCore * transform_core;
GDisplay * gdisp;
int srw, srh;
int i, k, gci;
int xa, ya, xb, yb;
gdisp = tool->gdisp_ptr;
transform_core = (TransformCore *) tool->private;
gdisplay_transform_coords (gdisp, transform_core->tx1, transform_core->ty1,
&transform_core->sx1, &transform_core->sy1, 0);
gdisplay_transform_coords (gdisp, transform_core->tx2, transform_core->ty2,
&transform_core->sx2, &transform_core->sy2, 0);
gdisplay_transform_coords (gdisp, transform_core->tx3, transform_core->ty3,
&transform_core->sx3, &transform_core->sy3, 0);
gdisplay_transform_coords (gdisp, transform_core->tx4, transform_core->ty4,
&transform_core->sx4, &transform_core->sy4, 0);
x1 = transform_core->sx1; y1 = transform_core->sy1;
x2 = transform_core->sx2; y2 = transform_core->sy2;
x3 = transform_core->sx3; y3 = transform_core->sy3;
x4 = transform_core->sx4; y4 = transform_core->sy4;
/* find the handles' width and height */
srw = 10;
srh = 10;
/* draw the bounding box */
gdk_draw_line (transform_core->core->win, transform_core->core->gc,
x1, y1, x2, y2);
gdk_draw_line (transform_core->core->win, transform_core->core->gc,
x2, y2, x4, y4);
gdk_draw_line (transform_core->core->win, transform_core->core->gc,
x3, y3, x4, y4);
gdk_draw_line (transform_core->core->win, transform_core->core->gc,
x3, y3, x1, y1);
/* Draw the grid */
if ((transform_core->grid_coords != NULL) &&
(transform_core->tgrid_coords != NULL) &&
((tool->type != PERSPECTIVE) ||
((transform_core->transform[0][0] >=0.0) &&
(transform_core->transform[1][1] >=0.0))))
{
gci = 0;
k = transform_core->ngx + transform_core->ngy;
for (i = 0; i < k; i++)
{
gdisplay_transform_coords (gdisp, transform_core->tgrid_coords[gci],
transform_core->tgrid_coords[gci+1],
&xa, &ya, 0);
gdisplay_transform_coords (gdisp, transform_core->tgrid_coords[gci+2],
transform_core->tgrid_coords[gci+3],
&xb, &yb, 0);
gdk_draw_line (transform_core->core->win, transform_core->core->gc,
xa, ya, xb, yb);
gci += 4;
}
}
/* draw the tool handles */
gdk_draw_rectangle (transform_core->core->win, transform_core->core->gc, 0,
x1 - (srw >> 1), y1 - (srh >> 1), srw, srh);
gdk_draw_rectangle (transform_core->core->win, transform_core->core->gc, 0,
x2 - (srw >> 1), y2 - (srh >> 1), srw, srh);
gdk_draw_rectangle (transform_core->core->win, transform_core->core->gc, 0,
x3 - (srw >> 1), y3 - (srh >> 1), srw, srh);
gdk_draw_rectangle (transform_core->core->win, transform_core->core->gc, 0,
x4 - (srw >> 1), y4 - (srh >> 1), srw, srh);
/* draw the center */
if (tool->type == ROTATE)
{
gdisplay_transform_coords (gdisp, transform_core->tcx, transform_core->tcy,
&transform_core->scx, &transform_core->scy, 0);
gdk_draw_arc (transform_core->core->win, transform_core->core->gc, 1,
transform_core->scx - (srw >> 1),
transform_core->scy - (srh >> 1),
srw, srh, 0, 23040);
}
if(transform_tool_showpath())
{
GimpMatrix tmp_matrix;
if (transform_tool_direction () == TRANSFORM_CORRECTIVE)
{
gimp_matrix_invert (transform_core->transform,tmp_matrix);
}
else
{
gimp_matrix_duplicate(transform_core->transform,tmp_matrix);
}
paths_draw_current(gdisp,transform_core->core,tmp_matrix);
}
}
Tool *
transform_core_new (ToolType type,
int interactive)
{
Tool * tool;
TransformCore * private;
int i;
tool = tools_new_tool (type);
private = g_new (TransformCore, 1);
private->interactive = interactive;
if (interactive)
private->core = draw_core_new (transform_core_draw);
else
private->core = draw_core_new (transform_core_no_draw);
private->function = CREATING;
private->original = NULL;
private->bpressed = FALSE;
for (i = 0; i < TRAN_INFO_SIZE; i++)
private->trans_info[i] = 0;
private->grid_coords = private->tgrid_coords = NULL;
tool->scroll_lock = TRUE; /* Disallow scrolling */
tool->preserve = FALSE; /* Don't preserve on drawable change */
tool->private = (void *) private;
tool->button_press_func = transform_core_button_press;
tool->button_release_func = transform_core_button_release;
tool->motion_func = transform_core_motion;
tool->cursor_update_func = transform_core_cursor_update;
tool->control_func = transform_core_control;
return tool;
}
void
transform_core_free (Tool *tool)
{
TransformCore * transform_core;
transform_core = (TransformCore *) tool->private;
/* Make sure the selection core is not visible */
if (tool->state == ACTIVE)
draw_core_stop (transform_core->core, tool);
/* Free the selection core */
draw_core_free (transform_core->core);
/* Free up the original selection if it exists */
if (transform_core->original)
tile_manager_destroy (transform_core->original);
/* If there is an information dialog, free it up */
if (transform_info)
info_dialog_free (transform_info);
transform_info = NULL;
transform_info_inited = FALSE;
/* Free the grid line endpoint arrays if they exist */
if (transform_core->grid_coords != NULL)
g_free (transform_core->grid_coords);
if (transform_core->tgrid_coords != NULL)
g_free (transform_core->tgrid_coords);
/* Finally, free the transform tool itself */
g_free (transform_core);
}
void
transform_bounding_box (Tool *tool)
{
TransformCore * transform_core;
int i, k;
int gci;
GDisplay * gdisp;
transform_core = (TransformCore *) tool->private;
gimp_matrix_transform_point (transform_core->transform,
transform_core->x1, transform_core->y1,
&transform_core->tx1, &transform_core->ty1);
gimp_matrix_transform_point (transform_core->transform,
transform_core->x2, transform_core->y1,
&transform_core->tx2, &transform_core->ty2);
gimp_matrix_transform_point (transform_core->transform,
transform_core->x1, transform_core->y2,
&transform_core->tx3, &transform_core->ty3);
gimp_matrix_transform_point (transform_core->transform,
transform_core->x2, transform_core->y2,
&transform_core->tx4, &transform_core->ty4);
if (tool->type == ROTATE)
gimp_matrix_transform_point (transform_core->transform,
transform_core->cx, transform_core->cy,
&transform_core->tcx, &transform_core->tcy);
if (transform_core->grid_coords != NULL &&
transform_core->tgrid_coords != NULL)
{
gci = 0;
k = (transform_core->ngx + transform_core->ngy) * 2;
for (i = 0; i < k; i++)
{
gimp_matrix_transform_point (transform_core->transform,
transform_core->grid_coords[gci],
transform_core->grid_coords[gci+1],
&(transform_core->tgrid_coords[gci]),
&(transform_core->tgrid_coords[gci+1]));
gci += 2;
}
}
gdisp = (GDisplay *) tool->gdisp_ptr;
}
void
transform_core_reset (Tool *tool,
void *gdisp_ptr)
{
TransformCore * transform_core;
GDisplay * gdisp;
transform_core = (TransformCore *) tool->private;
gdisp = (GDisplay *) gdisp_ptr;
if (transform_core->original)
tile_manager_destroy (transform_core->original);
transform_core->original = NULL;
/* inactivate the tool */
transform_core->function = CREATING;
draw_core_stop (transform_core->core, tool);
info_dialog_popdown (transform_info);
tool->state = INACTIVE;
tool->gdisp_ptr = NULL;
tool->drawable = NULL;
}
static int
transform_core_bounds (Tool *tool,
void *gdisp_ptr)
{
GDisplay * gdisp;
TransformCore * transform_core;
TileManager * tiles;
GimpDrawable *drawable;
int offset_x, offset_y;
gdisp = (GDisplay *) gdisp_ptr;
transform_core = (TransformCore *) tool->private;
tiles = transform_core->original;
drawable = gimage_active_drawable (gdisp->gimage);
/* find the boundaries */
if (tiles)
{
transform_core->x1 = tiles->x;
transform_core->y1 = tiles->y;
transform_core->x2 = tiles->x + tiles->width;
transform_core->y2 = tiles->y + tiles->height;
}
else
{
drawable_offsets (drawable, &offset_x, &offset_y);
drawable_mask_bounds (drawable,
&transform_core->x1, &transform_core->y1,
&transform_core->x2, &transform_core->y2);
transform_core->x1 += offset_x;
transform_core->y1 += offset_y;
transform_core->x2 += offset_x;
transform_core->y2 += offset_y;
}
transform_core->cx = (transform_core->x1 + transform_core->x2) / 2;
transform_core->cy = (transform_core->y1 + transform_core->y2) / 2;
/* changing the bounds invalidates any grid we may have */
transform_core_grid_recalc (transform_core);
return TRUE;
}
void
transform_core_grid_density_changed ()
{
TransformCore * transform_core;
transform_core = (TransformCore *) active_tool->private;
if (transform_core->function == CREATING)
return;
draw_core_pause (transform_core->core, active_tool);
transform_core_grid_recalc (transform_core);
transform_bounding_box (active_tool);
draw_core_resume (transform_core->core, active_tool);
}
void
transform_core_showpath_changed (gint type)
{
TransformCore * transform_core;
transform_core = (TransformCore *) active_tool->private;
if (transform_core->function == CREATING)
return;
if (type)
draw_core_pause (transform_core->core, active_tool);
else
draw_core_resume (transform_core->core, active_tool);
}
static void
transform_core_grid_recalc (TransformCore *transform_core)
{
if (transform_core->grid_coords != NULL)
{
g_free (transform_core->grid_coords);
transform_core->grid_coords = NULL;
}
if (transform_core->tgrid_coords != NULL)
{
g_free (transform_core->tgrid_coords);
transform_core->tgrid_coords = NULL;
}
if (transform_tool_show_grid())
transform_core_setup_grid (active_tool);
}
static void
transform_core_setup_grid (Tool *tool)
{
TransformCore * transform_core;
int i, gci;
double *coords;
transform_core = (TransformCore *) tool->private;
/* We use the transform_tool_grid_size function only here, even
* if the user changes the grid size in the middle of an
* operation, nothing happens.
*/
transform_core->ngx =
(transform_core->x2 - transform_core->x1) / transform_tool_grid_size ();
if (transform_core->ngx > 0)
transform_core->ngx--;
transform_core->ngy =
(transform_core->y2 - transform_core->y1) / transform_tool_grid_size ();
if (transform_core->ngy > 0)
transform_core->ngy--;
transform_core->grid_coords = coords = (double *)
g_malloc ((transform_core->ngx + transform_core->ngy) * 4
* sizeof(double));
transform_core->tgrid_coords = (double *)
g_malloc ((transform_core->ngx + transform_core->ngy) * 4
* sizeof(double));
gci = 0;
for (i = 1; i <= transform_core->ngx; i++)
{
coords[gci] = transform_core->x1 +
((double) i)/(transform_core->ngx + 1) *
(transform_core->x2 - transform_core->x1);
coords[gci+1] = transform_core->y1;
coords[gci+2] = coords[gci];
coords[gci+3] = transform_core->y2;
gci += 4;
}
for (i = 1; i <= transform_core->ngy; i++)
{
coords[gci] = transform_core->x1;
coords[gci+1] = transform_core->y1 +
((double) i)/(transform_core->ngy + 1) *
(transform_core->y2 - transform_core->y1);
coords[gci+2] = transform_core->x2;
coords[gci+3] = coords[gci+1];
gci += 4;
}
}
static void *
transform_core_recalc (Tool *tool,
void *gdisp_ptr)
{
TransformCore * transform_core;
transform_core = (TransformCore *) tool->private;
transform_core_bounds (tool, gdisp_ptr);
return (* transform_core->trans_func) (tool, gdisp_ptr, RECALC);
}
/* Actually carry out a transformation */
TileManager *
transform_core_do (GImage *gimage,
GimpDrawable *drawable,
TileManager *float_tiles,
int interpolation,
GimpMatrix matrix,
progress_func_t progress_callback,
gpointer progress_data)
{
PixelRegion destPR;
TileManager *tiles;
GimpMatrix m;
GimpMatrix im;
int itx, ity;
int tx1, ty1, tx2, ty2;
int width, height;
int alpha;
int bytes, b;
int x, y;
int sx, sy;
int x1, y1, x2, y2;
double xinc, yinc, winc;
double tx, ty, tw;
double ttx = 0.0, tty = 0.0;
unsigned char * dest, * d;
unsigned char * src[16];
Tile *tile[16];
unsigned char bg_col[MAX_CHANNELS];
int i;
double a_val, a_recip;
int newval;
PixelSurround surround;
alpha = 0;
/* turn interpolation off for simple transformations (e.g. rot90) */
if (gimp_matrix_is_simple (matrix)
|| interpolation_type == NEAREST_NEIGHBOR_INTERPOLATION)
interpolation = FALSE;
/* Get the background color */
gimage_get_background (gimage, drawable, bg_col);
switch (drawable_type (drawable))
{
case RGB_GIMAGE: case RGBA_GIMAGE:
bg_col[ALPHA_PIX] = TRANSPARENT_OPACITY;
alpha = 3;
break;
case GRAY_GIMAGE: case GRAYA_GIMAGE:
bg_col[ALPHA_G_PIX] = TRANSPARENT_OPACITY;
alpha = 1;
break;
case INDEXED_GIMAGE: case INDEXEDA_GIMAGE:
bg_col[ALPHA_I_PIX] = TRANSPARENT_OPACITY;
alpha = 1;
/* If the gimage is indexed color, ignore smoothing value */
interpolation = 0;
break;
}
if (transform_tool_direction () == TRANSFORM_CORRECTIVE)
{
/* keep the original matrix here, so we dont need to recalculate
the inverse later */
gimp_matrix_duplicate (matrix, m);
gimp_matrix_invert (matrix, im);
matrix = im;
}
else
{
/* Find the inverse of the transformation matrix */
gimp_matrix_invert (matrix, m);
}
paths_transform_current_path(gimage,matrix,FALSE);
x1 = float_tiles->x;
y1 = float_tiles->y;
x2 = x1 + float_tiles->width;
y2 = y1 + float_tiles->height;
/* Find the bounding coordinates */
if (active_tool && transform_tool_clip ())
{
tx1 = x1;
ty1 = y1;
tx2 = x2;
ty2 = y2;
}
else
{
double dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4;
gimp_matrix_transform_point (matrix, x1, y1, &dx1, &dy1);
gimp_matrix_transform_point (matrix, x2, y1, &dx2, &dy2);
gimp_matrix_transform_point (matrix, x1, y2, &dx3, &dy3);
gimp_matrix_transform_point (matrix, x2, y2, &dx4, &dy4);
tx1 = MINIMUM (dx1, dx2);
tx1 = MINIMUM (tx1, dx3);
tx1 = MINIMUM (tx1, dx4);
ty1 = MINIMUM (dy1, dy2);
ty1 = MINIMUM (ty1, dy3);
ty1 = MINIMUM (ty1, dy4);
tx2 = MAXIMUM (dx1, dx2);
tx2 = MAXIMUM (tx2, dx3);
tx2 = MAXIMUM (tx2, dx4);
ty2 = MAXIMUM (dy1, dy2);
ty2 = MAXIMUM (ty2, dy3);
ty2 = MAXIMUM (ty2, dy4);
}
/* Get the new temporary buffer for the transformed result */
tiles = tile_manager_new ((tx2 - tx1), (ty2 - ty1), float_tiles->bpp);
pixel_region_init (&destPR, tiles, 0, 0, (tx2 - tx1), (ty2 - ty1), TRUE);
tiles->x = tx1;
tiles->y = ty1;
/* initialise the pixel_surround accessor */
if (interpolation) {
if (interpolation_type == CUBIC_INTERPOLATION) {
pixel_surround_init(&surround, float_tiles, 4, 4, bg_col);
} else {
pixel_surround_init(&surround, float_tiles, 2, 2, bg_col);
}
} else {
/* not actually useful, keeps the code cleaner */
pixel_surround_init(&surround, float_tiles, 1, 1, bg_col);
}
width = tiles->width;
height = tiles->height;
bytes = tiles->bpp;
dest = (unsigned char *) g_malloc (width * bytes);
xinc = m[0][0];
yinc = m[1][0];
winc = m[2][0];
/* these loops could be rearranged, depending on which bit of code
* you'd most like to write more than once.
*/
for (y = ty1; y < ty2; y++)
{
if (progress_callback && !(y & 0xf))
(*progress_callback)(ty1, ty2, y, progress_data);
/* set up inverse transform steps */
tx = xinc * tx1 + m[0][1] * y + m[0][2];
ty = yinc * tx1 + m[1][1] * y + m[1][2];
tw = winc * tx1 + m[2][1] * y + m[2][2];
d = dest;
for (x = tx1; x < tx2; x++)
{
/* normalize homogeneous coords */
if (tw == 0.0)
g_message (_("homogeneous coordinate = 0...\n"));
else if (tw != 1.0)
{
ttx = tx / tw;
tty = ty / tw;
}
else
{
ttx = tx;
tty = ty;
}
/* Set the destination pixels */
if (interpolation)
{
if (interpolation_type == CUBIC_INTERPOLATION)
{
/* ttx & tty are the subpixel coordinates of the point in the original
* selection's floating buffer. We need the four integer pixel coords
* around them: itx to itx + 3, ity to ity + 3
*/
itx = floor(ttx);
ity = floor(tty);
/* check if any part of our region overlaps the buffer */
if ((itx + 2) >= x1 && (itx - 1) < x2 &&
(ity + 2) >= y1 && (ity - 1) < y2 )
{
unsigned char* data;
int row;
double dx, dy;
unsigned char* start;
/* lock the pixel surround */
data = pixel_surround_lock(&surround, itx - 1 - x1, ity - 1 - y1);
row = pixel_surround_rowstride(&surround);
/* the fractional error */
dx = ttx - itx;
dy = tty - ity;
/* calculate alpha of result */
start = &data[alpha];
a_val = cubic (dy,
CUBIC_ROW (dx, start, bytes),
CUBIC_ROW (dx, start + row, bytes),
CUBIC_ROW (dx, start + row + row, bytes),
CUBIC_ROW (dx, start + row + row + row, bytes));
if (a_val <= 0.0)
{
a_recip = 0.0;
d[alpha] = 0;
}
else if (a_val > 255.0)
{
a_recip = 1.0 / a_val;
d[alpha] = 255;
}
else
{
a_recip = 1.0 / a_val;
d[alpha] = RINT(a_val);
}
/* for colour channels c,
* result = bicubic(c * alpha) / bicubic(alpha)
*/
for (i = -alpha; i < 0; ++i) {
start = &data[alpha];
newval = RINT(a_recip * cubic (dy,
CUBIC_SCALED_ROW (dx, start, bytes, i),
CUBIC_SCALED_ROW (dx, start + row, bytes, i),
CUBIC_SCALED_ROW (dx, start + row + row, bytes, i),
CUBIC_SCALED_ROW (dx, start + row + row + row, bytes, i)));
if (newval <= 0) {
*d++ = 0;
} else if (newval > 255) {
*d++ = 255;
} else {
*d++ = newval;
}
}
d++;
pixel_surround_release(&surround);
}
else /* not in source range */
{
/* increment the destination pointers */
for (b = 0; b < bytes; b++)
*d++ = bg_col[b];
}
}
else /* linear */
{
itx = floor(ttx);
ity = floor(tty);
/* expand source area to cover interpolation region */
/* (which runs from itx to itx + 1, same in y) */
if ((itx + 1) >= x1 && itx < x2 &&
(ity + 1) >= y1 && ity < y2 )
{
unsigned char* data;
int row;
double dx, dy;
unsigned char* chan;
/* lock the pixel surround */
data = pixel_surround_lock(&surround, itx - x1, ity - y1);
row = pixel_surround_rowstride(&surround);
/* the fractional error */
dx = ttx - itx;
dy = tty - ity;
/* calculate alpha value of result pixel */
chan = &data[alpha];
a_val = BILINEAR (chan[0], chan[bytes], chan[row], chan[row+bytes], dx, dy);
if (a_val <= 0.0) {
a_recip = 0.0;
d[alpha] = 0.0;
} else if (a_val >= 255.0) {
a_recip = 1.0 / a_val;
d[alpha] = 255;
} else {
a_recip = 1.0 / a_val;
d[alpha] = RINT(a_val);
}
/* for colour channels c,
* result = bilinear(c * alpha) / bilinear(alpha)
*/
for (i = -alpha; i < 0; ++i) {
chan = &data[alpha];
newval = RINT(a_recip *
BILINEAR (chan[0] * chan[i],
chan[bytes] * chan[bytes+i],
chan[row] * chan[row+i],
chan[row+bytes] * chan[row+bytes+i], dx, dy));
if (newval <= 0) {
*d++ = 0;
} else if (newval > 255) {
*d++ = 255;
} else {
*d++ = newval;
}
}
/* already set alpha */
d++;
pixel_surround_release(&surround);
}
else /* not in source range */
{
/* increment the destination pointers */
for (b = 0; b < bytes; b++)
*d++ = bg_col[b];
}
}
}
else /* no interpolation */
{
itx = RINT(ttx);
ity = RINT(tty);
if (itx >= x1 && itx < x2 &&
ity >= y1 && ity < y2 )
{
/* x, y coordinates into source tiles */
sx = itx - x1;
sy = ity - y1;
REF_TILE (0, sx, sy);
for (b = 0; b < bytes; b++)
*d++ = src[0][b];
tile_release (tile[0], FALSE);
}
else /* not in source range */
{
/* increment the destination pointers */
for (b = 0; b < bytes; b++)
*d++ = bg_col[b];
}
}
/* increment the transformed coordinates */
tx += xinc;
ty += yinc;
tw += winc;
}
/* set the pixel region row */
pixel_region_set_row (&destPR, 0, (y - ty1), width, dest);
}
pixel_surround_clear(&surround);
g_free (dest);
return tiles;
}
TileManager *
transform_core_cut (GImage *gimage,
GimpDrawable *drawable,
int *new_layer)
{
TileManager *tiles;
/* extract the selected mask if there is a selection */
if (! gimage_mask_is_empty (gimage))
{
tiles = gimage_mask_extract (gimage, drawable, TRUE, TRUE);
*new_layer = TRUE;
}
/* otherwise, just copy the layer */
else
{
tiles = gimage_mask_extract (gimage, drawable, FALSE, TRUE);
*new_layer = FALSE;
}
return tiles;
}
/* Paste a transform to the gdisplay */
Layer *
transform_core_paste (GImage *gimage,
GimpDrawable *drawable,
TileManager *tiles,
int new_layer)
{
Layer * layer;
Layer * floating_layer;
if (new_layer)
{
layer = layer_from_tiles (gimage, drawable, tiles, _("Transformation"),
OPAQUE_OPACITY, NORMAL_MODE);
GIMP_DRAWABLE(layer)->offset_x = tiles->x;
GIMP_DRAWABLE(layer)->offset_y = tiles->y;
/* Start a group undo */
undo_push_group_start (gimage, EDIT_PASTE_UNDO);
floating_sel_attach (layer, drawable);
/* End the group undo */
undo_push_group_end (gimage);
/* Free the tiles */
tile_manager_destroy (tiles);
return layer;
}
else
{
if (GIMP_IS_LAYER(drawable))
layer=GIMP_LAYER(drawable);
else
return NULL;
layer_add_alpha (layer);
floating_layer = gimage_floating_sel (gimage);
if (floating_layer)
floating_sel_relax (floating_layer, TRUE);
gdisplays_update_area (gimage,
GIMP_DRAWABLE(layer)->offset_x, GIMP_DRAWABLE(layer)->offset_y,
GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
/* Push an undo */
undo_push_layer_mod (gimage, layer);
/* set the current layer's data */
GIMP_DRAWABLE(layer)->tiles = tiles;
/* Fill in the new layer's attributes */
GIMP_DRAWABLE(layer)->width = tiles->width;
GIMP_DRAWABLE(layer)->height = tiles->height;
GIMP_DRAWABLE(layer)->bytes = tiles->bpp;
GIMP_DRAWABLE(layer)->offset_x = tiles->x;
GIMP_DRAWABLE(layer)->offset_y = tiles->y;
if (floating_layer)
floating_sel_rigor (floating_layer, TRUE);
drawable_update (GIMP_DRAWABLE(layer), 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height);
/* if we were operating on the floating selection, then it's boundary
* and previews need invalidating */
if (layer == floating_layer)
floating_sel_invalidate (floating_layer);
return layer;
}
}
/* Note: cubic function no longer clips result */
static double
cubic (double dx,
int jm1,
int j,
int jp1,
int jp2)
{
double result;
#if 0
/* Equivalent to Gimp 1.1.1 and earlier - some ringing */
result = ((( ( - jm1 + j - jp1 + jp2 ) * dx +
( jm1 + jm1 - j - j + jp1 - jp2 ) ) * dx +
( - jm1 + jp1 ) ) * dx + j );
/* Recommended by Mitchell and Netravali - too blurred? */
result = ((( ( - 7 * jm1 + 21 * j - 21 * jp1 + 7 * jp2 ) * dx +
( 15 * jm1 - 36 * j + 27 * jp1 - 6 * jp2 ) ) * dx +
( - 9 * jm1 + 9 * jp1 ) ) * dx + (jm1 + 16 * j + jp1) ) / 18.0;
#else
/* Catmull-Rom - not bad */
result = ((( ( - jm1 + 3 * j - 3 * jp1 + jp2 ) * dx +
( 2 * jm1 - 5 * j + 4 * jp1 - jp2 ) ) * dx +
( - jm1 + jp1 ) ) * dx + (j + j) ) / 2.0;
#endif
return result;
}