1048 lines
37 KiB
C++
1048 lines
37 KiB
C++
|
/* PIKA - Photo and Image Kooker Application
|
||
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||
|
*
|
||
|
* Original copyright, applying to most contents (license remains unchanged):
|
||
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||
|
*
|
||
|
* pikabrush-transform.c
|
||
|
*
|
||
|
* 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 <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||
|
#include <gegl.h>
|
||
|
|
||
|
#include "libpikamath/pikamath.h"
|
||
|
|
||
|
extern "C"
|
||
|
{
|
||
|
|
||
|
#include "core-types.h"
|
||
|
|
||
|
#include "gegl/pika-gegl-loops.h"
|
||
|
|
||
|
#include "pikabrush.h"
|
||
|
#include "pikabrush-mipmap.h"
|
||
|
#include "pikabrush-transform.h"
|
||
|
#include "pikatempbuf.h"
|
||
|
|
||
|
|
||
|
#define PIXELS_PER_THREAD \
|
||
|
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
|
||
|
|
||
|
|
||
|
/* local function prototypes */
|
||
|
|
||
|
static void pika_brush_transform_bounding_box (const PikaTempBuf *temp_buf,
|
||
|
const PikaMatrix3 *matrix,
|
||
|
gint *x,
|
||
|
gint *y,
|
||
|
gint *width,
|
||
|
gint *height);
|
||
|
|
||
|
static void pika_brush_transform_blur (PikaTempBuf *buf,
|
||
|
gint r);
|
||
|
static gint pika_brush_transform_blur_radius (gint height,
|
||
|
gint width,
|
||
|
gdouble hardness);
|
||
|
static void pika_brush_transform_adjust_hardness_matrix (gdouble width,
|
||
|
gdouble height,
|
||
|
gdouble blur_radius,
|
||
|
PikaMatrix3 *matrix);
|
||
|
|
||
|
|
||
|
/* public functions */
|
||
|
|
||
|
void
|
||
|
pika_brush_real_transform_size (PikaBrush *brush,
|
||
|
gdouble scale,
|
||
|
gdouble aspect_ratio,
|
||
|
gdouble angle,
|
||
|
gboolean reflect,
|
||
|
gint *width,
|
||
|
gint *height)
|
||
|
{
|
||
|
const PikaTempBuf *source;
|
||
|
PikaMatrix3 matrix;
|
||
|
gdouble scale_x, scale_y;
|
||
|
gint x, y;
|
||
|
|
||
|
pika_brush_transform_get_scale (scale, aspect_ratio,
|
||
|
&scale_x, &scale_y);
|
||
|
|
||
|
source = pika_brush_mipmap_get_mask (brush, &scale_x, &scale_y);
|
||
|
|
||
|
pika_brush_transform_matrix (pika_temp_buf_get_width (source),
|
||
|
pika_temp_buf_get_height (source),
|
||
|
scale_x, scale_y, angle, reflect, &matrix);
|
||
|
|
||
|
pika_brush_transform_bounding_box (source, &matrix, &x, &y, width, height);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Transforms the brush mask with bilinear interpolation.
|
||
|
*
|
||
|
* Rather than calculating the inverse transform for each point in the
|
||
|
* transformed image, this algorithm uses the inverse transformed
|
||
|
* corner points of the destination image to work out the starting
|
||
|
* position in the source image and the U and V deltas in the source
|
||
|
* image space. It then uses a scan-line approach, looping through
|
||
|
* rows and columns in the transformed (destination) image while
|
||
|
* walking along the corresponding rows and columns (named U and V) in
|
||
|
* the source image.
|
||
|
*
|
||
|
* The horizontal in destination space (transform result) is reverse
|
||
|
* transformed into source image space to get U. The vertical in
|
||
|
* destination space (transform result) is reverse transformed into
|
||
|
* source image space to get V.
|
||
|
*
|
||
|
* The strength of this particular algorithm is that calculation work
|
||
|
* should depend more upon the final transformed brush size rather
|
||
|
* than the input brush size.
|
||
|
*
|
||
|
* There are no floating point calculations in the inner loop for speed.
|
||
|
*
|
||
|
* Some variables end with the suffix _i to indicate they have been
|
||
|
* premultiplied by int_multiple
|
||
|
*/
|
||
|
PikaTempBuf *
|
||
|
pika_brush_real_transform_mask (PikaBrush *brush,
|
||
|
gdouble scale,
|
||
|
gdouble aspect_ratio,
|
||
|
gdouble angle,
|
||
|
gboolean reflect,
|
||
|
gdouble hardness)
|
||
|
{
|
||
|
PikaTempBuf *result;
|
||
|
const PikaTempBuf *source;
|
||
|
const guchar *src;
|
||
|
PikaMatrix3 matrix;
|
||
|
gdouble scale_x, scale_y;
|
||
|
gint src_width;
|
||
|
gint src_height;
|
||
|
gint src_width_minus_one;
|
||
|
gint src_height_minus_one;
|
||
|
gint dest_width;
|
||
|
gint dest_height;
|
||
|
gint blur_radius;
|
||
|
gint x, y;
|
||
|
gdouble b_lx, b_rx, t_lx, t_rx;
|
||
|
gdouble b_ly, b_ry, t_ly, t_ry;
|
||
|
gdouble src_tl_to_tr_delta_x;
|
||
|
gdouble src_tl_to_tr_delta_y;
|
||
|
gdouble src_tl_to_bl_delta_x;
|
||
|
gdouble src_tl_to_bl_delta_y;
|
||
|
gint src_walk_ux_i;
|
||
|
gint src_walk_uy_i;
|
||
|
gint src_walk_vx_i;
|
||
|
gint src_walk_vy_i;
|
||
|
gint src_x_min_i;
|
||
|
gint src_y_min_i;
|
||
|
gint src_x_max_i;
|
||
|
gint src_y_max_i;
|
||
|
|
||
|
/*
|
||
|
* tl, tr etc are used because it is easier to visualize top left,
|
||
|
* top right etc corners of the forward transformed source image
|
||
|
* rectangle.
|
||
|
*/
|
||
|
const gint fraction_bits = 12;
|
||
|
const gint int_multiple = pow (2, fraction_bits);
|
||
|
|
||
|
/* In inner loop's bilinear calculation, two numbers that were each
|
||
|
* previously multiplied by int_multiple are multiplied together.
|
||
|
* To get back the right result, the multiplication result must be
|
||
|
* divided *twice* by 2^fraction_bits, equivalent to bit shift right
|
||
|
* by 2 * fraction_bits
|
||
|
*/
|
||
|
const gint recovery_bits = 2 * fraction_bits;
|
||
|
|
||
|
/*
|
||
|
* example: suppose fraction_bits = 9
|
||
|
* a 9-bit mask looks like this: 0001 1111 1111
|
||
|
* and is given by: 2^fraction_bits - 1
|
||
|
* demonstration:
|
||
|
* 2^0 = 0000 0000 0001
|
||
|
* 2^1 = 0000 0000 0010
|
||
|
* :
|
||
|
* 2^8 = 0001 0000 0000
|
||
|
* 2^9 = 0010 0000 0000
|
||
|
* 2^9 - 1 = 0001 1111 1111
|
||
|
*/
|
||
|
const guint fraction_bitmask = pow(2, fraction_bits) - 1 ;
|
||
|
|
||
|
pika_brush_transform_get_scale (scale, aspect_ratio,
|
||
|
&scale_x, &scale_y);
|
||
|
|
||
|
source = pika_brush_mipmap_get_mask (brush, &scale_x, &scale_y);
|
||
|
|
||
|
src_width = pika_temp_buf_get_width (source);
|
||
|
src_height = pika_temp_buf_get_height (source);
|
||
|
|
||
|
pika_brush_transform_matrix (src_width, src_height,
|
||
|
scale_x, scale_y, angle, reflect, &matrix);
|
||
|
|
||
|
if (pika_matrix3_is_identity (&matrix) && hardness == 1.0)
|
||
|
return pika_temp_buf_copy (source);
|
||
|
|
||
|
src_width_minus_one = src_width - 1;
|
||
|
src_height_minus_one = src_height - 1;
|
||
|
|
||
|
pika_brush_transform_bounding_box (source, &matrix,
|
||
|
&x, &y, &dest_width, &dest_height);
|
||
|
|
||
|
blur_radius = 0;
|
||
|
|
||
|
if (hardness < 1.0)
|
||
|
{
|
||
|
PikaMatrix3 unrotated_matrix;
|
||
|
gint unrotated_x;
|
||
|
gint unrotated_y;
|
||
|
gint unrotated_dest_width;
|
||
|
gint unrotated_dest_height;
|
||
|
|
||
|
pika_brush_transform_matrix (src_width, src_height,
|
||
|
scale_x, scale_y, 0.0, FALSE,
|
||
|
&unrotated_matrix);
|
||
|
|
||
|
pika_brush_transform_bounding_box (source, &unrotated_matrix,
|
||
|
&unrotated_x, &unrotated_y,
|
||
|
&unrotated_dest_width,
|
||
|
&unrotated_dest_height);
|
||
|
|
||
|
blur_radius = pika_brush_transform_blur_radius (unrotated_dest_width,
|
||
|
unrotated_dest_height,
|
||
|
hardness);
|
||
|
|
||
|
pika_brush_transform_adjust_hardness_matrix (dest_width, dest_height,
|
||
|
blur_radius, &matrix);
|
||
|
}
|
||
|
|
||
|
pika_matrix3_translate (&matrix, -x, -y);
|
||
|
pika_matrix3_invert (&matrix);
|
||
|
pika_matrix3_translate (&matrix, -0.5, -0.5);
|
||
|
|
||
|
result = pika_temp_buf_new (dest_width, dest_height,
|
||
|
pika_temp_buf_get_format (source));
|
||
|
|
||
|
src = pika_temp_buf_get_data (source);
|
||
|
|
||
|
/* prevent disappearance of 1x1 pixel brush at some rotations when
|
||
|
scaling < 1 */
|
||
|
/*
|
||
|
if (src_width == 1 && src_height == 1 && scale_x < 1 && scale_y < 1 )
|
||
|
{
|
||
|
*dest = src[0];
|
||
|
return result;
|
||
|
}*/
|
||
|
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
0.5, 0.5,
|
||
|
&t_lx, &t_ly);
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
dest_width - 0.5, 0.5,
|
||
|
&t_rx, &t_ry);
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
0.5, dest_height - 0.5,
|
||
|
&b_lx, &b_ly);
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
dest_width - 0.5, dest_height - 0.5,
|
||
|
&b_rx, &b_ry);
|
||
|
|
||
|
/* in image space, calc U (what was horizontal originally)
|
||
|
* note: double precision
|
||
|
*/
|
||
|
src_tl_to_tr_delta_x = t_rx - t_lx;
|
||
|
src_tl_to_tr_delta_y = t_ry - t_ly;
|
||
|
|
||
|
/* in image space, calc V (what was vertical originally)
|
||
|
* note: double precision
|
||
|
*/
|
||
|
src_tl_to_bl_delta_x = b_lx - t_lx;
|
||
|
src_tl_to_bl_delta_y = b_ly - t_ly;
|
||
|
|
||
|
/* speed optimized, note conversion to int precision */
|
||
|
src_walk_ux_i = (gint) ((src_tl_to_tr_delta_x / MAX (dest_width - 1, 1)) *
|
||
|
int_multiple);
|
||
|
src_walk_uy_i = (gint) ((src_tl_to_tr_delta_y / MAX (dest_width - 1, 1)) *
|
||
|
int_multiple);
|
||
|
src_walk_vx_i = (gint) ((src_tl_to_bl_delta_x / MAX (dest_height - 1, 1)) *
|
||
|
int_multiple);
|
||
|
src_walk_vy_i = (gint) ((src_tl_to_bl_delta_y / MAX (dest_height - 1, 1)) *
|
||
|
int_multiple);
|
||
|
|
||
|
src_x_min_i = -int_multiple / 2;
|
||
|
src_y_min_i = -int_multiple / 2;
|
||
|
|
||
|
src_x_max_i = src_width * int_multiple - int_multiple / 2;
|
||
|
src_y_max_i = src_height * int_multiple - int_multiple / 2;
|
||
|
|
||
|
gegl_parallel_distribute_area (
|
||
|
GEGL_RECTANGLE (0, 0, dest_width, dest_height), PIXELS_PER_THREAD,
|
||
|
[=] (const GeglRectangle *area)
|
||
|
{
|
||
|
guchar *dest;
|
||
|
gint src_space_cur_pos_x;
|
||
|
gint src_space_cur_pos_y;
|
||
|
gint src_space_cur_pos_x_i;
|
||
|
gint src_space_cur_pos_y_i;
|
||
|
gint src_space_row_start_x_i;
|
||
|
gint src_space_row_start_y_i;
|
||
|
const guchar *src_walker;
|
||
|
const guchar *pixel_next;
|
||
|
const guchar *pixel_below;
|
||
|
const guchar *pixel_below_next;
|
||
|
gint opposite_x, distance_from_true_x;
|
||
|
gint opposite_y, distance_from_true_y;
|
||
|
gint u, v;
|
||
|
|
||
|
dest = pika_temp_buf_get_data (result) +
|
||
|
dest_width * area->y + area->x;
|
||
|
|
||
|
/* initialize current position in source space to the start position (tl)
|
||
|
* speed optimized, note conversion to int precision
|
||
|
*/
|
||
|
src_space_row_start_x_i = (gint) (t_lx * int_multiple) +
|
||
|
src_walk_vx_i * area->y +
|
||
|
src_walk_ux_i * area->x;
|
||
|
src_space_row_start_y_i = (gint) (t_ly * int_multiple) +
|
||
|
src_walk_vy_i * area->y +
|
||
|
src_walk_uy_i * area->x;
|
||
|
|
||
|
for (v = 0; v < area->height; v++)
|
||
|
{
|
||
|
src_space_cur_pos_x_i = src_space_row_start_x_i;
|
||
|
src_space_cur_pos_y_i = src_space_row_start_y_i;
|
||
|
|
||
|
for (u = 0; u < area->width; u++)
|
||
|
{
|
||
|
if (src_space_cur_pos_x_i < src_x_min_i ||
|
||
|
src_space_cur_pos_x_i >= src_x_max_i ||
|
||
|
src_space_cur_pos_y_i < src_y_min_i ||
|
||
|
src_space_cur_pos_y_i >= src_y_max_i)
|
||
|
/* no corresponding pixel in source space */
|
||
|
{
|
||
|
*dest = 0;
|
||
|
}
|
||
|
else /* reverse transformed point hits source pixel */
|
||
|
{
|
||
|
src_space_cur_pos_x = src_space_cur_pos_x_i >> fraction_bits;
|
||
|
src_space_cur_pos_y = src_space_cur_pos_y_i >> fraction_bits;
|
||
|
|
||
|
src_walker = src +
|
||
|
src_space_cur_pos_y * src_width +
|
||
|
src_space_cur_pos_x;
|
||
|
|
||
|
pixel_next = src_walker + 1;
|
||
|
pixel_below = src_walker + src_width;
|
||
|
pixel_below_next = pixel_below + 1;
|
||
|
|
||
|
if (src_space_cur_pos_x < 0)
|
||
|
{
|
||
|
src_walker = pixel_next;
|
||
|
pixel_below = pixel_below_next;
|
||
|
}
|
||
|
else if (src_space_cur_pos_x >= src_width_minus_one)
|
||
|
{
|
||
|
pixel_next = src_walker;
|
||
|
pixel_below_next = pixel_below;
|
||
|
}
|
||
|
|
||
|
if (src_space_cur_pos_y < 0)
|
||
|
{
|
||
|
src_walker = pixel_below;
|
||
|
pixel_next = pixel_below_next;
|
||
|
}
|
||
|
else if (src_space_cur_pos_y >= src_height_minus_one)
|
||
|
{
|
||
|
pixel_below = src_walker;
|
||
|
pixel_below_next = pixel_next;
|
||
|
}
|
||
|
|
||
|
distance_from_true_x = src_space_cur_pos_x_i & fraction_bitmask;
|
||
|
distance_from_true_y = src_space_cur_pos_y_i & fraction_bitmask;
|
||
|
opposite_x = int_multiple - distance_from_true_x;
|
||
|
opposite_y = int_multiple - distance_from_true_y;
|
||
|
|
||
|
*dest = ((src_walker[0] * opposite_x +
|
||
|
pixel_next[0] * distance_from_true_x) * opposite_y +
|
||
|
(pixel_below[0] * opposite_x +
|
||
|
pixel_below_next[0] *distance_from_true_x) * distance_from_true_y
|
||
|
) >> recovery_bits;
|
||
|
}
|
||
|
|
||
|
src_space_cur_pos_x_i += src_walk_ux_i;
|
||
|
src_space_cur_pos_y_i += src_walk_uy_i;
|
||
|
|
||
|
dest++;
|
||
|
} /* end for x */
|
||
|
|
||
|
src_space_row_start_x_i += src_walk_vx_i;
|
||
|
src_space_row_start_y_i += src_walk_vy_i;
|
||
|
|
||
|
dest += dest_width - area->width;
|
||
|
} /* end for y */
|
||
|
});
|
||
|
|
||
|
pika_brush_transform_blur (result, blur_radius);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Transforms the brush pixmap with bilinear interpolation.
|
||
|
*
|
||
|
* The algorithm used is exactly the same as for the brush mask
|
||
|
* (pika_brush_real_transform_mask) except it accounts for 3 color channels
|
||
|
* instead of 1 grayscale channel.
|
||
|
*
|
||
|
* Rather than calculating the inverse transform for each point in the
|
||
|
* transformed image, this algorithm uses the inverse transformed
|
||
|
* corner points of the destination image to work out the starting
|
||
|
* position in the source image and the U and V deltas in the source
|
||
|
* image space. It then uses a scan-line approach, looping through
|
||
|
* rows and columns in the transformed (destination) image while
|
||
|
* walking along the corresponding rows and columns (named U and V) in
|
||
|
* the source image.
|
||
|
*
|
||
|
* The horizontal in destination space (transform result) is reverse
|
||
|
* transformed into source image space to get U. The vertical in
|
||
|
* destination space (transform result) is reverse transformed into
|
||
|
* source image space to get V.
|
||
|
*
|
||
|
* The strength of this particular algorithm is that calculation work
|
||
|
* should depend more upon the final transformed brush size rather
|
||
|
* than the input brush size.
|
||
|
*
|
||
|
* There are no floating point calculations in the inner loop for speed.
|
||
|
*
|
||
|
* Some variables end with the suffix _i to indicate they have been
|
||
|
* premultiplied by int_multiple
|
||
|
*/
|
||
|
PikaTempBuf *
|
||
|
pika_brush_real_transform_pixmap (PikaBrush *brush,
|
||
|
gdouble scale,
|
||
|
gdouble aspect_ratio,
|
||
|
gdouble angle,
|
||
|
gboolean reflect,
|
||
|
gdouble hardness)
|
||
|
{
|
||
|
PikaTempBuf *result;
|
||
|
const PikaTempBuf *source;
|
||
|
const guchar *src;
|
||
|
PikaMatrix3 matrix;
|
||
|
gdouble scale_x, scale_y;
|
||
|
gint src_width;
|
||
|
gint src_height;
|
||
|
gint src_width_minus_one;
|
||
|
gint src_height_minus_one;
|
||
|
gint dest_width;
|
||
|
gint dest_height;
|
||
|
gint blur_radius;
|
||
|
gint x, y;
|
||
|
gdouble b_lx, b_rx, t_lx, t_rx;
|
||
|
gdouble b_ly, b_ry, t_ly, t_ry;
|
||
|
gdouble src_tl_to_tr_delta_x;
|
||
|
gdouble src_tl_to_tr_delta_y;
|
||
|
gdouble src_tl_to_bl_delta_x;
|
||
|
gdouble src_tl_to_bl_delta_y;
|
||
|
gint src_walk_ux_i;
|
||
|
gint src_walk_uy_i;
|
||
|
gint src_walk_vx_i;
|
||
|
gint src_walk_vy_i;
|
||
|
gint src_x_min_i;
|
||
|
gint src_y_min_i;
|
||
|
gint src_x_max_i;
|
||
|
gint src_y_max_i;
|
||
|
|
||
|
/*
|
||
|
* tl, tr etc are used because it is easier to visualize top left,
|
||
|
* top right etc corners of the forward transformed source image
|
||
|
* rectangle.
|
||
|
*/
|
||
|
const gint fraction_bits = 12;
|
||
|
const gint int_multiple = pow (2, fraction_bits);
|
||
|
|
||
|
/* In inner loop's bilinear calculation, two numbers that were each
|
||
|
* previously multiplied by int_multiple are multiplied together.
|
||
|
* To get back the right result, the multiplication result must be
|
||
|
* divided *twice* by 2^fraction_bits, equivalent to bit shift right
|
||
|
* by 2 * fraction_bits
|
||
|
*/
|
||
|
const gint recovery_bits = 2 * fraction_bits;
|
||
|
|
||
|
/*
|
||
|
* example: suppose fraction_bits = 9
|
||
|
* a 9-bit mask looks like this: 0001 1111 1111
|
||
|
* and is given by: 2^fraction_bits - 1
|
||
|
* demonstration:
|
||
|
* 2^0 = 0000 0000 0001
|
||
|
* 2^1 = 0000 0000 0010
|
||
|
* :
|
||
|
* 2^8 = 0001 0000 0000
|
||
|
* 2^9 = 0010 0000 0000
|
||
|
* 2^9 - 1 = 0001 1111 1111
|
||
|
*/
|
||
|
const guint fraction_bitmask = pow(2, fraction_bits) - 1 ;
|
||
|
|
||
|
pika_brush_transform_get_scale (scale, aspect_ratio,
|
||
|
&scale_x, &scale_y);
|
||
|
|
||
|
source = pika_brush_mipmap_get_pixmap (brush, &scale_x, &scale_y);
|
||
|
|
||
|
src_width = pika_temp_buf_get_width (source);
|
||
|
src_height = pika_temp_buf_get_height (source);
|
||
|
|
||
|
pika_brush_transform_matrix (src_width, src_height,
|
||
|
scale_x, scale_y, angle, reflect, &matrix);
|
||
|
|
||
|
if (pika_matrix3_is_identity (&matrix) && hardness == 1.0)
|
||
|
return pika_temp_buf_copy (source);
|
||
|
|
||
|
src_width_minus_one = src_width - 1;
|
||
|
src_height_minus_one = src_height - 1;
|
||
|
|
||
|
pika_brush_transform_bounding_box (source, &matrix,
|
||
|
&x, &y, &dest_width, &dest_height);
|
||
|
|
||
|
blur_radius = 0;
|
||
|
|
||
|
if (hardness < 1.0)
|
||
|
{
|
||
|
PikaMatrix3 unrotated_matrix;
|
||
|
gint unrotated_x;
|
||
|
gint unrotated_y;
|
||
|
gint unrotated_dest_width;
|
||
|
gint unrotated_dest_height;
|
||
|
|
||
|
pika_brush_transform_matrix (src_width, src_height,
|
||
|
scale_x, scale_y, 0.0, FALSE,
|
||
|
&unrotated_matrix);
|
||
|
|
||
|
pika_brush_transform_bounding_box (source, &unrotated_matrix,
|
||
|
&unrotated_x, &unrotated_y,
|
||
|
&unrotated_dest_width,
|
||
|
&unrotated_dest_height);
|
||
|
|
||
|
blur_radius = pika_brush_transform_blur_radius (unrotated_dest_width,
|
||
|
unrotated_dest_height,
|
||
|
hardness);
|
||
|
|
||
|
pika_brush_transform_adjust_hardness_matrix (dest_width, dest_height,
|
||
|
blur_radius, &matrix);
|
||
|
}
|
||
|
|
||
|
pika_matrix3_translate (&matrix, -x, -y);
|
||
|
pika_matrix3_invert (&matrix);
|
||
|
pika_matrix3_translate (&matrix, -0.5, -0.5);
|
||
|
|
||
|
result = pika_temp_buf_new (dest_width, dest_height,
|
||
|
pika_temp_buf_get_format (source));
|
||
|
|
||
|
src = pika_temp_buf_get_data (source);
|
||
|
|
||
|
/* prevent disappearance of 1x1 pixel brush at some rotations when
|
||
|
scaling < 1 */
|
||
|
/*
|
||
|
if (src_width == 1 && src_height == 1 && scale_x < 1 && scale_y < 1 )
|
||
|
{
|
||
|
*dest = src[0];
|
||
|
return result;
|
||
|
}*/
|
||
|
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
0.5, 0.5,
|
||
|
&t_lx, &t_ly);
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
dest_width - 0.5, 0.5,
|
||
|
&t_rx, &t_ry);
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
0.5, dest_height - 0.5,
|
||
|
&b_lx, &b_ly);
|
||
|
pika_matrix3_transform_point (&matrix,
|
||
|
dest_width - 0.5, dest_height - 0.5,
|
||
|
&b_rx, &b_ry);
|
||
|
|
||
|
/* in image space, calc U (what was horizontal originally)
|
||
|
* note: double precision
|
||
|
*/
|
||
|
src_tl_to_tr_delta_x = t_rx - t_lx;
|
||
|
src_tl_to_tr_delta_y = t_ry - t_ly;
|
||
|
|
||
|
/* in image space, calc V (what was vertical originally)
|
||
|
* note: double precision
|
||
|
*/
|
||
|
src_tl_to_bl_delta_x = b_lx - t_lx;
|
||
|
src_tl_to_bl_delta_y = b_ly - t_ly;
|
||
|
|
||
|
/* speed optimized, note conversion to int precision */
|
||
|
src_walk_ux_i = (gint) ((src_tl_to_tr_delta_x / MAX (dest_width - 1, 1)) *
|
||
|
int_multiple);
|
||
|
src_walk_uy_i = (gint) ((src_tl_to_tr_delta_y / MAX (dest_width - 1, 1)) *
|
||
|
int_multiple);
|
||
|
src_walk_vx_i = (gint) ((src_tl_to_bl_delta_x / MAX (dest_height - 1, 1)) *
|
||
|
int_multiple);
|
||
|
src_walk_vy_i = (gint) ((src_tl_to_bl_delta_y / MAX (dest_height - 1, 1)) *
|
||
|
int_multiple);
|
||
|
|
||
|
src_x_min_i = -int_multiple / 2;
|
||
|
src_y_min_i = -int_multiple / 2;
|
||
|
|
||
|
src_x_max_i = src_width * int_multiple - int_multiple / 2;
|
||
|
src_y_max_i = src_height * int_multiple - int_multiple / 2;
|
||
|
|
||
|
gegl_parallel_distribute_area (
|
||
|
GEGL_RECTANGLE (0, 0, dest_width, dest_height), PIXELS_PER_THREAD,
|
||
|
[=] (const GeglRectangle *area)
|
||
|
{
|
||
|
guchar *dest;
|
||
|
gint src_space_cur_pos_x;
|
||
|
gint src_space_cur_pos_y;
|
||
|
gint src_space_cur_pos_x_i;
|
||
|
gint src_space_cur_pos_y_i;
|
||
|
gint src_space_row_start_x_i;
|
||
|
gint src_space_row_start_y_i;
|
||
|
const guchar *src_walker;
|
||
|
const guchar *pixel_next;
|
||
|
const guchar *pixel_below;
|
||
|
const guchar *pixel_below_next;
|
||
|
gint opposite_x, distance_from_true_x;
|
||
|
gint opposite_y, distance_from_true_y;
|
||
|
gint u, v;
|
||
|
|
||
|
dest = pika_temp_buf_get_data (result) +
|
||
|
3 * (dest_width * area->y + area->x);
|
||
|
|
||
|
/* initialize current position in source space to the start position (tl)
|
||
|
* speed optimized, note conversion to int precision
|
||
|
*/
|
||
|
src_space_row_start_x_i = (gint) (t_lx * int_multiple) +
|
||
|
src_walk_vx_i * area->y +
|
||
|
src_walk_ux_i * area->x;
|
||
|
src_space_row_start_y_i = (gint) (t_ly * int_multiple) +
|
||
|
src_walk_vy_i * area->y +
|
||
|
src_walk_uy_i * area->x;
|
||
|
|
||
|
for (v = 0; v < area->height; v++)
|
||
|
{
|
||
|
src_space_cur_pos_x_i = src_space_row_start_x_i;
|
||
|
src_space_cur_pos_y_i = src_space_row_start_y_i;
|
||
|
|
||
|
for (u = 0; u < area->width; u++)
|
||
|
{
|
||
|
if (src_space_cur_pos_x_i < src_x_min_i ||
|
||
|
src_space_cur_pos_x_i >= src_x_max_i ||
|
||
|
src_space_cur_pos_y_i < src_y_min_i ||
|
||
|
src_space_cur_pos_y_i >= src_y_max_i)
|
||
|
/* no corresponding pixel in source space */
|
||
|
{
|
||
|
dest[0] = 0;
|
||
|
dest[1] = 0;
|
||
|
dest[2] = 0;
|
||
|
}
|
||
|
else /* reverse transformed point hits source pixel */
|
||
|
{
|
||
|
src_space_cur_pos_x = src_space_cur_pos_x_i >> fraction_bits;
|
||
|
src_space_cur_pos_y = src_space_cur_pos_y_i >> fraction_bits;
|
||
|
|
||
|
src_walker = src +
|
||
|
3 * (src_space_cur_pos_y * src_width +
|
||
|
src_space_cur_pos_x);
|
||
|
|
||
|
pixel_next = src_walker + 3;
|
||
|
pixel_below = src_walker + 3 * src_width;
|
||
|
pixel_below_next = pixel_below + 3;
|
||
|
|
||
|
if (src_space_cur_pos_x < 0)
|
||
|
{
|
||
|
src_walker = pixel_next;
|
||
|
pixel_below = pixel_below_next;
|
||
|
}
|
||
|
else if (src_space_cur_pos_x >= src_width_minus_one)
|
||
|
{
|
||
|
pixel_next = src_walker;
|
||
|
pixel_below_next = pixel_below;
|
||
|
}
|
||
|
|
||
|
if (src_space_cur_pos_y < 0)
|
||
|
{
|
||
|
src_walker = pixel_below;
|
||
|
pixel_next = pixel_below_next;
|
||
|
}
|
||
|
else if (src_space_cur_pos_y >= src_height_minus_one)
|
||
|
{
|
||
|
pixel_below = src_walker;
|
||
|
pixel_below_next = pixel_next;
|
||
|
}
|
||
|
|
||
|
distance_from_true_x = src_space_cur_pos_x_i & fraction_bitmask;
|
||
|
distance_from_true_y = src_space_cur_pos_y_i & fraction_bitmask;
|
||
|
opposite_x = int_multiple - distance_from_true_x;
|
||
|
opposite_y = int_multiple - distance_from_true_y;
|
||
|
|
||
|
dest[0] = ((src_walker[0] * opposite_x +
|
||
|
pixel_next[0] * distance_from_true_x) * opposite_y +
|
||
|
(pixel_below[0] * opposite_x +
|
||
|
pixel_below_next[0] *distance_from_true_x) * distance_from_true_y
|
||
|
) >> recovery_bits;
|
||
|
|
||
|
dest[1] = ((src_walker[1] * opposite_x +
|
||
|
pixel_next[1] * distance_from_true_x) * opposite_y +
|
||
|
(pixel_below[1] * opposite_x +
|
||
|
pixel_below_next[1] *distance_from_true_x) * distance_from_true_y
|
||
|
) >> recovery_bits;
|
||
|
|
||
|
dest[2] = ((src_walker[2] * opposite_x +
|
||
|
pixel_next[2] * distance_from_true_x) * opposite_y +
|
||
|
(pixel_below[2] * opposite_x +
|
||
|
pixel_below_next[2] *distance_from_true_x) * distance_from_true_y
|
||
|
) >> recovery_bits;
|
||
|
}
|
||
|
|
||
|
src_space_cur_pos_x_i += src_walk_ux_i;
|
||
|
src_space_cur_pos_y_i += src_walk_uy_i;
|
||
|
|
||
|
dest += 3;
|
||
|
} /* end for x */
|
||
|
|
||
|
src_space_row_start_x_i += src_walk_vx_i;
|
||
|
src_space_row_start_y_i += src_walk_vy_i;
|
||
|
|
||
|
dest += 3 * (dest_width - area->width);
|
||
|
} /* end for y */
|
||
|
});
|
||
|
|
||
|
pika_brush_transform_blur (result, blur_radius);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
pika_brush_transform_get_scale (gdouble scale,
|
||
|
gdouble aspect_ratio,
|
||
|
gdouble *scale_x,
|
||
|
gdouble *scale_y)
|
||
|
{
|
||
|
if (aspect_ratio < 0.0)
|
||
|
{
|
||
|
*scale_x = scale * (1.0 + (aspect_ratio / 20.0));
|
||
|
*scale_y = scale;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*scale_x = scale;
|
||
|
*scale_y = scale * (1.0 - (aspect_ratio / 20.0));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
pika_brush_transform_matrix (gdouble width,
|
||
|
gdouble height,
|
||
|
gdouble scale_x,
|
||
|
gdouble scale_y,
|
||
|
gdouble angle,
|
||
|
gboolean reflect,
|
||
|
PikaMatrix3 *matrix)
|
||
|
{
|
||
|
const gdouble center_x = width / 2;
|
||
|
const gdouble center_y = height / 2;
|
||
|
|
||
|
pika_matrix3_identity (matrix);
|
||
|
pika_matrix3_scale (matrix, scale_x, scale_y);
|
||
|
pika_matrix3_translate (matrix, - center_x * scale_x, - center_y * scale_y);
|
||
|
pika_matrix3_rotate (matrix, -2 * G_PI * angle);
|
||
|
if (reflect)
|
||
|
pika_matrix3_scale (matrix, -1.0, 1.0);
|
||
|
pika_matrix3_translate (matrix, center_x * scale_x, center_y * scale_y);
|
||
|
}
|
||
|
|
||
|
/* private functions */
|
||
|
|
||
|
static void
|
||
|
pika_brush_transform_bounding_box (const PikaTempBuf *temp_buf,
|
||
|
const PikaMatrix3 *matrix,
|
||
|
gint *x,
|
||
|
gint *y,
|
||
|
gint *width,
|
||
|
gint *height)
|
||
|
{
|
||
|
const gdouble w = pika_temp_buf_get_width (temp_buf);
|
||
|
const gdouble h = pika_temp_buf_get_height (temp_buf);
|
||
|
gdouble x1, x2, x3, x4;
|
||
|
gdouble y1, y2, y3, y4;
|
||
|
|
||
|
pika_matrix3_transform_point (matrix, 0, 0, &x1, &y1);
|
||
|
pika_matrix3_transform_point (matrix, w, 0, &x2, &y2);
|
||
|
pika_matrix3_transform_point (matrix, 0, h, &x3, &y3);
|
||
|
pika_matrix3_transform_point (matrix, w, h, &x4, &y4);
|
||
|
|
||
|
*x = (gint) ceil (MIN (MIN (x1, x2), MIN (x3, x4)) - 0.5);
|
||
|
*y = (gint) ceil (MIN (MIN (y1, y2), MIN (y3, y4)) - 0.5);
|
||
|
|
||
|
*width = (gint) ceil (MAX (MAX (x1, x2), MAX (x3, x4)) - 0.5) - *x;
|
||
|
*height = (gint) ceil (MAX (MAX (y1, y2), MAX (y3, y4)) - 0.5) - *y;
|
||
|
|
||
|
/* Transform size can not be less than 1 px */
|
||
|
*width = MAX (1, *width);
|
||
|
*height = MAX (1, *height);
|
||
|
}
|
||
|
|
||
|
/* Blurs the brush mask/pixmap, in place, using a convolution of the form:
|
||
|
*
|
||
|
* 12 11 10 9 8
|
||
|
* 7 6 5 4 3
|
||
|
* 2 1 0 1 2
|
||
|
* 3 4 5 6 7
|
||
|
* 8 9 10 11 12
|
||
|
*
|
||
|
* (i.e., an array, wrapped into a matrix, whose i-th element is
|
||
|
* `abs (i - a / 2)`, where `a` is the length of the array.) `r` specifies the
|
||
|
* convolution kernel's radius.
|
||
|
*/
|
||
|
static void
|
||
|
pika_brush_transform_blur (PikaTempBuf *buf,
|
||
|
gint r)
|
||
|
{
|
||
|
typedef struct
|
||
|
{
|
||
|
gint sum;
|
||
|
gint weighted_sum;
|
||
|
gint middle_sum;
|
||
|
} Sums;
|
||
|
|
||
|
const Babl *format = pika_temp_buf_get_format (buf);
|
||
|
gint components = babl_format_get_n_components (format);
|
||
|
gint components_r = components * r;
|
||
|
gint width = pika_temp_buf_get_width (buf);
|
||
|
gint height = pika_temp_buf_get_height (buf);
|
||
|
gint stride = components * width;
|
||
|
gint stride_r = stride * r;
|
||
|
guchar *data = pika_temp_buf_get_data (buf);
|
||
|
gint rw = MIN (r, width - 1);
|
||
|
gint rh = MIN (r, height - 1);
|
||
|
gfloat n = 2 * r + 1;
|
||
|
gfloat n_r = n * r;
|
||
|
gfloat weight = floor (n * n / 2) * (floor (n * n / 2) + 1);
|
||
|
gfloat weight_inv = 1 / weight;
|
||
|
Sums *sums;
|
||
|
|
||
|
if (rw <= 0 || rh <= 0)
|
||
|
return;
|
||
|
|
||
|
sums = g_new (Sums, width * height * components);
|
||
|
|
||
|
gegl_parallel_distribute_range (
|
||
|
height, PIXELS_PER_THREAD / width,
|
||
|
[=] (gint y0, gint height)
|
||
|
{
|
||
|
gint x;
|
||
|
gint y;
|
||
|
gint c;
|
||
|
const guchar *d;
|
||
|
Sums *s;
|
||
|
|
||
|
d = data + y0 * stride;
|
||
|
s = sums + y0 * stride;
|
||
|
|
||
|
for (y = 0; y < height; y++)
|
||
|
{
|
||
|
const guchar *p;
|
||
|
|
||
|
struct
|
||
|
{
|
||
|
gint sum;
|
||
|
gint weighted_sum;
|
||
|
gint leading_sum;
|
||
|
gint leading_weighted_sum;
|
||
|
} acc[components];
|
||
|
|
||
|
memset (acc, 0, sizeof (acc));
|
||
|
|
||
|
p = d;
|
||
|
|
||
|
for (x = 0; x <= rw; x++)
|
||
|
{
|
||
|
for (c = 0; c < components; c++)
|
||
|
{
|
||
|
acc[c].sum += *p;
|
||
|
acc[c].weighted_sum += -x * *p;
|
||
|
|
||
|
p++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (x = 0; x < width; x++)
|
||
|
{
|
||
|
for (c = 0; c < components; c++)
|
||
|
{
|
||
|
if (x > 0)
|
||
|
{
|
||
|
acc[c].weighted_sum += acc[c].sum;
|
||
|
acc[c].leading_weighted_sum += acc[c].leading_sum;
|
||
|
|
||
|
if (x < width - r)
|
||
|
{
|
||
|
acc[c].sum += d[components_r];
|
||
|
acc[c].weighted_sum += -r * d[components_r];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
acc[c].leading_sum += d[0];
|
||
|
|
||
|
s->sum = acc[c].sum;
|
||
|
s->weighted_sum = acc[c].weighted_sum;
|
||
|
s->middle_sum = 2 * acc[c].leading_weighted_sum -
|
||
|
acc[c].weighted_sum;
|
||
|
|
||
|
if (x >= r)
|
||
|
{
|
||
|
acc[c].sum -= d[-components_r];
|
||
|
acc[c].weighted_sum -= r * d[-components_r];
|
||
|
acc[c].leading_sum -= d[-components_r];
|
||
|
acc[c].leading_weighted_sum -= r * d[-components_r];
|
||
|
}
|
||
|
|
||
|
d++;
|
||
|
s++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
gegl_parallel_distribute_range (
|
||
|
width, PIXELS_PER_THREAD / height,
|
||
|
[=] (gint x0, gint width)
|
||
|
{
|
||
|
gint x;
|
||
|
gint y;
|
||
|
gint c;
|
||
|
guchar *d0;
|
||
|
const Sums *s0;
|
||
|
guchar *d;
|
||
|
const Sums *s;
|
||
|
|
||
|
d0 = data + x0 * components;
|
||
|
s0 = sums + x0 * components;
|
||
|
|
||
|
for (x = 0; x < width; x++)
|
||
|
{
|
||
|
const Sums *p;
|
||
|
gfloat n_y;
|
||
|
|
||
|
struct
|
||
|
{
|
||
|
gfloat weighted_sum;
|
||
|
gint leading_sum;
|
||
|
gint trailing_sum;
|
||
|
} acc[components];
|
||
|
|
||
|
memset (acc, 0, sizeof (acc));
|
||
|
|
||
|
d = d0 + components * x;
|
||
|
s = s0 + components * x;
|
||
|
|
||
|
p = s + stride;
|
||
|
|
||
|
for (y = 1, n_y = n; y <= rh; y++, n_y += n)
|
||
|
{
|
||
|
for (c = 0; c < components; c++)
|
||
|
{
|
||
|
acc[c].weighted_sum += n_y * p->sum - p->weighted_sum;
|
||
|
acc[c].trailing_sum += p->sum;
|
||
|
|
||
|
p++;
|
||
|
}
|
||
|
|
||
|
p += stride - components;
|
||
|
}
|
||
|
|
||
|
for (y = 0; y < height; y++)
|
||
|
{
|
||
|
for (c = 0; c < components; c++)
|
||
|
{
|
||
|
if (y > 0)
|
||
|
{
|
||
|
acc[c].weighted_sum += s->weighted_sum +
|
||
|
n * (acc[c].leading_sum -
|
||
|
acc[c].trailing_sum);
|
||
|
acc[c].trailing_sum -= s->sum;
|
||
|
|
||
|
if (y < height - r)
|
||
|
{
|
||
|
acc[c].weighted_sum += n_r * s[stride_r].sum -
|
||
|
s[stride_r].weighted_sum;
|
||
|
acc[c].trailing_sum += s[stride_r].sum;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
acc[c].leading_sum += s->sum;
|
||
|
|
||
|
*d = (acc[c].weighted_sum + s->middle_sum) * weight_inv + 0.5f;
|
||
|
|
||
|
acc[c].weighted_sum += s->weighted_sum;
|
||
|
|
||
|
if (y >= r)
|
||
|
{
|
||
|
acc[c].weighted_sum -= n_r * s[-stride_r].sum +
|
||
|
s[-stride_r].weighted_sum;
|
||
|
acc[c].leading_sum -= s[-stride_r].sum;
|
||
|
}
|
||
|
|
||
|
d++;
|
||
|
s++;
|
||
|
}
|
||
|
|
||
|
d += stride - components;
|
||
|
s += stride - components;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
g_free (sums);
|
||
|
}
|
||
|
|
||
|
static gint
|
||
|
pika_brush_transform_blur_radius (gint height,
|
||
|
gint width,
|
||
|
gdouble hardness)
|
||
|
{
|
||
|
return floor ((1.0 - hardness) * (sqrt (0.5) - 0.5) * MIN (width, height));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_brush_transform_adjust_hardness_matrix (gdouble width,
|
||
|
gdouble height,
|
||
|
gdouble blur_radius,
|
||
|
PikaMatrix3 *matrix)
|
||
|
{
|
||
|
gdouble scale;
|
||
|
|
||
|
if (blur_radius == 0.0)
|
||
|
return;
|
||
|
|
||
|
scale = (MIN (width, height) - 2.0 * blur_radius) / MIN (width, height);
|
||
|
|
||
|
pika_matrix3_scale (matrix, scale, scale);
|
||
|
pika_matrix3_translate (matrix,
|
||
|
(1.0 - scale) * width / 2.0,
|
||
|
(1.0 - scale) * height / 2.0);
|
||
|
}
|
||
|
|
||
|
} /* extern "C" */
|