/* 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 . */ #include "config.h" #include #include #include #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 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 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::value_type; using kernel_type = typename Subsample::kernel_type; using accum_type = typename Subsample::accum_type; Subsample 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 (mask, dest, dest_offset_x, dest_offset_y, index1, index2); } else if (mask_format == babl_format ("Y float")) { gimp_brush_core_subsample_mask_impl (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 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 T operator () (T x) const = delete; }; template class CachedPressure { T map[T (~0) + 1]; public: template 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 U operator () (U x) const = delete; }; template 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 (subsample_mask, core->pressure_brush, CachedPressure ( Pressure (pressure))); } else if (subsample_mask_format == babl_format ("Y float")) { gimp_brush_core_pressurize_mask_impl (subsample_mask, core->pressure_brush, Pressure (pressure)); } else { g_warn_if_reached (); } return core->pressure_brush; } template 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 (brush_mask, dest, dest_offset_x, dest_offset_y); } else if (brush_mask_format == babl_format ("Y float")) { gimp_brush_core_solidify_mask_impl (brush_mask, dest, dest_offset_x, dest_offset_y); } else { g_warn_if_reached (); } return dest; }