/* 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" #include "gimpbrushcore-kernels.h" #define PIXELS_PER_THREAD \ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */) static inline void rotate_pointers (gulong **p, guint32 n) { guint32 i; gulong *tmp; tmp = p[0]; for (i = 0; i < n-1; i++) p[i] = p[i+1]; p[i] = tmp; } const GimpTempBuf * gimp_brush_core_subsample_mask (GimpBrushCore *core, const GimpTempBuf *mask, gdouble x, gdouble y) { GimpTempBuf *dest; gdouble left; gint index1; gint index2; gint dest_offset_x = 0; gint dest_offset_y = 0; const gint *kernel; gint mask_width = gimp_temp_buf_get_width (mask); gint mask_height = gimp_temp_buf_get_height (mask); gint dest_width; gint dest_height; 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; } } kernel = subsample[index2][index1]; 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; } dest = gimp_temp_buf_new (mask_width + 2, mask_height + 2, gimp_temp_buf_get_format (mask)); gimp_temp_buf_data_clear (dest); dest_width = gimp_temp_buf_get_width (dest); dest_height = gimp_temp_buf_get_height (dest); core->subsample_brushes[index2][index1] = dest; gegl_parallel_distribute_range ( mask_height, PIXELS_PER_THREAD / mask_width, [=] (gint y, gint height) { const guchar *m; guchar *d; const gint *k; gint y0; gint i, j; gint r, s; gint offs; gulong *accum[KERNEL_HEIGHT]; /* Allocate and initialize the accum buffer */ for (i = 0; i < KERNEL_HEIGHT ; i++) accum[i] = g_new0 (gulong, dest_width + 1); y0 = MAX (y - (KERNEL_HEIGHT - 1), 0); m = 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 = gimp_temp_buf_get_data (dest) + (i + dest_offset_y) * dest_width; for (j = 0; j < dest_width; j++) *d++ = (accum[0][j] + 127) / KERNEL_SUM; rotate_pointers (accum, KERNEL_HEIGHT); memset (accum[KERNEL_HEIGHT - 1], 0, sizeof (gulong) * 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 = gimp_temp_buf_get_data (dest) + (i + dest_offset_y) * dest_width; for (j = 0; j < dest_width; j++) *d++ = (accum[0][j] + (KERNEL_SUM / 2)) / KERNEL_SUM; rotate_pointers (accum, KERNEL_HEIGHT); i++; } } for (i = 0; i < KERNEL_HEIGHT ; i++) g_free (accum[i]); }); return dest; } /* #define FANCY_PRESSURE */ const GimpTempBuf * gimp_brush_core_pressurize_mask (GimpBrushCore *core, const GimpTempBuf *brush_mask, gdouble x, gdouble y, gdouble pressure) { static guchar mapi[256]; const GimpTempBuf *subsample_mask; gint i; /* Get the raw subsampled mask */ subsample_mask = gimp_brush_core_subsample_mask (core, brush_mask, x, y); /* Special case pressure = 0.5 */ if ((gint) (pressure * 100 + 0.5) == 50) return subsample_mask; g_clear_pointer (&core->pressure_brush, gimp_temp_buf_unref); core->pressure_brush = gimp_temp_buf_new (gimp_temp_buf_get_width (brush_mask) + 2, gimp_temp_buf_get_height (brush_mask) + 2, gimp_temp_buf_get_format (brush_mask)); gimp_temp_buf_data_clear (core->pressure_brush); #ifdef FANCY_PRESSURE /* Create the 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 * * | / -- * | / / * / / | * -- / | */ { gdouble map[256]; gdouble ds, s, c; 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++) mapi[i] = (gint) (255 * 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 = 0; i < 256; i++) mapi[i] = (gint) (255 * (1 - map[i] / map[0])); } } #else /* ! FANCY_PRESSURE */ { gdouble j, k; j = pressure + pressure; k = 0; for (i = 0; i < 256; i++) { if (k > 255) mapi[i] = 255; else mapi[i] = (guchar) k; k += j; } } #endif /* FANCY_PRESSURE */ /* Now convert the brush */ gegl_parallel_distribute_range ( gimp_temp_buf_get_width (subsample_mask) * gimp_temp_buf_get_height (subsample_mask), PIXELS_PER_THREAD, [=] (gint offset, gint size) { const guchar *source; guchar *dest; gint i; source = gimp_temp_buf_get_data (subsample_mask) + offset; dest = gimp_temp_buf_get_data (core->pressure_brush) + offset; for (i = 0; i < size; i++) *dest++ = mapi[(*source++)]; }); return core->pressure_brush; } const GimpTempBuf * gimp_brush_core_solidify_mask (GimpBrushCore *core, const GimpTempBuf *brush_mask, gdouble x, gdouble y) { GimpTempBuf *dest; 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) { while (x < 0) x += brush_mask_width; if ((x - floor (x)) >= 0.5) dest_offset_x++; } if ((brush_mask_height % 2) == 0) { while (y < 0) y += 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; } dest = gimp_temp_buf_new (brush_mask_width + 2, brush_mask_height + 2, babl_format ("Y float")); gimp_temp_buf_data_clear (dest); core->solid_brushes[dest_offset_y][dest_offset_x] = dest; gegl_parallel_distribute_area ( GEGL_RECTANGLE (0, 0, brush_mask_width, brush_mask_height), PIXELS_PER_THREAD, [=] (const GeglRectangle *area) { const guchar *m; gfloat *d; gint i, j; m = gimp_temp_buf_get_data (brush_mask) + area->y * brush_mask_width + area->x; d = ((gfloat *) gimp_temp_buf_get_data (dest) + ((dest_offset_y + 1 + area->y) * gimp_temp_buf_get_width (dest) + (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 += brush_mask_width - area->width; d += brush_mask_width + 2 - area->width; } }); return dest; } } /* extern "C" */