diff options
Diffstat (limited to 'app/paint/gimpbrushcore-loops.cc')
-rw-r--r-- | app/paint/gimpbrushcore-loops.cc | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/app/paint/gimpbrushcore-loops.cc b/app/paint/gimpbrushcore-loops.cc new file mode 100644 index 0000000..31b4afb --- /dev/null +++ b/app/paint/gimpbrushcore-loops.cc @@ -0,0 +1,647 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 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 <gegl.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "libgimpmath/gimpmath.h" + +extern "C" +{ + +#include "paint-types.h" + +#include "core/gimptempbuf.h" + +#include "gimpbrushcore.h" +#include "gimpbrushcore-loops.h" + +} /* extern "C" */ + +#include "gimpbrushcore-kernels.h" + + +#define PIXELS_PER_THREAD \ + (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */) + +#define EPSILON 1e-6 + + +static void +clear_edges (GimpTempBuf *buf, + gint top, + gint bottom, + gint left, + gint right) +{ + guchar *data; + const Babl *format = gimp_temp_buf_get_format (buf); + gint bpp = babl_format_get_bytes_per_pixel (format); + gint width = gimp_temp_buf_get_width (buf); + gint height = gimp_temp_buf_get_height (buf); + + if (top + bottom >= height || left + right >= width) + { + gimp_temp_buf_data_clear (buf); + + return; + } + + data = gimp_temp_buf_get_data (buf); + + memset (data, 0, (top * width + left) * bpp); + + if (left + right) + { + gint stride = width * bpp; + gint size = (left + right) * bpp; + gint y; + + data = gimp_temp_buf_get_data (buf) + + ((top + 1) * width - right) * bpp; + + for (y = top; y < height - bottom - 1; y++) + { + memset (data, 0, size); + + data += stride; + } + } + + data = gimp_temp_buf_get_data (buf) + + ((height - bottom) * width - right) * bpp; + + memset (data, 0, (bottom * width + right) * bpp); +} + +template <class T> +static inline void +rotate_pointers (T **p, + gint n) +{ + T *tmp; + gint i; + + tmp = p[0]; + + for (i = 0; i < n - 1; i++) + p[i] = p[i + 1]; + + p[i] = tmp; +} + +template <class T> +static void +gimp_brush_core_subsample_mask_impl (const GimpTempBuf *mask, + GimpTempBuf *dest, + gint dest_offset_x, + gint dest_offset_y, + gint index1, + gint index2) +{ + using value_type = typename Subsample<T>::value_type; + using kernel_type = typename Subsample<T>::kernel_type; + using accum_type = typename Subsample<T>::accum_type; + + Subsample<T> subsample; + const kernel_type *kernel = subsample.kernel[index2][index1]; + gint mask_width = gimp_temp_buf_get_width (mask); + gint mask_height = gimp_temp_buf_get_height (mask); + gint dest_width = gimp_temp_buf_get_width (dest); + gint dest_height = gimp_temp_buf_get_height (dest); + + gegl_parallel_distribute_range ( + mask_height, PIXELS_PER_THREAD / mask_width, + [=] (gint y, gint height) + { + const value_type *m; + value_type *d; + const kernel_type *k; + gint y0; + gint i, j; + gint r, s; + gint offs; + accum_type *accum[KERNEL_HEIGHT]; + + /* Allocate and initialize the accum buffer */ + for (i = 0; i < KERNEL_HEIGHT ; i++) + accum[i] = gegl_scratch_new0 (accum_type, dest_width + 1); + + y0 = MAX (y - (KERNEL_HEIGHT - 1), 0); + + m = (const value_type *) gimp_temp_buf_get_data (mask) + + y0 * mask_width; + + for (i = y0; i < y; i++) + { + for (j = 0; j < mask_width; j++) + { + k = kernel + KERNEL_WIDTH * (y - i); + for (r = y - i; r < KERNEL_HEIGHT; r++) + { + offs = j + dest_offset_x; + s = KERNEL_WIDTH; + while (s--) + accum[r][offs++] += *m * *k++; + } + m++; + } + + rotate_pointers (accum, KERNEL_HEIGHT); + } + + for (i = y; i < y + height; i++) + { + for (j = 0; j < mask_width; j++) + { + k = kernel; + for (r = 0; r < KERNEL_HEIGHT; r++) + { + offs = j + dest_offset_x; + s = KERNEL_WIDTH; + while (s--) + accum[r][offs++] += *m * *k++; + } + m++; + } + + /* store the accum buffer into the destination mask */ + d = (value_type *) gimp_temp_buf_get_data (dest) + + (i + dest_offset_y) * dest_width; + for (j = 0; j < dest_width; j++) + *d++ = subsample.round (accum[0][j]); + + rotate_pointers (accum, KERNEL_HEIGHT); + + memset (accum[KERNEL_HEIGHT - 1], 0, + sizeof (accum_type) * dest_width); + } + + if (y + height == mask_height) + { + /* store the rest of the accum buffer into the dest mask */ + while (i + dest_offset_y < dest_height) + { + d = (value_type *) gimp_temp_buf_get_data (dest) + + (i + dest_offset_y) * dest_width; + for (j = 0; j < dest_width; j++) + *d++ = subsample.round (accum[0][j]); + + rotate_pointers (accum, KERNEL_HEIGHT); + i++; + } + } + + for (i = KERNEL_HEIGHT - 1; i >= 0; i--) + gegl_scratch_free (accum[i]); + }); +} + +const GimpTempBuf * +gimp_brush_core_subsample_mask (GimpBrushCore *core, + const GimpTempBuf *mask, + gdouble x, + gdouble y) +{ + GimpTempBuf *dest; + const Babl *mask_format; + gdouble left; + gint index1; + gint index2; + gint dest_offset_x = 0; + gint dest_offset_y = 0; + gint mask_width = gimp_temp_buf_get_width (mask); + gint mask_height = gimp_temp_buf_get_height (mask); + + left = x - floor (x); + index1 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1)); + + left = y - floor (y); + index2 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1)); + + if ((mask_width % 2) == 0) + { + index1 += KERNEL_SUBSAMPLE >> 1; + + if (index1 > KERNEL_SUBSAMPLE) + { + index1 -= KERNEL_SUBSAMPLE + 1; + dest_offset_x = 1; + } + } + + if ((mask_height % 2) == 0) + { + index2 += KERNEL_SUBSAMPLE >> 1; + + if (index2 > KERNEL_SUBSAMPLE) + { + index2 -= KERNEL_SUBSAMPLE + 1; + dest_offset_y = 1; + } + } + + if (mask == core->last_subsample_brush_mask && + ! core->subsample_cache_invalid) + { + if (core->subsample_brushes[index2][index1]) + return core->subsample_brushes[index2][index1]; + } + else + { + gint i, j; + + for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++) + for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++) + g_clear_pointer (&core->subsample_brushes[i][j], gimp_temp_buf_unref); + + core->last_subsample_brush_mask = mask; + core->subsample_cache_invalid = FALSE; + } + + mask_format = gimp_temp_buf_get_format (mask); + + dest = gimp_temp_buf_new (mask_width + 2, + mask_height + 2, + mask_format); + clear_edges (dest, dest_offset_y, 0, 0, 0); + + core->subsample_brushes[index2][index1] = dest; + + if (mask_format == babl_format ("Y u8")) + { + gimp_brush_core_subsample_mask_impl<guchar> (mask, dest, + dest_offset_x, dest_offset_y, + index1, index2); + } + else if (mask_format == babl_format ("Y float")) + { + gimp_brush_core_subsample_mask_impl<gfloat> (mask, dest, + dest_offset_x, dest_offset_y, + index1, index2); + } + else + { + g_warn_if_reached (); + } + + return dest; +} + +/* The simple pressure profile + * + * It is: I'(I) = MIN (2 * pressure * I, 1) + */ +class SimplePressure +{ + gfloat scale; + +public: + SimplePressure (gdouble pressure) + { + scale = 2.0 * pressure; + } + + guchar + operator () (guchar x) const + { + gint v = RINT (scale * x); + + return MIN (v, 255); + } + + gfloat + operator () (gfloat x) const + { + gfloat v = scale * x; + + return MIN (v, 1.0f); + } + + template <class T> + T + operator () (T x) const = delete; +}; + +/* The fancy pressure profile + * + * It is: I'(I) = tanh (20 * (pressure - 0.5) * I) : pressure > 0.5 + * I'(I) = 1 - tanh (20 * (0.5 - pressure) * (1 - I)) : pressure < 0.5 + * + * It looks like: + * + * low pressure medium pressure high pressure + * + * | / -- + * | / / + * / / | + * -- / | + */ +class FancyPressure +{ + gfloat map[257]; + +public: + FancyPressure (gdouble pressure) + { + gdouble ds, s, c; + gint i; + + ds = (pressure - 0.5) * (20.0 / 256.0); + s = 0; + c = 1.0; + + if (ds > 0) + { + for (i = 0; i < 256; i++) + { + map[i] = s / c; + s += c * ds; + c += s * ds; + } + + for (i = 0; i < 256; i++) + map[i] = map[i] / map[255]; + } + else + { + ds = -ds; + + for (i = 255; i >= 0; i--) + { + map[i] = s / c; + s += c * ds; + c += s * ds; + } + + for (i = 255; i >= 0; i--) + map[i] = 1.0f - map[i] / map[0]; + } + + map[256] = map[255]; + } + + guchar + operator () (guchar x) const + { + return RINT (255.0f * map[x]); + } + + gfloat + operator () (gfloat x) const + { + gint i; + gfloat f; + + x *= 255.0f; + + i = floorf (x); + f = x - i; + + return map[i] + (map[i + 1] - map[i]) * f; + } + + template <class T> + T + operator () (T x) const = delete; +}; + +template <class T> +class CachedPressure +{ + T map[T (~0) + 1]; + +public: + template <class Pressure> + CachedPressure (Pressure pressure) + { + gint i; + + for (i = 0; i < (gint) G_N_ELEMENTS (map); i++) + map[i] = pressure (T (i)); + } + + T + operator () (T x) const + { + return map[x]; + } + + template <class U> + U + operator () (U x) const = delete; +}; + +template <class T, + class Pressure> +void +gimp_brush_core_pressurize_mask_impl (const GimpTempBuf *mask, + GimpTempBuf *dest, + Pressure pressure) +{ + gegl_parallel_distribute_range ( + gimp_temp_buf_get_width (mask) * gimp_temp_buf_get_height (mask), + PIXELS_PER_THREAD, + [=] (gint offset, gint size) + { + const T *m; + T *d; + gint i; + + m = (const T *) gimp_temp_buf_get_data (mask) + offset; + d = ( T *) gimp_temp_buf_get_data (dest) + offset; + + for (i = 0; i < size; i++) + *d++ = pressure (*m++); + }); +} + +/* #define FANCY_PRESSURE */ + +const GimpTempBuf * +gimp_brush_core_pressurize_mask (GimpBrushCore *core, + const GimpTempBuf *brush_mask, + gdouble x, + gdouble y, + gdouble pressure) +{ + const GimpTempBuf *subsample_mask; + const Babl *subsample_mask_format; + + /* Get the raw subsampled mask */ + subsample_mask = gimp_brush_core_subsample_mask (core, + brush_mask, + x, y); + + /* Special case pressure = 0.5 */ + if (fabs (pressure - 0.5) <= EPSILON) + return subsample_mask; + + g_clear_pointer (&core->pressure_brush, gimp_temp_buf_unref); + + subsample_mask_format = gimp_temp_buf_get_format (subsample_mask); + + core->pressure_brush = + gimp_temp_buf_new (gimp_temp_buf_get_width (brush_mask) + 2, + gimp_temp_buf_get_height (brush_mask) + 2, + subsample_mask_format); + +#ifdef FANCY_PRESSURE + using Pressure = FancyPressure; +#else + using Pressure = SimplePressure; +#endif + + if (subsample_mask_format == babl_format ("Y u8")) + { + gimp_brush_core_pressurize_mask_impl<guchar> (subsample_mask, + core->pressure_brush, + CachedPressure<guchar> ( + Pressure (pressure))); + } + else if (subsample_mask_format == babl_format ("Y float")) + { + gimp_brush_core_pressurize_mask_impl<gfloat> (subsample_mask, + core->pressure_brush, + Pressure (pressure)); + } + else + { + g_warn_if_reached (); + } + + return core->pressure_brush; +} + +template <class T> +static void +gimp_brush_core_solidify_mask_impl (const GimpTempBuf *mask, + GimpTempBuf *dest, + gint dest_offset_x, + gint dest_offset_y) +{ + gint mask_width = gimp_temp_buf_get_width (mask); + gint mask_height = gimp_temp_buf_get_height (mask); + gint dest_width = gimp_temp_buf_get_width (dest); + + gegl_parallel_distribute_area ( + GEGL_RECTANGLE (0, 0, mask_width, mask_height), + PIXELS_PER_THREAD, + [=] (const GeglRectangle *area) + { + const T *m; + gfloat *d; + gint i, j; + + m = (const T *) gimp_temp_buf_get_data (mask) + + area->y * mask_width + area->x; + d = ((gfloat *) gimp_temp_buf_get_data (dest) + + ((dest_offset_y + 1 + area->y) * dest_width + + (dest_offset_x + 1 + area->x))); + + for (i = 0; i < area->height; i++) + { + for (j = 0; j < area->width; j++) + *d++ = (*m++) ? 1.0 : 0.0; + + m += mask_width - area->width; + d += dest_width - area->width; + } + }); +} + +const GimpTempBuf * +gimp_brush_core_solidify_mask (GimpBrushCore *core, + const GimpTempBuf *brush_mask, + gdouble x, + gdouble y) +{ + GimpTempBuf *dest; + const Babl *brush_mask_format; + gint dest_offset_x = 0; + gint dest_offset_y = 0; + gint brush_mask_width = gimp_temp_buf_get_width (brush_mask); + gint brush_mask_height = gimp_temp_buf_get_height (brush_mask); + + if ((brush_mask_width % 2) == 0) + { + if (x < 0.0) + x = fmod (x, brush_mask_width) + brush_mask_width; + + if ((x - floor (x)) >= 0.5) + dest_offset_x++; + } + + if ((brush_mask_height % 2) == 0) + { + if (y < 0.0) + y = fmod (y, brush_mask_height) + brush_mask_height; + + if ((y - floor (y)) >= 0.5) + dest_offset_y++; + } + + if (! core->solid_cache_invalid && + brush_mask == core->last_solid_brush_mask) + { + if (core->solid_brushes[dest_offset_y][dest_offset_x]) + return core->solid_brushes[dest_offset_y][dest_offset_x]; + } + else + { + gint i, j; + + for (i = 0; i < BRUSH_CORE_SOLID_SUBSAMPLE; i++) + for (j = 0; j < BRUSH_CORE_SOLID_SUBSAMPLE; j++) + g_clear_pointer (&core->solid_brushes[i][j], gimp_temp_buf_unref); + + core->last_solid_brush_mask = brush_mask; + core->solid_cache_invalid = FALSE; + } + + brush_mask_format = gimp_temp_buf_get_format (brush_mask); + + dest = gimp_temp_buf_new (brush_mask_width + 2, + brush_mask_height + 2, + babl_format ("Y float")); + clear_edges (dest, + 1 + dest_offset_y, 1 - dest_offset_y, + 1 + dest_offset_x, 1 - dest_offset_x); + + core->solid_brushes[dest_offset_y][dest_offset_x] = dest; + + if (brush_mask_format == babl_format ("Y u8")) + { + gimp_brush_core_solidify_mask_impl<guchar> (brush_mask, dest, + dest_offset_x, dest_offset_y); + } + else if (brush_mask_format == babl_format ("Y float")) + { + gimp_brush_core_solidify_mask_impl<gfloat> (brush_mask, dest, + dest_offset_x, dest_offset_y); + } + else + { + g_warn_if_reached (); + } + + return dest; +} |