diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
commit | 5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch) | |
tree | cbffb45144febf451e54061db2b21395faf94bfe /app/core/gimpbrush-transform.cc | |
parent | Initial commit. (diff) | |
download | gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip |
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/core/gimpbrush-transform.cc')
-rw-r--r-- | app/core/gimpbrush-transform.cc | 1043 |
1 files changed, 1043 insertions, 0 deletions
diff --git a/app/core/gimpbrush-transform.cc b/app/core/gimpbrush-transform.cc new file mode 100644 index 0000000..45673cd --- /dev/null +++ b/app/core/gimpbrush-transform.cc @@ -0,0 +1,1043 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbrush-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 "libgimpmath/gimpmath.h" + +extern "C" +{ + +#include "core-types.h" + +#include "gegl/gimp-gegl-loops.h" + +#include "gimpbrush.h" +#include "gimpbrush-mipmap.h" +#include "gimpbrush-transform.h" +#include "gimptempbuf.h" + + +#define PIXELS_PER_THREAD \ + (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */) + + +/* local function prototypes */ + +static void gimp_brush_transform_bounding_box (const GimpTempBuf *temp_buf, + const GimpMatrix3 *matrix, + gint *x, + gint *y, + gint *width, + gint *height); + +static void gimp_brush_transform_blur (GimpTempBuf *buf, + gint r); +static gint gimp_brush_transform_blur_radius (gint height, + gint width, + gdouble hardness); +static void gimp_brush_transform_adjust_hardness_matrix (gdouble width, + gdouble height, + gdouble blur_radius, + GimpMatrix3 *matrix); + + +/* public functions */ + +void +gimp_brush_real_transform_size (GimpBrush *brush, + gdouble scale, + gdouble aspect_ratio, + gdouble angle, + gboolean reflect, + gint *width, + gint *height) +{ + const GimpTempBuf *source; + GimpMatrix3 matrix; + gdouble scale_x, scale_y; + gint x, y; + + gimp_brush_transform_get_scale (scale, aspect_ratio, + &scale_x, &scale_y); + + source = gimp_brush_mipmap_get_mask (brush, &scale_x, &scale_y); + + gimp_brush_transform_matrix (gimp_temp_buf_get_width (source), + gimp_temp_buf_get_height (source), + scale_x, scale_y, angle, reflect, &matrix); + + gimp_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 + */ +GimpTempBuf * +gimp_brush_real_transform_mask (GimpBrush *brush, + gdouble scale, + gdouble aspect_ratio, + gdouble angle, + gboolean reflect, + gdouble hardness) +{ + GimpTempBuf *result; + const GimpTempBuf *source; + const guchar *src; + GimpMatrix3 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 ; + + gimp_brush_transform_get_scale (scale, aspect_ratio, + &scale_x, &scale_y); + + source = gimp_brush_mipmap_get_mask (brush, &scale_x, &scale_y); + + src_width = gimp_temp_buf_get_width (source); + src_height = gimp_temp_buf_get_height (source); + + gimp_brush_transform_matrix (src_width, src_height, + scale_x, scale_y, angle, reflect, &matrix); + + if (gimp_matrix3_is_identity (&matrix) && hardness == 1.0) + return gimp_temp_buf_copy (source); + + src_width_minus_one = src_width - 1; + src_height_minus_one = src_height - 1; + + gimp_brush_transform_bounding_box (source, &matrix, + &x, &y, &dest_width, &dest_height); + + blur_radius = 0; + + if (hardness < 1.0) + { + GimpMatrix3 unrotated_matrix; + gint unrotated_x; + gint unrotated_y; + gint unrotated_dest_width; + gint unrotated_dest_height; + + gimp_brush_transform_matrix (src_width, src_height, + scale_x, scale_y, 0.0, FALSE, + &unrotated_matrix); + + gimp_brush_transform_bounding_box (source, &unrotated_matrix, + &unrotated_x, &unrotated_y, + &unrotated_dest_width, + &unrotated_dest_height); + + blur_radius = gimp_brush_transform_blur_radius (unrotated_dest_width, + unrotated_dest_height, + hardness); + + gimp_brush_transform_adjust_hardness_matrix (dest_width, dest_height, + blur_radius, &matrix); + } + + gimp_matrix3_translate (&matrix, -x, -y); + gimp_matrix3_invert (&matrix); + gimp_matrix3_translate (&matrix, -0.5, -0.5); + + result = gimp_temp_buf_new (dest_width, dest_height, + gimp_temp_buf_get_format (source)); + + src = gimp_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; + }*/ + + gimp_matrix3_transform_point (&matrix, + 0.5, 0.5, + &t_lx, &t_ly); + gimp_matrix3_transform_point (&matrix, + dest_width - 0.5, 0.5, + &t_rx, &t_ry); + gimp_matrix3_transform_point (&matrix, + 0.5, dest_height - 0.5, + &b_lx, &b_ly); + gimp_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 = gimp_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 */ + }); + + gimp_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 + * (gimp_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 + */ +GimpTempBuf * +gimp_brush_real_transform_pixmap (GimpBrush *brush, + gdouble scale, + gdouble aspect_ratio, + gdouble angle, + gboolean reflect, + gdouble hardness) +{ + GimpTempBuf *result; + const GimpTempBuf *source; + const guchar *src; + GimpMatrix3 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 ; + + gimp_brush_transform_get_scale (scale, aspect_ratio, + &scale_x, &scale_y); + + source = gimp_brush_mipmap_get_pixmap (brush, &scale_x, &scale_y); + + src_width = gimp_temp_buf_get_width (source); + src_height = gimp_temp_buf_get_height (source); + + gimp_brush_transform_matrix (src_width, src_height, + scale_x, scale_y, angle, reflect, &matrix); + + if (gimp_matrix3_is_identity (&matrix) && hardness == 1.0) + return gimp_temp_buf_copy (source); + + src_width_minus_one = src_width - 1; + src_height_minus_one = src_height - 1; + + gimp_brush_transform_bounding_box (source, &matrix, + &x, &y, &dest_width, &dest_height); + + blur_radius = 0; + + if (hardness < 1.0) + { + GimpMatrix3 unrotated_matrix; + gint unrotated_x; + gint unrotated_y; + gint unrotated_dest_width; + gint unrotated_dest_height; + + gimp_brush_transform_matrix (src_width, src_height, + scale_x, scale_y, 0.0, FALSE, + &unrotated_matrix); + + gimp_brush_transform_bounding_box (source, &unrotated_matrix, + &unrotated_x, &unrotated_y, + &unrotated_dest_width, + &unrotated_dest_height); + + blur_radius = gimp_brush_transform_blur_radius (unrotated_dest_width, + unrotated_dest_height, + hardness); + + gimp_brush_transform_adjust_hardness_matrix (dest_width, dest_height, + blur_radius, &matrix); + } + + gimp_matrix3_translate (&matrix, -x, -y); + gimp_matrix3_invert (&matrix); + gimp_matrix3_translate (&matrix, -0.5, -0.5); + + result = gimp_temp_buf_new (dest_width, dest_height, + gimp_temp_buf_get_format (source)); + + src = gimp_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; + }*/ + + gimp_matrix3_transform_point (&matrix, + 0.5, 0.5, + &t_lx, &t_ly); + gimp_matrix3_transform_point (&matrix, + dest_width - 0.5, 0.5, + &t_rx, &t_ry); + gimp_matrix3_transform_point (&matrix, + 0.5, dest_height - 0.5, + &b_lx, &b_ly); + gimp_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 = gimp_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 */ + }); + + gimp_brush_transform_blur (result, blur_radius); + + return result; +} + +void +gimp_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 +gimp_brush_transform_matrix (gdouble width, + gdouble height, + gdouble scale_x, + gdouble scale_y, + gdouble angle, + gboolean reflect, + GimpMatrix3 *matrix) +{ + const gdouble center_x = width / 2; + const gdouble center_y = height / 2; + + gimp_matrix3_identity (matrix); + gimp_matrix3_scale (matrix, scale_x, scale_y); + gimp_matrix3_translate (matrix, - center_x * scale_x, - center_y * scale_y); + gimp_matrix3_rotate (matrix, -2 * G_PI * angle); + if (reflect) + gimp_matrix3_scale (matrix, -1.0, 1.0); + gimp_matrix3_translate (matrix, center_x * scale_x, center_y * scale_y); +} + +/* private functions */ + +static void +gimp_brush_transform_bounding_box (const GimpTempBuf *temp_buf, + const GimpMatrix3 *matrix, + gint *x, + gint *y, + gint *width, + gint *height) +{ + const gdouble w = gimp_temp_buf_get_width (temp_buf); + const gdouble h = gimp_temp_buf_get_height (temp_buf); + gdouble x1, x2, x3, x4; + gdouble y1, y2, y3, y4; + + gimp_matrix3_transform_point (matrix, 0, 0, &x1, &y1); + gimp_matrix3_transform_point (matrix, w, 0, &x2, &y2); + gimp_matrix3_transform_point (matrix, 0, h, &x3, &y3); + gimp_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 +gimp_brush_transform_blur (GimpTempBuf *buf, + gint r) +{ + typedef struct + { + gint sum; + gint weighted_sum; + gint middle_sum; + } Sums; + + const Babl *format = gimp_temp_buf_get_format (buf); + gint components = babl_format_get_n_components (format); + gint components_r = components * r; + gint width = gimp_temp_buf_get_width (buf); + gint height = gimp_temp_buf_get_height (buf); + gint stride = components * width; + gint stride_r = stride * r; + guchar *data = gimp_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 +gimp_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 +gimp_brush_transform_adjust_hardness_matrix (gdouble width, + gdouble height, + gdouble blur_radius, + GimpMatrix3 *matrix) +{ + gdouble scale; + + if (blur_radius == 0.0) + return; + + scale = (MIN (width, height) - 2.0 * blur_radius) / MIN (width, height); + + gimp_matrix3_scale (matrix, scale, scale); + gimp_matrix3_translate (matrix, + (1.0 - scale) * width / 2.0, + (1.0 - scale) * height / 2.0); +} + +} /* extern "C" */ |