mirror of https://github.com/GNOME/gimp.git
1305 lines
44 KiB
C
1305 lines
44 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995-2003 Spencer Kimball, Peter Mattis, and others
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <gegl.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "base/pixel-region.h"
|
|
#include "base/pixel-surround.h"
|
|
#include "base/tile-manager.h"
|
|
#include "base/tile.h"
|
|
|
|
#include "paint-funcs/scale-region.h"
|
|
|
|
#include "gimp-transform-region.h"
|
|
#include "gimpchannel.h"
|
|
#include "gimpcontext.h"
|
|
#include "gimpimage.h"
|
|
#include "gimppickable.h"
|
|
#include "gimpprogress.h"
|
|
|
|
|
|
/* forward function prototypes */
|
|
|
|
static void gimp_transform_region_nearest (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress);
|
|
static void gimp_transform_region_linear (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
gint recursion_level,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress);
|
|
static void gimp_transform_region_cubic (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
gint recursion_level,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress);
|
|
static void gimp_transform_region_lanczos (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
gint recursion_level,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress);
|
|
|
|
static inline void untransform_coords (const GimpMatrix3 *m,
|
|
const gint x,
|
|
const gint y,
|
|
gdouble *tu,
|
|
gdouble *tv,
|
|
gdouble *tw);
|
|
static inline void normalize_coords (const gint coords,
|
|
const gdouble *tu,
|
|
const gdouble *tv,
|
|
const gdouble *tw,
|
|
gdouble *u,
|
|
gdouble *v);
|
|
|
|
static inline gboolean supersample_dtest (const gdouble u0,
|
|
const gdouble v0,
|
|
const gdouble u1,
|
|
const gdouble v1,
|
|
const gdouble u2,
|
|
const gdouble v2,
|
|
const gdouble u3,
|
|
const gdouble v3);
|
|
|
|
static void sample_adapt (TileManager *tm,
|
|
const gdouble xc,
|
|
const gdouble yc,
|
|
const gdouble x0,
|
|
const gdouble y0,
|
|
const gdouble x1,
|
|
const gdouble y1,
|
|
const gdouble x2,
|
|
const gdouble y2,
|
|
const gdouble x3,
|
|
const gdouble y3,
|
|
const gint level,
|
|
guchar *color,
|
|
const guchar *bg_color,
|
|
gint bpp,
|
|
gint alpha);
|
|
|
|
static void sample_linear (PixelSurround *surround,
|
|
const gdouble u,
|
|
const gdouble v,
|
|
guchar *color,
|
|
const gint bytes,
|
|
const gint alpha);
|
|
static void sample_cubic (PixelSurround *surround,
|
|
const gdouble u,
|
|
const gdouble v,
|
|
guchar *color,
|
|
const gint bytes,
|
|
const gint alpha);
|
|
static void sample_lanczos (PixelSurround *surround,
|
|
const gfloat *lanczos,
|
|
const gdouble u,
|
|
const gdouble v,
|
|
guchar *color,
|
|
const gint bytes,
|
|
const gint alpha);
|
|
|
|
|
|
/* public functions */
|
|
|
|
void
|
|
gimp_transform_region (GimpPickable *pickable,
|
|
GimpContext *context,
|
|
TileManager *orig_tiles,
|
|
gint orig_offset_x,
|
|
gint orig_offset_y,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
const GimpMatrix3 *matrix,
|
|
GimpInterpolationType interpolation_type,
|
|
gint recursion_level,
|
|
GimpProgress *progress)
|
|
{
|
|
GimpImageType pickable_type;
|
|
GimpMatrix3 m;
|
|
gint u1, v1, u2, v2; /* source bounding box */
|
|
gint alpha;
|
|
guchar bg_color[MAX_CHANNELS];
|
|
|
|
g_return_if_fail (GIMP_IS_PICKABLE (pickable));
|
|
|
|
u1 = orig_offset_x;
|
|
v1 = orig_offset_y;
|
|
u2 = u1 + tile_manager_width (orig_tiles);
|
|
v2 = v1 + tile_manager_height (orig_tiles);
|
|
|
|
m = *matrix;
|
|
gimp_matrix3_invert (&m);
|
|
|
|
/* turn interpolation off for simple transformations (e.g. rot90) */
|
|
if (gimp_matrix3_is_simple (matrix))
|
|
interpolation_type = GIMP_INTERPOLATION_NONE;
|
|
|
|
pickable_type = gimp_pickable_get_image_type (pickable);
|
|
|
|
/* Get the background color */
|
|
gimp_image_get_background (gimp_pickable_get_image (pickable), context,
|
|
pickable_type, bg_color);
|
|
|
|
switch (GIMP_IMAGE_TYPE_BASE_TYPE (pickable_type))
|
|
{
|
|
case GIMP_RGB:
|
|
bg_color[ALPHA] = TRANSPARENT_OPACITY;
|
|
alpha = ALPHA;
|
|
break;
|
|
|
|
case GIMP_GRAY:
|
|
bg_color[ALPHA_G] = TRANSPARENT_OPACITY;
|
|
alpha = ALPHA_G;
|
|
break;
|
|
|
|
case GIMP_INDEXED:
|
|
bg_color[ALPHA_I] = TRANSPARENT_OPACITY;
|
|
alpha = ALPHA_I;
|
|
/* If the image is indexed color, ignore interpolation value */
|
|
interpolation_type = GIMP_INTERPOLATION_NONE;
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/* "Outside" a channel is transparency, not the bg color */
|
|
if (GIMP_IS_CHANNEL (pickable))
|
|
bg_color[0] = TRANSPARENT_OPACITY;
|
|
|
|
/* setting alpha = 0 will cause the channel's value to be treated
|
|
* as alpha and the color channel loops never to be entered
|
|
*/
|
|
if (tile_manager_bpp (orig_tiles) == 1)
|
|
alpha = 0;
|
|
|
|
switch (interpolation_type)
|
|
{
|
|
case GIMP_INTERPOLATION_NONE:
|
|
gimp_transform_region_nearest (orig_tiles, destPR,
|
|
dest_x1, dest_y1, dest_x2, dest_y2,
|
|
u1, v1, u2, v2,
|
|
&m, alpha, bg_color, progress);
|
|
break;
|
|
|
|
case GIMP_INTERPOLATION_LINEAR:
|
|
gimp_transform_region_linear (orig_tiles, destPR,
|
|
dest_x1, dest_y1, dest_x2, dest_y2,
|
|
u1, v1, u2, v2,
|
|
&m, alpha, recursion_level,
|
|
bg_color, progress);
|
|
break;
|
|
|
|
case GIMP_INTERPOLATION_CUBIC:
|
|
gimp_transform_region_cubic (orig_tiles, destPR,
|
|
dest_x1, dest_y1, dest_x2, dest_y2,
|
|
u1, v1, u2, v2,
|
|
&m, alpha, recursion_level,
|
|
bg_color, progress);
|
|
break;
|
|
|
|
case GIMP_INTERPOLATION_LANCZOS:
|
|
gimp_transform_region_lanczos (orig_tiles, destPR,
|
|
dest_x1, dest_y1, dest_x2, dest_y2,
|
|
u1, v1, u2, v2,
|
|
&m, alpha, recursion_level,
|
|
bg_color, progress);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_transform_region_nearest (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress)
|
|
{
|
|
gdouble uinc, vinc, winc; /* increments in source coordinates */
|
|
gint pixels;
|
|
gint total;
|
|
gint n;
|
|
gpointer pr;
|
|
|
|
uinc = m->coeff[0][0];
|
|
vinc = m->coeff[1][0];
|
|
winc = m->coeff[2][0];
|
|
|
|
total = destPR->w * destPR->h;
|
|
|
|
for (pr = pixel_regions_register (1, destPR), pixels = 0, n = 0;
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr), n++)
|
|
{
|
|
guchar *dest = destPR->data;
|
|
gint y;
|
|
|
|
for (y = destPR->y; y < destPR->y + destPR->h; y++)
|
|
{
|
|
gint x = dest_x1 + destPR->x;
|
|
gint width = destPR->w;
|
|
guchar *d = dest;
|
|
gdouble tu, tv, tw; /* undivided source coordinates and divisor */
|
|
|
|
/* set up inverse transform steps */
|
|
tu = uinc * x + m->coeff[0][1] * (dest_y1 + y) + m->coeff[0][2];
|
|
tv = vinc * x + m->coeff[1][1] * (dest_y1 + y) + m->coeff[1][2];
|
|
tw = winc * x + m->coeff[2][1] * (dest_y1 + y) + m->coeff[2][2];
|
|
|
|
while (width--)
|
|
{
|
|
gdouble u, v; /* source coordinates */
|
|
gint iu, iv;
|
|
|
|
/* normalize homogeneous coords */
|
|
normalize_coords (1, &tu, &tv, &tw, &u, &v);
|
|
|
|
iu = (gint) u;
|
|
iv = (gint) v;
|
|
|
|
/* Set the destination pixels */
|
|
if (iu >= u1 && iu < u2 &&
|
|
iv >= v1 && iv < v2)
|
|
{
|
|
tile_manager_read_pixel_data_1 (orig_tiles, iu - u1, iv - v1,
|
|
d);
|
|
|
|
d += destPR->bytes;
|
|
}
|
|
else /* not in source range */
|
|
{
|
|
gint b;
|
|
|
|
for (b = 0; b < destPR->bytes; b++)
|
|
*d++ = bg_color[b];
|
|
}
|
|
|
|
tu += uinc;
|
|
tv += vinc;
|
|
tw += winc;
|
|
}
|
|
|
|
dest += destPR->rowstride;
|
|
}
|
|
|
|
if (progress)
|
|
{
|
|
pixels += destPR->w * destPR->h;
|
|
|
|
if (n % 16 == 0)
|
|
gimp_progress_set_value (progress,
|
|
(gdouble) pixels / (gdouble) total);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_transform_region_linear (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
gint recursion_level,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress)
|
|
{
|
|
PixelSurround *surround;
|
|
gdouble uinc, vinc, winc; /* increments in source coordinates */
|
|
gint pixels;
|
|
gint total;
|
|
gint n;
|
|
gpointer pr;
|
|
|
|
surround = pixel_surround_new (orig_tiles, 2, 2, PIXEL_SURROUND_BACKGROUND);
|
|
pixel_surround_set_bg (surround, bg_color);
|
|
|
|
uinc = m->coeff[0][0];
|
|
vinc = m->coeff[1][0];
|
|
winc = m->coeff[2][0];
|
|
|
|
total = destPR->w * destPR->h;
|
|
|
|
for (pr = pixel_regions_register (1, destPR), pixels = 0, n = 0;
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr), n++)
|
|
{
|
|
guchar *dest = destPR->data;
|
|
gint y;
|
|
|
|
for (y = destPR->y; y < destPR->y + destPR->h; y++)
|
|
{
|
|
guchar *d = dest;
|
|
gint width = destPR->w;
|
|
gdouble tu[5], tv[5]; /* undivided source coordinates */
|
|
gdouble tw[5]; /* divisor */
|
|
|
|
/* set up inverse transform steps */
|
|
untransform_coords (m, dest_x1 + destPR->x, dest_y1 + y, tu, tv, tw);
|
|
|
|
while (width--)
|
|
{
|
|
gdouble u[5], v[5]; /* source coordinates */
|
|
gint i;
|
|
|
|
/* normalize homogeneous coords */
|
|
normalize_coords (5, tu, tv, tw, u, v);
|
|
|
|
/* Set the destination pixels */
|
|
if (supersample_dtest (u[1], v[1], u[2], v[2],
|
|
u[3], v[3], u[4], v[4]))
|
|
{
|
|
sample_adapt (orig_tiles,
|
|
u[0] - u1, v[0] - v1,
|
|
u[1] - u1, v[1] - v1,
|
|
u[2] - u1, v[2] - v1,
|
|
u[3] - u1, v[3] - v1,
|
|
u[4] - u1, v[4] - v1,
|
|
recursion_level,
|
|
d, bg_color, destPR->bytes, alpha);
|
|
}
|
|
else
|
|
{
|
|
sample_linear (surround, u[0] - u1, v[0] - v1,
|
|
d, destPR->bytes, alpha);
|
|
}
|
|
|
|
d += destPR->bytes;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
tu[i] += uinc;
|
|
tv[i] += vinc;
|
|
tw[i] += winc;
|
|
}
|
|
}
|
|
|
|
dest += destPR->rowstride;
|
|
}
|
|
|
|
if (progress)
|
|
{
|
|
pixels += destPR->w * destPR->h;
|
|
|
|
if (n % 16 == 0)
|
|
gimp_progress_set_value (progress,
|
|
(gdouble) pixels / (gdouble) total);
|
|
}
|
|
}
|
|
|
|
pixel_surround_destroy (surround);
|
|
}
|
|
|
|
static void
|
|
gimp_transform_region_cubic (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
gint recursion_level,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress)
|
|
{
|
|
PixelSurround *surround;
|
|
gdouble uinc, vinc, winc; /* increments in source coordinates */
|
|
gint pixels;
|
|
gint total;
|
|
gint n;
|
|
gpointer pr;
|
|
|
|
surround = pixel_surround_new (orig_tiles, 4, 4, PIXEL_SURROUND_BACKGROUND);
|
|
pixel_surround_set_bg (surround, bg_color);
|
|
|
|
uinc = m->coeff[0][0];
|
|
vinc = m->coeff[1][0];
|
|
winc = m->coeff[2][0];
|
|
|
|
total = destPR->w * destPR->h;
|
|
|
|
for (pr = pixel_regions_register (1, destPR), pixels = 0, n = 0;
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr), n++)
|
|
{
|
|
guchar *dest = destPR->data;
|
|
gint y;
|
|
|
|
for (y = destPR->y; y < destPR->y + destPR->h; y++)
|
|
{
|
|
guchar *d = dest;
|
|
gint width = destPR->w;
|
|
gdouble tu[5], tv[5]; /* undivided source coordinates */
|
|
gdouble tw[5]; /* divisor */
|
|
|
|
/* set up inverse transform steps */
|
|
untransform_coords (m, dest_x1 + destPR->x, dest_y1 + y, tu, tv, tw);
|
|
|
|
while (width--)
|
|
{
|
|
gdouble u[5], v[5]; /* source coordinates */
|
|
gint i;
|
|
|
|
/* normalize homogeneous coords */
|
|
normalize_coords (5, tu, tv, tw, u, v);
|
|
|
|
if (supersample_dtest (u[1], v[1], u[2], v[2],
|
|
u[3], v[3], u[4], v[4]))
|
|
{
|
|
sample_adapt (orig_tiles,
|
|
u[0] - u1, v[0] - v1,
|
|
u[1] - u1, v[1] - v1,
|
|
u[2] - u1, v[2] - v1,
|
|
u[3] - u1, v[3] - v1,
|
|
u[4] - u1, v[4] - v1,
|
|
recursion_level,
|
|
d, bg_color, destPR->bytes, alpha);
|
|
}
|
|
else
|
|
{
|
|
sample_cubic (surround, u[0] - u1, v[0] - v1,
|
|
d, destPR->bytes, alpha);
|
|
}
|
|
|
|
d += destPR->bytes;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
tu[i] += uinc;
|
|
tv[i] += vinc;
|
|
tw[i] += winc;
|
|
}
|
|
}
|
|
|
|
dest += destPR->rowstride;
|
|
}
|
|
|
|
if (progress)
|
|
{
|
|
pixels += destPR->w * destPR->h;
|
|
|
|
if (n % 16 == 0)
|
|
gimp_progress_set_value (progress,
|
|
(gdouble) pixels / (gdouble) total);
|
|
}
|
|
}
|
|
|
|
pixel_surround_destroy (surround);
|
|
}
|
|
|
|
static void
|
|
gimp_transform_region_lanczos (TileManager *orig_tiles,
|
|
PixelRegion *destPR,
|
|
gint dest_x1,
|
|
gint dest_y1,
|
|
gint dest_x2,
|
|
gint dest_y2,
|
|
gint u1,
|
|
gint v1,
|
|
gint u2,
|
|
gint v2,
|
|
const GimpMatrix3 *m,
|
|
gint alpha,
|
|
gint recursion_level,
|
|
const guchar *bg_color,
|
|
GimpProgress *progress)
|
|
{
|
|
PixelSurround *surround;
|
|
gfloat *lanczos; /* Lanczos lookup table */
|
|
gdouble uinc, vinc, winc; /* increments in source coordinates */
|
|
gint pixels;
|
|
gint total;
|
|
gint n;
|
|
gpointer pr;
|
|
|
|
surround = pixel_surround_new (orig_tiles,
|
|
LANCZOS_WIDTH2, LANCZOS_WIDTH2,
|
|
PIXEL_SURROUND_BACKGROUND);
|
|
pixel_surround_set_bg (surround, bg_color);
|
|
|
|
/* allocate and fill lanczos lookup table */
|
|
lanczos = create_lanczos_lookup ();
|
|
|
|
uinc = m->coeff[0][0];
|
|
vinc = m->coeff[1][0];
|
|
winc = m->coeff[2][0];
|
|
|
|
total = destPR->w * destPR->h;
|
|
|
|
for (pr = pixel_regions_register (1, destPR), pixels = 0, n = 0;
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr), n++)
|
|
{
|
|
guchar *dest = destPR->data;
|
|
gint y;
|
|
|
|
for (y = destPR->y; y < destPR->y + destPR->h; y++)
|
|
{
|
|
guchar *d = dest;
|
|
gint width = destPR->w;
|
|
gdouble tu[5], tv[5]; /* undivided source coordinates */
|
|
gdouble tw[5]; /* divisor */
|
|
|
|
/* set up inverse transform steps */
|
|
untransform_coords (m, dest_x1 + destPR->x, dest_y1 + y, tu, tv, tw);
|
|
|
|
while (width--)
|
|
{
|
|
gdouble u[5], v[5]; /* source coordinates */
|
|
gint i;
|
|
|
|
/* normalize homogeneous coords */
|
|
normalize_coords (5, tu, tv, tw, u, v);
|
|
|
|
if (supersample_dtest (u[1], v[1], u[2], v[2],
|
|
u[3], v[3], u[4], v[4]))
|
|
{
|
|
sample_adapt (orig_tiles,
|
|
u[0] - u1, v[0] - v1,
|
|
u[1] - u1, v[1] - v1,
|
|
u[2] - u1, v[2] - v1,
|
|
u[3] - u1, v[3] - v1,
|
|
u[4] - u1, v[4] - v1,
|
|
recursion_level,
|
|
d, bg_color, destPR->bytes, alpha);
|
|
}
|
|
else
|
|
{
|
|
sample_lanczos (surround, lanczos, u[0] - u1, v[0] - v1,
|
|
d, destPR->bytes, alpha);
|
|
}
|
|
|
|
d += destPR->bytes;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
tu[i] += uinc;
|
|
tv[i] += vinc;
|
|
tw[i] += winc;
|
|
}
|
|
}
|
|
|
|
dest += destPR->rowstride;
|
|
}
|
|
|
|
if (progress)
|
|
{
|
|
pixels += destPR->w * destPR->h;
|
|
|
|
if (n % 16 == 0)
|
|
gimp_progress_set_value (progress,
|
|
(gdouble) pixels / (gdouble) total);
|
|
}
|
|
}
|
|
|
|
g_free (lanczos);
|
|
pixel_surround_destroy (surround);
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static inline void
|
|
untransform_coords (const GimpMatrix3 *m,
|
|
const gint x,
|
|
const gint y,
|
|
gdouble *tu,
|
|
gdouble *tv,
|
|
gdouble *tw)
|
|
{
|
|
tu[0] = m->coeff[0][0] * (x ) + m->coeff[0][1] * (y ) + m->coeff[0][2];
|
|
tv[0] = m->coeff[1][0] * (x ) + m->coeff[1][1] * (y ) + m->coeff[1][2];
|
|
tw[0] = m->coeff[2][0] * (x ) + m->coeff[2][1] * (y ) + m->coeff[2][2];
|
|
|
|
tu[1] = m->coeff[0][0] * (x - 1) + m->coeff[0][1] * (y ) + m->coeff[0][2];
|
|
tv[1] = m->coeff[1][0] * (x - 1) + m->coeff[1][1] * (y ) + m->coeff[1][2];
|
|
tw[1] = m->coeff[2][0] * (x - 1) + m->coeff[2][1] * (y ) + m->coeff[2][2];
|
|
|
|
tu[2] = m->coeff[0][0] * (x ) + m->coeff[0][1] * (y - 1) + m->coeff[0][2];
|
|
tv[2] = m->coeff[1][0] * (x ) + m->coeff[1][1] * (y - 1) + m->coeff[1][2];
|
|
tw[2] = m->coeff[2][0] * (x ) + m->coeff[2][1] * (y - 1) + m->coeff[2][2];
|
|
|
|
tu[3] = m->coeff[0][0] * (x + 1) + m->coeff[0][1] * (y ) + m->coeff[0][2];
|
|
tv[3] = m->coeff[1][0] * (x + 1) + m->coeff[1][1] * (y ) + m->coeff[1][2];
|
|
tw[3] = m->coeff[2][0] * (x + 1) + m->coeff[2][1] * (y ) + m->coeff[2][2];
|
|
|
|
tu[4] = m->coeff[0][0] * (x ) + m->coeff[0][1] * (y + 1) + m->coeff[0][2];
|
|
tv[4] = m->coeff[1][0] * (x ) + m->coeff[1][1] * (y + 1) + m->coeff[1][2];
|
|
tw[4] = m->coeff[2][0] * (x ) + m->coeff[2][1] * (y + 1) + m->coeff[2][2];
|
|
}
|
|
|
|
static inline void
|
|
normalize_coords (const gint coords,
|
|
const gdouble *tu,
|
|
const gdouble *tv,
|
|
const gdouble *tw,
|
|
gdouble *u,
|
|
gdouble *v)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < coords; i++)
|
|
{
|
|
if (G_LIKELY (tw[i] != 0.0))
|
|
{
|
|
u[i] = tu[i] / tw[i];
|
|
v[i] = tv[i] / tw[i];
|
|
}
|
|
else
|
|
{
|
|
g_warning ("homogeneous coordinate = 0...\n");
|
|
|
|
u[i] = tu[i];
|
|
v[i] = tv[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#define BILINEAR(jk, j1k, jk1, j1k1, dx, dy) \
|
|
((1 - dy) * (jk + dx * (j1k - jk)) + \
|
|
dy * (jk1 + dx * (j1k1 - jk1)))
|
|
|
|
/* u & v are the subpixel coordinates of the point in
|
|
* the original selection's floating buffer.
|
|
* We need the two pixel coords around them:
|
|
* iu to iu + 1, iv to iv + 1
|
|
*/
|
|
static void
|
|
sample_linear (PixelSurround *surround,
|
|
const gdouble u,
|
|
const gdouble v,
|
|
guchar *color,
|
|
const gint bytes,
|
|
const gint alpha)
|
|
{
|
|
gdouble a_val, a_recip;
|
|
gint i;
|
|
const gint iu = floor (u);
|
|
const gint iv = floor (v);
|
|
gint rowstride;
|
|
gdouble du, dv;
|
|
const guchar *alphachan;
|
|
const guchar *data;
|
|
|
|
/* lock the pixel surround */
|
|
data = pixel_surround_lock (surround, iu, iv, &rowstride);
|
|
|
|
/* the fractional error */
|
|
du = u - iu;
|
|
dv = v - iv;
|
|
|
|
/* calculate alpha value of result pixel */
|
|
alphachan = &data[alpha];
|
|
a_val = BILINEAR (alphachan[0], alphachan[bytes],
|
|
alphachan[rowstride], alphachan[rowstride + bytes], du, dv);
|
|
|
|
if (a_val <= 0.0)
|
|
{
|
|
a_recip = 0.0;
|
|
color[alpha] = 0.0;
|
|
}
|
|
else if (a_val >= 255.0)
|
|
{
|
|
a_recip = 1.0 / a_val;
|
|
color[alpha] = 255;
|
|
}
|
|
else
|
|
{
|
|
a_recip = 1.0 / a_val;
|
|
color[alpha] = RINT (a_val);
|
|
}
|
|
|
|
/* for colour channels c,
|
|
* result = bilinear (c * alpha) / bilinear (alpha)
|
|
*
|
|
* never entered for alpha == 0
|
|
*/
|
|
for (i = 0; i < alpha; i++)
|
|
{
|
|
gint newval =
|
|
ROUND ((a_recip *
|
|
BILINEAR (alphachan[0] * data[i],
|
|
alphachan[bytes] * data[bytes + i],
|
|
alphachan[rowstride] * data[rowstride + i],
|
|
alphachan[rowstride + bytes] * data[rowstride + bytes + i],
|
|
du, dv)));
|
|
|
|
color[i] = CLAMP (newval, 0, 255);
|
|
}
|
|
}
|
|
|
|
/* macros to handle conversion to/from fixed point, this fixed point code
|
|
* uses signed integers, by using 8 bits for the fractional part we have
|
|
*
|
|
* 1 bit sign
|
|
* 21 bits integer part
|
|
* 8 bit fractional part
|
|
*
|
|
* 1023 discrete subpixel sample positions should be enough for the needs
|
|
* of the supersampling algorithm, drawables where the dimensions have a need
|
|
* exceeding 2^21 ( 2097152px, will typically use terabytes of memory, when
|
|
* that is the common need, we can probably assume 64 bit integers and adjust
|
|
* FIXED_SHIFT accordingly.
|
|
*/
|
|
#define FIXED_SHIFT 10
|
|
#define FIXED_UNIT (1 << FIXED_SHIFT)
|
|
#define DOUBLE2FIXED(val) ((val) * FIXED_UNIT)
|
|
#define FIXED2DOUBLE(val) ((val) / FIXED_UNIT)
|
|
|
|
/*
|
|
bilinear interpolation of a fixed point pixel
|
|
*/
|
|
static void
|
|
sample_bi (TileManager *tm,
|
|
const gint x,
|
|
const gint y,
|
|
guchar *color,
|
|
const guchar *bg_color,
|
|
const gint bpp,
|
|
const gint alpha)
|
|
{
|
|
const gint xscale = (x & (FIXED_UNIT-1));
|
|
const gint yscale = (y & (FIXED_UNIT-1));
|
|
const gint x0 = x >> FIXED_SHIFT;
|
|
const gint y0 = y >> FIXED_SHIFT;
|
|
const gint x1 = x0 + 1;
|
|
const gint y1 = y0 + 1;
|
|
guchar C[4][4];
|
|
gint i;
|
|
|
|
/* fill the color with default values, since
|
|
* tile_manager_read_pixel_data_1 does nothing, when accesses are
|
|
* out of bounds.
|
|
*/
|
|
for (i = 0; i < 4; i++)
|
|
*(guint*) (&C[i]) = *(guint*) (bg_color);
|
|
|
|
tile_manager_read_pixel_data_1 (tm, x0, y0, C[0]);
|
|
tile_manager_read_pixel_data_1 (tm, x1, y0, C[2]);
|
|
tile_manager_read_pixel_data_1 (tm, x0, y1, C[1]);
|
|
tile_manager_read_pixel_data_1 (tm, x1, y1, C[3]);
|
|
|
|
#define lerp(v1, v2, r) \
|
|
(((guint)(v1) * (FIXED_UNIT - (guint)(r)) + \
|
|
(guint)(v2) * (guint)(r)) >> FIXED_SHIFT)
|
|
|
|
color[alpha]= lerp (lerp (C[0][alpha], C[1][alpha], yscale),
|
|
lerp (C[2][alpha], C[3][alpha], yscale), xscale);
|
|
|
|
if (color[alpha])
|
|
{
|
|
/* to avoid problems, calculate with premultiplied alpha */
|
|
for (i = 0; i < alpha; i++)
|
|
color[i] = lerp (lerp (C[0][i] * C[0][alpha] / 255,
|
|
C[1][i] * C[1][alpha] / 255, yscale),
|
|
lerp (C[2][i] * C[2][alpha] / 255,
|
|
C[3][i] * C[3][alpha] / 255, yscale), xscale);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < alpha; i++)
|
|
color[i] = 0;
|
|
}
|
|
#undef lerp
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Returns TRUE if one of the deltas of the quad edge is > 1.0 (16.16 fixed
|
|
* values). This is the condition used on whether additional recursive
|
|
* subdivision should be used.
|
|
*/
|
|
static inline gboolean
|
|
supersample_test (const gint x0, const gint y0,
|
|
const gint x1, const gint y1,
|
|
const gint x2, const gint y2,
|
|
const gint x3, const gint y3)
|
|
{
|
|
return (abs (x0 - x1) > FIXED_UNIT ||
|
|
abs (x1 - x2) > FIXED_UNIT ||
|
|
abs (x2 - x3) > FIXED_UNIT ||
|
|
abs (x3 - x0) > FIXED_UNIT ||
|
|
|
|
abs (y0 - y1) > FIXED_UNIT ||
|
|
abs (y1 - y2) > FIXED_UNIT ||
|
|
abs (y2 - y3) > FIXED_UNIT ||
|
|
abs (y3 - y0) > FIXED_UNIT);
|
|
}
|
|
|
|
/*
|
|
* Returns TRUE if one of the deltas of the quad edge is > sqrt(2) (double
|
|
* values). This is the condition used on whether supersampling should be used
|
|
* or not. By making this sqrt(2) supersampling will not be triggered by
|
|
* rotations.
|
|
*/
|
|
static inline gboolean
|
|
supersample_dtest (const gdouble x0, const gdouble y0,
|
|
const gdouble x1, const gdouble y1,
|
|
const gdouble x2, const gdouble y2,
|
|
const gdouble x3, const gdouble y3)
|
|
{
|
|
return (fabs (x0 - x1) > G_SQRT2 ||
|
|
fabs (x1 - x2) > G_SQRT2 ||
|
|
fabs (x2 - x3) > G_SQRT2 ||
|
|
fabs (x3 - x0) > G_SQRT2 ||
|
|
|
|
fabs (y0 - y1) > G_SQRT2 ||
|
|
fabs (y1 - y2) > G_SQRT2 ||
|
|
fabs (y2 - y3) > G_SQRT2 ||
|
|
fabs (y3 - y0) > G_SQRT2);
|
|
}
|
|
|
|
/*
|
|
sample a grid that is spaced according to the quadraliteral's edges,
|
|
it subdivides a maximum of level times before sampling.
|
|
0..3 is a cycle around the quad
|
|
*/
|
|
static void
|
|
get_sample (TileManager *tm,
|
|
const gint xc,
|
|
const gint yc,
|
|
const gint x0,
|
|
const gint y0,
|
|
const gint x1,
|
|
const gint y1,
|
|
const gint x2,
|
|
const gint y2,
|
|
const gint x3,
|
|
const gint y3,
|
|
gint *cc,
|
|
const gint level,
|
|
guint *color,
|
|
const guchar *bg_color,
|
|
const gint bpp,
|
|
const gint alpha)
|
|
{
|
|
if (!level || !supersample_test (x0, y0, x1, y1, x2, y2, x3, y3))
|
|
{
|
|
gint i;
|
|
guchar C[4];
|
|
|
|
sample_bi (tm, xc, yc, C, bg_color, bpp, alpha);
|
|
|
|
for (i = 0; i < bpp; i++)
|
|
color[i]+= C[i];
|
|
|
|
(*cc)++; /* increase number of samples taken */
|
|
}
|
|
else
|
|
{
|
|
gint tx, lx, rx, bx, tlx, trx, blx, brx;
|
|
gint ty, ly, ry, by, tly, try, bly, bry;
|
|
|
|
/* calculate subdivided corner coordinates (including centercoords
|
|
thus using a bilinear interpolation,. almost as good as
|
|
doing the perspective transform for each subpixel coordinate*/
|
|
|
|
tx = (x0 + x1) / 2;
|
|
tlx = (x0 + xc) / 2;
|
|
trx = (x1 + xc) / 2;
|
|
lx = (x0 + x3) / 2;
|
|
rx = (x1 + x2) / 2;
|
|
blx = (x3 + xc) / 2;
|
|
brx = (x2 + xc) / 2;
|
|
bx = (x3 + x2) / 2;
|
|
|
|
ty = (y0 + y1) / 2;
|
|
tly = (y0 + yc) / 2;
|
|
try = (y1 + yc) / 2;
|
|
ly = (y0 + y3) / 2;
|
|
ry = (y1 + y2) / 2;
|
|
bly = (y3 + yc) / 2;
|
|
bry = (y2 + yc) / 2;
|
|
by = (y3 + y2) / 2;
|
|
|
|
get_sample (tm,
|
|
tlx,tly,
|
|
x0,y0, tx,ty, xc,yc, lx,ly,
|
|
cc, level-1, color, bg_color, bpp, alpha);
|
|
|
|
get_sample (tm,
|
|
trx,try,
|
|
tx,ty, x1,y1, rx,ry, xc,yc,
|
|
cc, level-1, color, bg_color, bpp, alpha);
|
|
|
|
get_sample (tm,
|
|
brx,bry,
|
|
xc,yc, rx,ry, x2,y2, bx,by,
|
|
cc, level-1, color, bg_color, bpp, alpha);
|
|
|
|
get_sample (tm,
|
|
blx,bly,
|
|
lx,ly, xc,yc, bx,by, x3,y3,
|
|
cc, level-1, color, bg_color, bpp, alpha);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sample_adapt (TileManager *tm,
|
|
const gdouble xc,
|
|
const gdouble yc,
|
|
const gdouble x0,
|
|
const gdouble y0,
|
|
const gdouble x1,
|
|
const gdouble y1,
|
|
const gdouble x2,
|
|
const gdouble y2,
|
|
const gdouble x3,
|
|
const gdouble y3,
|
|
const gint level,
|
|
guchar *color,
|
|
const guchar *bg_color,
|
|
const gint bpp,
|
|
const gint alpha)
|
|
{
|
|
gint cc = 0;
|
|
gint i;
|
|
guint C[MAX_CHANNELS];
|
|
|
|
C[0] = C[1] = C[2] = C[3] = 0;
|
|
|
|
get_sample (tm,
|
|
DOUBLE2FIXED (xc), DOUBLE2FIXED (yc),
|
|
DOUBLE2FIXED (x0), DOUBLE2FIXED (y0),
|
|
DOUBLE2FIXED (x1), DOUBLE2FIXED (y1),
|
|
DOUBLE2FIXED (x2), DOUBLE2FIXED (y2),
|
|
DOUBLE2FIXED (x3), DOUBLE2FIXED (y3),
|
|
&cc, level, C, bg_color, bpp, alpha);
|
|
|
|
if (!cc)
|
|
cc=1;
|
|
|
|
color[alpha] = C[alpha] / cc;
|
|
|
|
if (color[alpha])
|
|
{
|
|
/* go from premultiplied to postmultiplied alpha */
|
|
for (i = 0; i < alpha; i++)
|
|
color[i] = ((C[i] / cc) * 255) / color[alpha];
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < alpha; i++)
|
|
color[i] = 0;
|
|
}
|
|
}
|
|
|
|
/* access interleaved pixels */
|
|
#define CUBIC_ROW(dx, row, step) \
|
|
gimp_drawable_transform_cubic(dx,\
|
|
(row)[0], (row)[step], (row)[step+step], (row)[step+step+step])
|
|
|
|
#define CUBIC_SCALED_ROW(dx, row, arow, step) \
|
|
gimp_drawable_transform_cubic(dx, \
|
|
(arow)[0] * (row)[0], \
|
|
(arow)[step] * (row)[step], \
|
|
(arow)[step+step] * (row)[step+step], \
|
|
(arow)[step+step+step] * (row)[step+step+step])
|
|
|
|
|
|
/* Note: cubic function no longer clips result. */
|
|
/* Inlining this function makes sample_cubic() run about 10% faster. (Sven) */
|
|
static inline gdouble
|
|
gimp_drawable_transform_cubic (const gdouble dx,
|
|
const gint jm1,
|
|
const gint j,
|
|
const gint jp1,
|
|
const gint jp2)
|
|
{
|
|
gdouble 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;
|
|
#endif
|
|
|
|
/* 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;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* u & v are the subpixel coordinates of the point in
|
|
* the original selection's floating buffer.
|
|
* We need the four integer pixel coords around them:
|
|
* iu to iu + 3, iv to iv + 3
|
|
*/
|
|
static void
|
|
sample_cubic (PixelSurround *surround,
|
|
const gdouble u,
|
|
const gdouble v,
|
|
guchar *color,
|
|
const gint bytes,
|
|
const gint alpha)
|
|
{
|
|
gdouble a_val, a_recip;
|
|
gint i;
|
|
const gint iu = floor(u);
|
|
const gint iv = floor(v);
|
|
gint rowstride;
|
|
gdouble du, dv;
|
|
const guchar *data;
|
|
|
|
/* lock the pixel surround */
|
|
data = pixel_surround_lock (surround, iu - 1 , iv - 1, &rowstride);
|
|
|
|
/* the fractional error */
|
|
du = u - iu;
|
|
dv = v - iv;
|
|
|
|
/* calculate alpha of result */
|
|
a_val = gimp_drawable_transform_cubic
|
|
(dv,
|
|
CUBIC_ROW (du, data + alpha + rowstride * 0, bytes),
|
|
CUBIC_ROW (du, data + alpha + rowstride * 1, bytes),
|
|
CUBIC_ROW (du, data + alpha + rowstride * 2, bytes),
|
|
CUBIC_ROW (du, data + alpha + rowstride * 3, bytes));
|
|
|
|
if (a_val <= 0.0)
|
|
{
|
|
a_recip = 0.0;
|
|
color[alpha] = 0;
|
|
}
|
|
else if (a_val > 255.0)
|
|
{
|
|
a_recip = 1.0 / a_val;
|
|
color[alpha] = 255;
|
|
}
|
|
else
|
|
{
|
|
a_recip = 1.0 / a_val;
|
|
color[alpha] = RINT (a_val);
|
|
}
|
|
|
|
/* for colour channels c,
|
|
* result = bicubic (c * alpha) / bicubic (alpha)
|
|
*
|
|
* never entered for alpha == 0
|
|
*/
|
|
for (i = 0; i < alpha; i++)
|
|
{
|
|
gint newval =
|
|
ROUND ((a_recip *
|
|
gimp_drawable_transform_cubic
|
|
(dv,
|
|
CUBIC_SCALED_ROW (du,
|
|
i + data + rowstride * 0,
|
|
data + alpha + rowstride * 0,
|
|
bytes),
|
|
CUBIC_SCALED_ROW (du,
|
|
i + data + rowstride * 1,
|
|
data + alpha + rowstride * 1,
|
|
bytes),
|
|
CUBIC_SCALED_ROW (du,
|
|
i + data + rowstride * 2,
|
|
data + alpha + rowstride * 2,
|
|
bytes),
|
|
CUBIC_SCALED_ROW (du,
|
|
i + data + rowstride * 3,
|
|
data + alpha + rowstride * 3,
|
|
bytes))));
|
|
|
|
color[i] = CLAMP (newval, 0, 255);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sample_lanczos (PixelSurround *surround,
|
|
const gfloat *lanczos,
|
|
const gdouble u,
|
|
const gdouble v,
|
|
guchar *color,
|
|
const gint bytes,
|
|
const gint alpha)
|
|
{
|
|
gdouble x_kernel[LANCZOS_WIDTH2]; /* 1-D kernels of window coeffs */
|
|
gdouble y_kernel[LANCZOS_WIDTH2];
|
|
gdouble x_sum, y_sum; /* sum of Lanczos weights */
|
|
gdouble arecip;
|
|
gdouble aval;
|
|
gint su, sv;
|
|
gint b;
|
|
gint i, j;
|
|
gint iu, iv;
|
|
gint rowstride;
|
|
const guchar *data;
|
|
const guchar *src;
|
|
|
|
iu = (gint) u;
|
|
iv = (gint) v;
|
|
|
|
/* get weight for fractional error */
|
|
su = (gint) ((u - iu) * LANCZOS_SPP);
|
|
sv = (gint) ((v - iv) * LANCZOS_SPP);
|
|
|
|
/* fill 1D kernels */
|
|
for (x_sum = y_sum = 0.0, i = LANCZOS_WIDTH; i >= -LANCZOS_WIDTH; i--)
|
|
{
|
|
gint pos = i * LANCZOS_SPP;
|
|
|
|
x_sum += x_kernel[LANCZOS_WIDTH + i] = lanczos[ABS (su - pos)];
|
|
y_sum += y_kernel[LANCZOS_WIDTH + i] = lanczos[ABS (sv - pos)];
|
|
}
|
|
|
|
/* normalise the weighted arrays */
|
|
for (i = 0; i < LANCZOS_WIDTH2 ; i++)
|
|
{
|
|
x_kernel[i] /= x_sum;
|
|
y_kernel[i] /= y_sum;
|
|
}
|
|
|
|
/* lock the pixel surround */
|
|
data = pixel_surround_lock (surround,
|
|
iu - LANCZOS_WIDTH, iv - LANCZOS_WIDTH,
|
|
&rowstride);
|
|
|
|
src = data + alpha;
|
|
aval = 0.0;
|
|
|
|
for (j = 0; j < LANCZOS_WIDTH2 ; j++)
|
|
{
|
|
for (i = 0; i < LANCZOS_WIDTH2 ; i++)
|
|
aval += y_kernel[j] * x_kernel[i] * (gdouble) src[i * bytes];
|
|
|
|
src += rowstride;
|
|
}
|
|
|
|
if (aval <= 0.0)
|
|
{
|
|
arecip = 0.0;
|
|
aval = 0;
|
|
}
|
|
else if (aval > 255.0)
|
|
{
|
|
arecip = 1.0 / aval;
|
|
aval = 255;
|
|
}
|
|
else
|
|
{
|
|
arecip = 1.0 / aval;
|
|
}
|
|
|
|
for (b = 0; b < alpha; b++)
|
|
{
|
|
const guchar *asrc;
|
|
gdouble newval = 0.0;
|
|
|
|
src = data + b;
|
|
asrc = data + alpha;
|
|
|
|
for (j = 0; j < LANCZOS_WIDTH2; j++)
|
|
{
|
|
for (i = 0; i < LANCZOS_WIDTH2; i++)
|
|
newval += (y_kernel[j] * x_kernel[i] *
|
|
(gdouble) src[i * bytes] * (gdouble) asrc[i * bytes]);
|
|
|
|
src += rowstride;
|
|
asrc += rowstride;
|
|
}
|
|
|
|
newval *= arecip;
|
|
color[b] = CLAMP (ROUND (newval), 0, 255);
|
|
}
|
|
|
|
color[alpha] = RINT (aval);
|
|
}
|