gimp/app/transform_core.c
Lauri Alanko faeaa7cc23 Removed most of the image id system. They're still used with pdb.
At quick glance, nothing seems to be broken, but if things weird
	out, blame me.

	Now just the same for layers, channels and displays...
1998-06-29 00:24:44 +00:00

1340 lines
35 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 <stdlib.h>
#include <math.h>
#include "appenv.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 "layers_dialog.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"
#define SQR(x) ((x) * (x))
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif /* M_PI */
/* variables */
static TranInfo old_trans_info;
InfoDialog * transform_info = NULL;
/* forward function declarations */
static int transform_core_bounds (Tool *, void *);
static void * transform_core_recalc (Tool *, void *);
static double cubic (double, int, int, int, int);
#define BILINEAR(jk,j1k,jk1,j1k1,dx,dy) \
((1-dy) * ((1-dx)*jk + dx*j1k) + \
dy * ((1-dx)*jk1 + dx*j1k1))
#define REF_TILE(i,x,y) \
tile[i] = tile_manager_get_tile (float_tiles, x, y, 0); \
tile_ref (tile[i]); \
src[i] = tile[i]->data + tile[i]->bpp * (tile[i]->ewidth * ((y) % TILE_HEIGHT) + ((x) % TILE_WIDTH));
void
transform_core_button_press (tool, bevent, gdisp_ptr)
Tool *tool;
GdkEventButton *bevent;
gpointer gdisp_ptr;
{
TransformCore * transform_core;
GDisplay * gdisp;
Layer * layer;
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 */
tool->drawable = gimage_active_drawable (gdisp->gimage);
/* 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 ((transform_core->function >= CREATING) && (gdisp_ptr == tool->gdisp_ptr) &&
transform_core->interactive)
{
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;
}
/* 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;
}
/* 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 gdisplay that owns this tool */
tool->gdisp_ptr = gdisp_ptr;
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);
/* Initialize the transform tool */
(* transform_core->trans_func) (tool, gdisp_ptr, INIT);
/* Recalculate the transform tool */
transform_core_recalc (tool, gdisp_ptr);
/* start drawing the bounding box and handles... */
draw_core_start (transform_core->core, gdisp->canvas->window, tool);
/* 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, bevent, gdisp_ptr)
Tool *tool;
GdkEventButton *bevent;
gpointer gdisp_ptr;
{
GDisplay *gdisp;
TransformCore *transform_core;
TileManager *new_tiles;
TransformUndo *tu;
int first_transform;
int new_layer;
int i, x, y;
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))
{
/* 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);
/* If original is NULL, then this is the first transformation */
first_transform = (transform_core->original) ? FALSE : TRUE;
/* If we're in interactive mode, and haven't yet done any
* transformations, we need to copy the current selection to
* the transform tool's private selection pointer, so that the
* original source can be repeatedly modified.
*/
if (first_transform)
transform_core->original = transform_core_cut (gdisp->gimage,
gimage_active_drawable (gdisp->gimage),
&new_layer);
else
new_layer = FALSE;
/* Send the request for the transformation to the tool...
*/
new_tiles = (* transform_core->trans_func) (tool, gdisp_ptr, FINISH);
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->first = first_transform;
tu->original = NULL;
/* 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);
}
}
gdisplays_flush ();
}
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);
}
/* if this tool is non-interactive, make it inactive after use */
if (!transform_core->interactive)
tool->state = INACTIVE;
}
void
transform_core_motion (tool, mevent, gdisp_ptr)
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, mevent, gdisp_ptr)
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;
case FLIP_HORZ: ctype = GDK_SB_H_DOUBLE_ARROW; break;
case FLIP_VERT: ctype = GDK_SB_V_DOUBLE_ARROW; break;
default: break;
}
gdisplay_install_tool_cursor (gdisp, ctype);
}
void
transform_core_control (tool, action, gdisp_ptr)
Tool *tool;
int 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;
}
}
void
transform_core_no_draw (tool)
Tool * tool;
{
return;
}
void
transform_core_draw (tool)
Tool * tool;
{
int x1, y1, x2, y2, x3, y3, x4, y4;
TransformCore * transform_core;
GDisplay * gdisp;
int srw, srh;
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 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);
}
Tool *
transform_core_new (type, interactive)
int type;
int interactive;
{
Tool * tool;
TransformCore * private;
int i;
tool = (Tool *) g_malloc (sizeof (Tool));
private = (TransformCore *) g_malloc (sizeof (TransformCore));
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;
tool->type = type;
tool->state = INACTIVE;
tool->scroll_lock = 1; /* Do not allow scrolling */
tool->auto_snap_to = TRUE;
tool->private = (void *) private;
tool->preserve = FALSE; /* Destroy when the image is dirtied. */
tool->button_press_func = transform_core_button_press;
tool->button_release_func = transform_core_button_release;
tool->motion_func = transform_core_motion;
tool->arrow_keys_func = standard_arrow_keys_func;
tool->cursor_update_func = transform_core_cursor_update;
tool->control_func = transform_core_control;
return tool;
}
void
transform_core_free (tool)
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;
/* Finally, free the transform tool itself */
g_free (transform_core);
}
void
transform_bounding_box (tool)
Tool * tool;
{
TransformCore * transform_core;
transform_core = (TransformCore *) tool->private;
transform_point (transform_core->transform,
transform_core->x1, transform_core->y1,
&transform_core->tx1, &transform_core->ty1);
transform_point (transform_core->transform,
transform_core->x2, transform_core->y1,
&transform_core->tx2, &transform_core->ty2);
transform_point (transform_core->transform,
transform_core->x1, transform_core->y2,
&transform_core->tx3, &transform_core->ty3);
transform_point (transform_core->transform,
transform_core->x2, transform_core->y2,
&transform_core->tx4, &transform_core->ty4);
}
void
transform_point (m, x, y, nx, ny)
Matrix m;
double x, y;
double *nx, *ny;
{
double xx, yy, ww;
xx = m[0][0] * x + m[0][1] * y + m[0][2];
yy = m[1][0] * x + m[1][1] * y + m[1][2];
ww = m[2][0] * x + m[2][1] * y + m[2][2];
if (!ww)
ww = 1.0;
*nx = xx / ww;
*ny = yy / ww;
}
void
mult_matrix (m1, m2)
Matrix m1, m2;
{
Matrix result;
int i, j, k;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
{
result [i][j] = 0.0;
for (k = 0; k < 3; k++)
result [i][j] += m1 [i][k] * m2[k][j];
}
/* copy the result into matrix 2 */
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
m2 [i][j] = result [i][j];
}
void
identity_matrix (m)
Matrix m;
{
int i, j;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
m[i][j] = (i == j) ? 1 : 0;
}
void
translate_matrix (m, x, y)
Matrix m;
double x, y;
{
Matrix trans;
identity_matrix (trans);
trans[0][2] = x;
trans[1][2] = y;
mult_matrix (trans, m);
}
void
scale_matrix (m, x, y)
Matrix m;
double x, y;
{
Matrix scale;
identity_matrix (scale);
scale[0][0] = x;
scale[1][1] = y;
mult_matrix (scale, m);
}
void
rotate_matrix (m, theta)
Matrix m;
double theta;
{
Matrix rotate;
double cos_theta, sin_theta;
cos_theta = cos (theta);
sin_theta = sin (theta);
identity_matrix (rotate);
rotate[0][0] = cos_theta;
rotate[0][1] = -sin_theta;
rotate[1][0] = sin_theta;
rotate[1][1] = cos_theta;
mult_matrix (rotate, m);
}
void
xshear_matrix (m, shear)
Matrix m;
double shear;
{
Matrix shear_m;
identity_matrix (shear_m);
shear_m[0][1] = shear;
mult_matrix (shear_m, m);
}
void
yshear_matrix (m, shear)
Matrix m;
double shear;
{
Matrix shear_m;
identity_matrix (shear_m);
shear_m[1][0] = shear;
mult_matrix (shear_m, m);
}
/* find the determinate for a 3x3 matrix */
static double
determinate (Matrix m)
{
int i;
double det = 0;
for (i = 0; i < 3; i ++)
{
det += m[0][i] * m[1][(i+1)%3] * m[2][(i+2)%3];
det -= m[2][i] * m[1][(i+1)%3] * m[0][(i+2)%3];
}
return det;
}
/* find the cofactor matrix of a matrix */
static void
cofactor (Matrix m, Matrix m_cof)
{
int i, j;
int x1, y1;
int x2, y2;
x1 = y1 = x2 = y2 = 0;
for (i = 0; i < 3; i++)
{
switch (i)
{
case 0 : y1 = 1; y2 = 2; break;
case 1 : y1 = 0; y2 = 2; break;
case 2 : y1 = 0; y2 = 1; break;
}
for (j = 0; j < 3; j++)
{
switch (j)
{
case 0 : x1 = 1; x2 = 2; break;
case 1 : x1 = 0; x2 = 2; break;
case 2 : x1 = 0; x2 = 1; break;
}
m_cof[i][j] = (m[x1][y1] * m[x2][y2] - m[x1][y2] * m[x2][y1]) *
(((i+j) % 2) ? -1 : 1);
}
}
}
/* find the inverse of a 3x3 matrix */
static void
invert (Matrix m, Matrix m_inv)
{
double det = determinate (m);
int i, j;
if (det == 0.0)
return;
/* Find the cofactor matrix of m, store it in m_inv */
cofactor (m, m_inv);
/* divide by the determinate */
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
m_inv[i][j] = m_inv[i][j] / det;
}
void
transform_core_reset(tool, gdisp_ptr)
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;
}
static int
transform_core_bounds (tool, gdisp_ptr)
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->levels[0].width;
transform_core->y2 = tiles->y + tiles->levels[0].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;
}
return TRUE;
}
static void *
transform_core_recalc (tool, gdisp_ptr)
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, drawable, float_tiles, interpolation, matrix)
GImage *gimage;
GimpDrawable *drawable;
TileManager *float_tiles;
int interpolation;
Matrix matrix;
{
PixelRegion destPR;
TileManager *tiles;
Matrix m;
int itx, ity;
int tx1, ty1, tx2, ty2;
int width, height;
int alpha;
int bytes, b;
int x, y;
int sx, sy;
int plus_x, plus_y;
int plus2_x, plus2_y;
int minus_x, minus_y;
int x1, y1, x2, y2;
double dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4;
double xinc, yinc, winc;
double tx, ty, tw;
double ttx = 0.0, tty = 0.0;
double dx = 0.0, dy = 0.0;
unsigned char * dest, * d;
unsigned char * src[16];
double src_a[16][MAX_CHANNELS];
Tile *tile[16];
int a[16];
unsigned char bg_col[MAX_CHANNELS];
int i;
double a_val, a_mult, a_recip;
int newval;
alpha = 0;
/* 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;
}
/* Find the inverse of the transformation matrix */
invert (matrix, m);
x1 = float_tiles->x;
y1 = float_tiles->y;
x2 = x1 + float_tiles->levels[0].width;
y2 = y1 + float_tiles->levels[0].height;
transform_point (matrix, x1, y1, &dx1, &dy1);
transform_point (matrix, x2, y1, &dx2, &dy2);
transform_point (matrix, x1, y2, &dx3, &dy3);
transform_point (matrix, x2, y2, &dx4, &dy4);
/* Find the bounding coordinates */
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->levels[0].bpp);
pixel_region_init (&destPR, tiles, 0, 0, (tx2 - tx1), (ty2 - ty1), TRUE);
tiles->x = tx1;
tiles->y = ty1;
width = tiles->levels[0].width;
height = tiles->levels[0].height;
bytes = tiles->levels[0].bpp;
dest = (unsigned char *) g_malloc (width * bytes);
xinc = m[0][0];
yinc = m[1][0];
winc = m[2][0];
for (y = ty1; y < ty2; y++)
{
/* When we calculate the inverse transformation, we should transform
* the center of each destination pixel...
*/
tx = xinc * (tx1 + 0.5) + m[0][1] * (y + 0.5) + m[0][2];
ty = yinc * (tx1 + 0.5) + m[1][1] * (y + 0.5) + m[1][2];
tw = winc * (tx1 + 0.5) + m[2][1] * (y + 0.5) + 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;
}
/* tx & ty are the coordinates of the point in the original
* selection's floating buffer. Make sure they're within bounds
*/
if (ttx < 0)
itx = (int) (ttx - 0.999999);
else
itx = (int) ttx;
if (tty < 0)
ity = (int) (tty - 0.999999);
else
ity = (int) tty;
/* if interpolation is on, get the fractional error */
if (interpolation)
{
dx = ttx - itx;
dy = tty - ity;
}
if (itx >= x1 && itx < x2 && ity >= y1 && ity < y2)
{
/* x, y coordinates into source tiles */
sx = itx - x1;
sy = ity - y1;
/* Set the destination pixels */
if (interpolation)
{
plus_x = (itx < (x2 - 1)) ? 1 : 0;
plus_y = (ity < (y2 - 1)) ? 1 : 0;
if (cubic_interpolation)
{
minus_x = (itx > x1) ? -1 : 0;
plus2_x = ((itx + 1) < (x2 - 1)) ? 2 : plus_x;
minus_y = (ity > y1) ? -1 : 0;
plus2_y = ((ity + 1) < (y2 - 1)) ? 2 : plus_y;
REF_TILE (0, sx + minus_x, sy + minus_y);
REF_TILE (1, sx, sy + minus_y);
REF_TILE (2, sx + plus_x, sy + minus_y);
REF_TILE (3, sx + plus2_x, sy + minus_y);
REF_TILE (4, sx + minus_x, sy);
REF_TILE (5, sx, sy);
REF_TILE (6, sx + plus_x, sy);
REF_TILE (7, sx + plus2_x, sy);
REF_TILE (8, sx + minus_x, sy + plus_y);
REF_TILE (9, sx, sy + plus_y);
REF_TILE (10, sx + plus_x, sy + plus_y);
REF_TILE (11, sx + plus2_x, sy + plus_y);
REF_TILE (12, sx + minus_x, sy + plus2_y);
REF_TILE (13, sx, sy + plus2_y);
REF_TILE (14, sx + plus_x, sy + plus2_y);
REF_TILE (15, sx + plus2_x, sy + plus2_y);
a[0] = (minus_y * minus_x) ? src[0][alpha] : 0;
a[1] = (minus_y) ? src[1][alpha] : 0;
a[2] = (minus_y * plus_x) ? src[2][alpha] : 0;
a[3] = (minus_y * plus2_x) ? src[3][alpha] : 0;
a[4] = (minus_x) ? src[4][alpha] : 0;
a[5] = src[5][alpha];
a[6] = (plus_x) ? src[6][alpha] : 0;
a[7] = (plus2_x) ? src[7][alpha] : 0;
a[8] = (plus_y * minus_x) ? src[8][alpha] : 0;
a[9] = (plus_y) ? src[9][alpha] : 0;
a[10] = (plus_y * plus_x) ? src[10][alpha] : 0;
a[11] = (plus_y * plus2_x) ? src[11][alpha] : 0;
a[12] = (plus2_y * minus_x) ? src[12][alpha] : 0;
a[13] = (plus2_y) ? src[13][alpha] : 0;
a[14] = (plus2_y * plus_x) ? src[14][alpha] : 0;
a[15] = (plus2_y * plus2_x) ? src[15][alpha] : 0;
a_val = cubic (dy,
cubic (dx, a[0], a[1], a[2], a[3]),
cubic (dx, a[4], a[5], a[6], a[7]),
cubic (dx, a[8], a[9], a[10], a[11]),
cubic (dx, a[12], a[13], a[14], a[15]));
if (a_val != 0)
a_recip = 255.0 / a_val;
else
a_recip = 0.0;
/* premultiply the alpha */
for (i = 0; i < 16; i++)
{
a_mult = a[i] * (1.0 / 255.0);
for (b = 0; b < alpha; b++)
src_a[i][b] = src[i][b] * a_mult;
}
for (b = 0; b < alpha; b++)
{
newval =
a_recip *
cubic (dy,
cubic (dx, src_a[0][b], src_a[1][b], src_a[2][b], src_a[3][b]),
cubic (dx, src_a[4][b], src_a[5][b], src_a[6][b], src_a[7][b]),
cubic (dx, src_a[8][b], src_a[9][b], src_a[10][b], src_a[11][b]),
cubic (dx, src_a[12][b], src_a[13][b], src_a[14][b], src_a[15][b]));
if ((newval & 0x100) == 0)
*d++ = newval;
else if (newval < 0)
*d++ = 0;
else
*d++ = 255;
}
*d++ = a_val;
for (b = 0; b < 16; b++)
tile_unref (tile[b], FALSE);
}
else /* linear */
{
REF_TILE (0, sx, sy);
REF_TILE (1, sx + plus_x, sy);
REF_TILE (2, sx, sy + plus_y);
REF_TILE (3, sx + plus_x, sy + plus_y);
/* Need special treatment for the alpha channel */
if (plus_x == 0 && plus_y == 0)
{
a[0] = src[0][alpha];
a[1] = a[2] = a[3] = 0;
}
else if (plus_x == 0)
{
a[0] = src[0][alpha];
a[2] = src[2][alpha];
a[1] = a[3] = 0;
}
else if (plus_y == 0)
{
a[0] = src[0][alpha];
a[1] = src[1][alpha];
a[2] = a[3] = 0;
}
else
{
a[0] = src[0][alpha];
a[1] = src[1][alpha];
a[2] = src[2][alpha];
a[3] = src[3][alpha];
}
/* The alpha channel */
a_val = BILINEAR (a[0], a[1], a[2], a[3], dx, dy);
if (a_val != 0)
a_recip = 255.0 / a_val;
else
a_recip = 0.0;
/* premultiply the alpha */
for (i = 0; i < 4; i++)
{
a_mult = a[i] * (1.0 / 255.0);
for (b = 0; b < alpha; b++)
src_a[i][b] = src[i][b] * a_mult;
}
for (b = 0; b < alpha; b++)
*d++ = a_recip * BILINEAR (src_a[0][b], src_a[1][b], src_a[2][b], src_a[3][b], dx, dy);
*d++ = a_val;
for (b = 0; b < 4; b++)
tile_unref (tile[b], FALSE);
}
}
else /* no interpolation */
{
REF_TILE (0, sx, sy);
for (b = 0; b < bytes; b++)
*d++ = src[0][b];
tile_unref (tile[0], FALSE);
}
}
else
{
/* 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);
}
g_free (dest);
return tiles;
}
TileManager *
transform_core_cut (gimage, drawable, new_layer)
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, drawable, tiles, new_layer)
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);
active_tool_layer = layer;
return layer;
}
else
{
if ((layer = drawable_layer ( (drawable))) == NULL)
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->levels[0].width;
GIMP_DRAWABLE(layer)->height = tiles->levels[0].height;
GIMP_DRAWABLE(layer)->bytes = tiles->levels[0].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);
return layer;
}
}
static double
cubic (dx, jm1, j, jp1, jp2)
double dx;
int jm1, j, jp1, jp2;
{
double dx1, dx2, dx3;
double h1, h2, h3, h4;
double result;
/* constraint parameter = -1 */
dx1 = fabs (dx);
dx2 = dx1 * dx1;
dx3 = dx2 * dx1;
h1 = dx3 - 2 * dx2 + 1;
result = h1 * j;
dx1 = fabs (dx - 1.0);
dx2 = dx1 * dx1;
dx3 = dx2 * dx1;
h2 = dx3 - 2 * dx2 + 1;
result += h2 * jp1;
dx1 = fabs (dx - 2.0);
dx2 = dx1 * dx1;
dx3 = dx2 * dx1;
h3 = -dx3 + 5 * dx2 - 8 * dx1 + 4;
result += h3 * jp2;
dx1 = fabs (dx + 1.0);
dx2 = dx1 * dx1;
dx3 = dx2 * dx1;
h4 = -dx3 + 5 * dx2 - 8 * dx1 + 4;
result += h4 * jm1;
if (result < 0.0)
result = 0.0;
if (result > 255.0)
result = 255.0;
return result;
}