mirror of https://github.com/GNOME/gimp.git
Issue #2372 - Reduced quality of the Parametric brush in 2.10
Promote the precision of generated brushes to 32-bit float, and modify brush preview generation, and gimpbrushcore-loops, to handle float brushes. This avoids posterization in large brushes. Note that non-generated brushes are still uint8.
This commit is contained in:
parent
12dde445c4
commit
8ef1113dee
|
@ -270,6 +270,7 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
|
|||
GimpTempBuf *return_buf = NULL;
|
||||
gint mask_width;
|
||||
gint mask_height;
|
||||
guchar *mask_data;
|
||||
guchar *mask;
|
||||
guchar *buf;
|
||||
gint x, y;
|
||||
|
@ -327,12 +328,18 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
|
|||
babl_format ("R'G'B'A u8"));
|
||||
gimp_temp_buf_data_clear (return_buf);
|
||||
|
||||
mask = gimp_temp_buf_get_data (mask_buf);
|
||||
mask = mask_data = gimp_temp_buf_lock (mask_buf, babl_format ("Y u8"),
|
||||
GEGL_ACCESS_READ);
|
||||
buf = gimp_temp_buf_get_data (return_buf);
|
||||
|
||||
if (pixmap_buf)
|
||||
{
|
||||
guchar *pixmap = gimp_temp_buf_get_data (pixmap_buf);
|
||||
guchar *pixmap_data;
|
||||
guchar *pixmap;
|
||||
|
||||
pixmap = pixmap_data = gimp_temp_buf_lock (pixmap_buf,
|
||||
babl_format ("R'G'B' u8"),
|
||||
GEGL_ACCESS_READ);
|
||||
|
||||
for (y = 0; y < mask_height; y++)
|
||||
{
|
||||
|
@ -344,6 +351,8 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
|
|||
*buf++ = *mask++;
|
||||
}
|
||||
}
|
||||
|
||||
gimp_temp_buf_unlock (pixmap_buf, pixmap_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -359,6 +368,8 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
|
|||
}
|
||||
}
|
||||
|
||||
gimp_temp_buf_unlock (mask_buf, mask_data);
|
||||
|
||||
if (scaled)
|
||||
{
|
||||
gimp_temp_buf_unref ((GimpTempBuf *) mask_buf);
|
||||
|
|
|
@ -405,11 +405,11 @@ gauss (gdouble f)
|
|||
}
|
||||
|
||||
/* set up lookup table */
|
||||
static guchar *
|
||||
static gfloat *
|
||||
gimp_brush_generated_calc_lut (gdouble radius,
|
||||
gdouble hardness)
|
||||
{
|
||||
guchar *lookup;
|
||||
gfloat *lookup;
|
||||
gint length;
|
||||
gint x;
|
||||
gdouble d;
|
||||
|
@ -419,7 +419,7 @@ gimp_brush_generated_calc_lut (gdouble radius,
|
|||
|
||||
length = OVERSAMPLING * ceil (1 + sqrt (2 * SQR (ceil (radius + 1.0))));
|
||||
|
||||
lookup = g_malloc (length);
|
||||
lookup = gegl_scratch_new (gfloat, length);
|
||||
sum = 0.0;
|
||||
|
||||
if ((1.0 - hardness) < 0.0000004)
|
||||
|
@ -449,12 +449,12 @@ gimp_brush_generated_calc_lut (gdouble radius,
|
|||
buffer[x % OVERSAMPLING] = gauss (pow (d / radius, exponent));
|
||||
|
||||
sum += buffer[x % OVERSAMPLING];
|
||||
lookup[x++] = RINT (sum * (255.0 / OVERSAMPLING));
|
||||
lookup[x++] = sum / OVERSAMPLING;
|
||||
}
|
||||
|
||||
while (x < length)
|
||||
{
|
||||
lookup[x++] = 0;
|
||||
lookup[x++] = 0.0f;
|
||||
}
|
||||
|
||||
return lookup;
|
||||
|
@ -472,9 +472,9 @@ gimp_brush_generated_calc (GimpBrushGenerated *brush,
|
|||
GimpVector2 *xaxis,
|
||||
GimpVector2 *yaxis)
|
||||
{
|
||||
guchar *centerp;
|
||||
guchar *lookup;
|
||||
guchar a;
|
||||
gfloat *centerp;
|
||||
gfloat *lookup;
|
||||
gfloat a;
|
||||
gint x, y;
|
||||
gdouble c, s, cs, ss;
|
||||
GimpVector2 x_axis;
|
||||
|
@ -497,12 +497,12 @@ gimp_brush_generated_calc (GimpBrushGenerated *brush,
|
|||
&s, &c, &x_axis, &y_axis);
|
||||
|
||||
mask = gimp_temp_buf_new (width, height,
|
||||
babl_format ("Y u8"));
|
||||
babl_format ("Y float"));
|
||||
|
||||
half_width = width / 2;
|
||||
half_height = height / 2;
|
||||
|
||||
centerp = gimp_temp_buf_get_data (mask) +
|
||||
centerp = (gfloat *) gimp_temp_buf_get_data (mask) +
|
||||
half_height * width + half_width;
|
||||
|
||||
lookup = gimp_brush_generated_calc_lut (radius, hardness);
|
||||
|
@ -553,7 +553,7 @@ gimp_brush_generated_calc (GimpBrushGenerated *brush,
|
|||
if (d < radius + 1)
|
||||
a = lookup[(gint) RINT (d * OVERSAMPLING)];
|
||||
else
|
||||
a = 0;
|
||||
a = 0.0f;
|
||||
|
||||
centerp[y * width + x] = a;
|
||||
|
||||
|
@ -562,7 +562,7 @@ gimp_brush_generated_calc (GimpBrushGenerated *brush,
|
|||
}
|
||||
}
|
||||
|
||||
g_free (lookup);
|
||||
gegl_scratch_free (lookup);
|
||||
|
||||
if (xaxis)
|
||||
*xaxis = x_axis;
|
||||
|
|
|
@ -7,50 +7,110 @@
|
|||
#ifndef __GIMP_BRUSH_CORE_KERNELS_H__
|
||||
#define __GIMP_BRUSH_CORE_KERNELS_H__
|
||||
|
||||
|
||||
#define KERNEL_WIDTH 3
|
||||
#define KERNEL_HEIGHT 3
|
||||
#define KERNEL_SUBSAMPLE 4
|
||||
#define KERNEL_SUM 256
|
||||
|
||||
|
||||
/* Brush pixel subsampling kernels */
|
||||
static const int subsample[5][5][9] =
|
||||
#ifdef __cplusplus
|
||||
|
||||
template <class T>
|
||||
struct Kernel;
|
||||
|
||||
template <>
|
||||
struct Kernel<guchar>
|
||||
{
|
||||
using value_type = guchar;
|
||||
using kernel_type = guint;
|
||||
using accum_type = gulong;
|
||||
|
||||
static constexpr kernel_type
|
||||
coeff (kernel_type x)
|
||||
{
|
||||
{ 64, 64, 0, 64, 64, 0, 0, 0, 0, },
|
||||
{ 25, 103, 0, 25, 103, 0, 0, 0, 0, },
|
||||
{ 0, 128, 0, 0, 128, 0, 0, 0, 0, },
|
||||
{ 0, 103, 25, 0, 103, 25, 0, 0, 0, },
|
||||
{ 0, 64, 64, 0, 64, 64, 0, 0, 0, }
|
||||
},
|
||||
return x;
|
||||
}
|
||||
|
||||
static constexpr value_type
|
||||
round (accum_type x)
|
||||
{
|
||||
{ 25, 25, 0, 103, 103, 0, 0, 0, 0, },
|
||||
{ 6, 44, 0, 44, 162, 0, 0, 0, 0, },
|
||||
{ 0, 50, 0, 0, 206, 0, 0, 0, 0, },
|
||||
{ 0, 44, 6, 0, 162, 44, 0, 0, 0, },
|
||||
{ 0, 25, 25, 0, 103, 103, 0, 0, 0, }
|
||||
},
|
||||
{
|
||||
{ 0, 0, 0, 128, 128, 0, 0, 0, 0, },
|
||||
{ 0, 0, 0, 50, 206, 0, 0, 0, 0, },
|
||||
{ 0, 0, 0, 0, 256, 0, 0, 0, 0, },
|
||||
{ 0, 0, 0, 0, 206, 50, 0, 0, 0, },
|
||||
{ 0, 0, 0, 0, 128, 128, 0, 0, 0, }
|
||||
},
|
||||
{
|
||||
{ 0, 0, 0, 103, 103, 0, 25, 25, 0, },
|
||||
{ 0, 0, 0, 44, 162, 0, 6, 44, 0, },
|
||||
{ 0, 0, 0, 0, 206, 0, 0, 50, 0, },
|
||||
{ 0, 0, 0, 0, 162, 44, 0, 44, 6, },
|
||||
{ 0, 0, 0, 0, 103, 103, 0, 25, 25, }
|
||||
},
|
||||
{
|
||||
{ 0, 0, 0, 64, 64, 0, 64, 64, 0, },
|
||||
{ 0, 0, 0, 25, 103, 0, 25, 103, 0, },
|
||||
{ 0, 0, 0, 0, 128, 0, 0, 128, 0, },
|
||||
{ 0, 0, 0, 0, 103, 25, 0, 103, 25, },
|
||||
{ 0, 0, 0, 0, 64, 64, 0, 64, 64, }
|
||||
return (x + 127) / 256;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Kernel<gfloat>
|
||||
{
|
||||
using value_type = gfloat;
|
||||
using kernel_type = gfloat;
|
||||
using accum_type = gfloat;
|
||||
|
||||
static constexpr kernel_type
|
||||
coeff (kernel_type x)
|
||||
{
|
||||
return x / 256.0f;
|
||||
}
|
||||
|
||||
static constexpr value_type
|
||||
round (accum_type x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Brush pixel subsampling kernels */
|
||||
template <class T>
|
||||
struct Subsample : Kernel<T>
|
||||
{
|
||||
#define C(x) (Subsample::coeff (x))
|
||||
|
||||
static constexpr typename Subsample::kernel_type kernel[5][5][9] =
|
||||
{
|
||||
{
|
||||
{ C( 64), C( 64), C( 0), C( 64), C( 64), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 25), C(103), C( 0), C( 25), C(103), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C(128), C( 0), C( 0), C(128), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C(103), C( 25), C( 0), C(103), C( 25), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 64), C( 64), C( 0), C( 64), C( 64), C( 0), C( 0), C( 0), }
|
||||
},
|
||||
{
|
||||
{ C( 25), C( 25), C( 0), C(103), C(103), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 6), C( 44), C( 0), C( 44), C(162), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 50), C( 0), C( 0), C(206), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 44), C( 6), C( 0), C(162), C( 44), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 25), C( 25), C( 0), C(103), C(103), C( 0), C( 0), C( 0), }
|
||||
},
|
||||
{
|
||||
{ C( 0), C( 0), C( 0), C(128), C(128), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 50), C(206), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(256), C( 0), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(206), C( 50), C( 0), C( 0), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(128), C(128), C( 0), C( 0), C( 0), }
|
||||
},
|
||||
{
|
||||
{ C( 0), C( 0), C( 0), C(103), C(103), C( 0), C( 25), C( 25), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 44), C(162), C( 0), C( 6), C( 44), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(206), C( 0), C( 0), C( 50), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(162), C( 44), C( 0), C( 44), C( 6), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(103), C(103), C( 0), C( 25), C( 25), }
|
||||
},
|
||||
{
|
||||
{ C( 0), C( 0), C( 0), C( 64), C( 64), C( 0), C( 64), C( 64), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 25), C(103), C( 0), C( 25), C(103), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(128), C( 0), C( 0), C(128), C( 0), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C(103), C( 25), C( 0), C(103), C( 25), },
|
||||
{ C( 0), C( 0), C( 0), C( 0), C( 64), C( 64), C( 0), C( 64), C( 64), }
|
||||
}
|
||||
};
|
||||
|
||||
#undef C
|
||||
};
|
||||
|
||||
template <class T>
|
||||
constexpr typename Subsample<T>::kernel_type Subsample<T>::kernel[5][5][9];
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
#endif /* __GIMP_BRUSH_CORE_KERNELS_H__ */
|
||||
|
|
|
@ -33,125 +33,75 @@ extern "C"
|
|||
|
||||
#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
|
||||
|
||||
|
||||
template <class T>
|
||||
static inline void
|
||||
rotate_pointers (gulong **p,
|
||||
guint32 n)
|
||||
rotate_pointers (T **p,
|
||||
gint n)
|
||||
{
|
||||
guint32 i;
|
||||
gulong *tmp;
|
||||
T *tmp;
|
||||
gint i;
|
||||
|
||||
tmp = p[0];
|
||||
|
||||
for (i = 0; i < n-1; i++)
|
||||
p[i] = p[i+1];
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
using value_type = typename Subsample<T>::value_type;
|
||||
using kernel_type = typename Subsample<T>::kernel_type;
|
||||
using accum_type = typename Subsample<T>::accum_type;
|
||||
|
||||
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;
|
||||
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 guchar *m;
|
||||
guchar *d;
|
||||
const gint *k;
|
||||
gint y0;
|
||||
gint i, j;
|
||||
gint r, s;
|
||||
gint offs;
|
||||
gulong *accum[KERNEL_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] = g_new0 (gulong, dest_width + 1);
|
||||
accum[i] = gegl_scratch_new0 (accum_type, dest_width + 1);
|
||||
|
||||
y0 = MAX (y - (KERNEL_HEIGHT - 1), 0);
|
||||
|
||||
m = gimp_temp_buf_get_data (mask) + y0 * mask_width;
|
||||
m = (const value_type *) gimp_temp_buf_get_data (mask) +
|
||||
y0 * mask_width;
|
||||
|
||||
for (i = y0; i < y; i++)
|
||||
{
|
||||
|
@ -187,13 +137,15 @@ gimp_brush_core_subsample_mask (GimpBrushCore *core,
|
|||
}
|
||||
|
||||
/* store the accum buffer into the destination mask */
|
||||
d = gimp_temp_buf_get_data (dest) + (i + dest_offset_y) * dest_width;
|
||||
d = (value_type *) 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;
|
||||
*d++ = subsample.round (accum[0][j]);
|
||||
|
||||
rotate_pointers (accum, KERNEL_HEIGHT);
|
||||
|
||||
memset (accum[KERNEL_HEIGHT - 1], 0, sizeof (gulong) * dest_width);
|
||||
memset (accum[KERNEL_HEIGHT - 1], 0,
|
||||
sizeof (accum_type) * dest_width);
|
||||
}
|
||||
|
||||
if (y + height == mask_height)
|
||||
|
@ -201,71 +153,170 @@ gimp_brush_core_subsample_mask (GimpBrushCore *core,
|
|||
/* 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;
|
||||
d = (value_type *) 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;
|
||||
*d++ = subsample.round (accum[0][j]);
|
||||
|
||||
rotate_pointers (accum, KERNEL_HEIGHT);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < KERNEL_HEIGHT ; i++)
|
||||
g_free (accum[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);
|
||||
gimp_temp_buf_data_clear (dest);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* #define FANCY_PRESSURE */
|
||||
|
||||
const GimpTempBuf *
|
||||
gimp_brush_core_pressurize_mask (GimpBrushCore *core,
|
||||
const GimpTempBuf *brush_mask,
|
||||
gdouble x,
|
||||
gdouble y,
|
||||
gdouble pressure)
|
||||
/* The simple pressure profile
|
||||
*
|
||||
* It is: I'(I) = MIN (2 * pressure * I, 1)
|
||||
*/
|
||||
class SimplePressure
|
||||
{
|
||||
static guchar mapi[256];
|
||||
const GimpTempBuf *subsample_mask;
|
||||
gint i;
|
||||
gfloat scale;
|
||||
|
||||
/* 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
|
||||
*
|
||||
* | / --
|
||||
* | / /
|
||||
* / / |
|
||||
* -- / |
|
||||
*/
|
||||
public:
|
||||
SimplePressure (gdouble pressure)
|
||||
{
|
||||
gdouble map[256];
|
||||
gdouble ds, s, c;
|
||||
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;
|
||||
|
@ -281,7 +332,7 @@ gimp_brush_core_pressurize_mask (GimpBrushCore *core,
|
|||
}
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
mapi[i] = (gint) (255 * map[i] / map[255]);
|
||||
map[i] = map[i] / map[255];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -294,54 +345,183 @@ gimp_brush_core_pressurize_mask (GimpBrushCore *core,
|
|||
c += s * ds;
|
||||
}
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
mapi[i] = (gint) (255 * (1 - map[i] / map[0]));
|
||||
for (i = 255; i >= 0; i--)
|
||||
map[i] = 1.0f - map[i] / map[0];
|
||||
}
|
||||
|
||||
map[256] = map[255];
|
||||
}
|
||||
|
||||
#else /* ! FANCY_PRESSURE */
|
||||
|
||||
guchar
|
||||
operator () (guchar x) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
return RINT (255.0f * map[x]);
|
||||
}
|
||||
|
||||
#endif /* FANCY_PRESSURE */
|
||||
gfloat
|
||||
operator () (gfloat x) const
|
||||
{
|
||||
gint i;
|
||||
gfloat f;
|
||||
|
||||
/* Now convert the brush */
|
||||
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 < 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 (subsample_mask) *
|
||||
gimp_temp_buf_get_height (subsample_mask),
|
||||
gimp_temp_buf_get_width (mask) * gimp_temp_buf_get_height (mask),
|
||||
PIXELS_PER_THREAD,
|
||||
[=] (gint offset, gint size)
|
||||
{
|
||||
const guchar *source;
|
||||
guchar *dest;
|
||||
gint i;
|
||||
const T *m;
|
||||
T *d;
|
||||
gint i;
|
||||
|
||||
source = gimp_temp_buf_get_data (subsample_mask) + offset;
|
||||
dest = gimp_temp_buf_get_data (core->pressure_brush) + offset;
|
||||
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++)
|
||||
*dest++ = mapi[(*source++)];
|
||||
*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;
|
||||
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 (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);
|
||||
gimp_temp_buf_data_clear (core->pressure_brush);
|
||||
|
||||
#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 *brush_mask,
|
||||
GimpTempBuf *dest,
|
||||
gint dest_offset_x,
|
||||
gint dest_offset_y)
|
||||
{
|
||||
gint brush_mask_width = gimp_temp_buf_get_width (brush_mask);
|
||||
gint brush_mask_height = gimp_temp_buf_get_height (brush_mask);
|
||||
|
||||
gegl_parallel_distribute_area (
|
||||
GEGL_RECTANGLE (0, 0, brush_mask_width, brush_mask_height),
|
||||
PIXELS_PER_THREAD,
|
||||
[=] (const GeglRectangle *area)
|
||||
{
|
||||
const T *m;
|
||||
gfloat *d;
|
||||
gint i, j;
|
||||
|
||||
m = (const T *) 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const GimpTempBuf *
|
||||
gimp_brush_core_solidify_mask (GimpBrushCore *core,
|
||||
const GimpTempBuf *brush_mask,
|
||||
|
@ -349,6 +529,7 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
|
|||
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);
|
||||
|
@ -356,8 +537,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
|
|||
|
||||
if ((brush_mask_width % 2) == 0)
|
||||
{
|
||||
while (x < 0)
|
||||
x += brush_mask_width;
|
||||
if (x < 0.0)
|
||||
x = fmod (x, brush_mask_width) + brush_mask_width;
|
||||
|
||||
if ((x - floor (x)) >= 0.5)
|
||||
dest_offset_x++;
|
||||
|
@ -365,8 +546,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
|
|||
|
||||
if ((brush_mask_height % 2) == 0)
|
||||
{
|
||||
while (y < 0)
|
||||
y += brush_mask_height;
|
||||
if (y < 0.0)
|
||||
y = fmod (y, brush_mask_height) + brush_mask_height;
|
||||
|
||||
if ((y - floor (y)) >= 0.5)
|
||||
dest_offset_y++;
|
||||
|
@ -390,6 +571,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
|
|||
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"));
|
||||
|
@ -397,32 +580,21 @@ gimp_brush_core_solidify_mask (GimpBrushCore *core,
|
|||
|
||||
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)
|
||||
|
||||
if (brush_mask_format == babl_format ("Y u8"))
|
||||
{
|
||||
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;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
|
Loading…
Reference in New Issue