1
1
mirror of https://gitlab.gnome.org/GNOME/gimp.git synced 2025-10-06 01:12:40 +02:00
Files
gimp/app/core/gimpbrush-mipmap.cc
Ell c4a201eaf4 Issue #5208 - paint brush is broken when aspect ratio is set to negative
Fix horizontal downscaling of brush mipmap levels with odd width.
We'd previously fail to skip the last pixel of each input row,
which isn't included in the output when the width is odd, causing
subsequent output rows to be shifted to the right.
2020-06-12 17:30:28 +03:00

515 lines
13 KiB
C++

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpbrush-mipmap.c
* Copyright (C) 2020 Ell
*
* 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 "gimpbrush.h"
#include "gimpbrush-mipmap.h"
#include "gimpbrush-private.h"
#include "gimptempbuf.h"
} /* extern "C" */
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
#define GIMP_BRUSH_MIPMAP(brush, mipmaps, x, y) \
((*(mipmaps))[(y) * (brush)->priv->n_horz_mipmaps + (x)])
/* local function prototypes */
static void gimp_brush_mipmap_clear (GimpBrush *brush,
GimpTempBuf ***mipmaps);
static const GimpTempBuf * gimp_brush_mipmap_get (GimpBrush *brush,
const GimpTempBuf *source,
GimpTempBuf ***mipmaps,
gdouble *scale_x,
gdouble *scale_y);
static GimpTempBuf * gimp_brush_mipmap_downscale (const GimpTempBuf *source);
static GimpTempBuf * gimp_brush_mipmap_downscale_horz (const GimpTempBuf *source);
static GimpTempBuf * gimp_brush_mipmap_downscale_vert (const GimpTempBuf *source);
/* private functions */
static void
gimp_brush_mipmap_clear (GimpBrush *brush,
GimpTempBuf ***mipmaps)
{
if (*mipmaps)
{
gint i;
for (i = 0;
i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps;
i++)
{
g_clear_pointer (&(*mipmaps)[i], gimp_temp_buf_unref);
}
g_clear_pointer (mipmaps, g_free);
}
}
static const GimpTempBuf *
gimp_brush_mipmap_get (GimpBrush *brush,
const GimpTempBuf *source,
GimpTempBuf ***mipmaps,
gdouble *scale_x,
gdouble *scale_y)
{
gint x;
gint y;
gint i;
if (! source)
return NULL;
if (! *mipmaps)
{
gint width = gimp_temp_buf_get_width (source);
gint height = gimp_temp_buf_get_height (source);
brush->priv->n_horz_mipmaps = floor (log (width) / M_LN2) + 1;
brush->priv->n_vert_mipmaps = floor (log (height) / M_LN2) + 1;
*mipmaps = g_new0 (GimpTempBuf *, brush->priv->n_horz_mipmaps *
brush->priv->n_vert_mipmaps);
GIMP_BRUSH_MIPMAP (brush, mipmaps, 0, 0) = gimp_temp_buf_ref (source);
}
x = floor (SAFE_CLAMP (log (1.0 / MAX (*scale_x, 0.0)) / M_LN2,
0, brush->priv->n_horz_mipmaps - 1));
y = floor (SAFE_CLAMP (log (1.0 / MAX (*scale_y, 0.0)) / M_LN2,
0, brush->priv->n_vert_mipmaps - 1));
*scale_x *= pow (2.0, x);
*scale_y *= pow (2.0, y);
if (GIMP_BRUSH_MIPMAP (brush, mipmaps, x, y))
return GIMP_BRUSH_MIPMAP (brush, mipmaps, x, y);
g_return_val_if_fail (x >= 0 || y >= 0, NULL);
for (i = 1; i <= x + y; i++)
{
gint u = x - i;
gint v = y;
if (u < 0)
{
v += u;
u = 0;
}
while (u <= x && v >= 0)
{
if (GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v))
{
for (; x - u > y - v; u++)
{
GIMP_BRUSH_MIPMAP (brush, mipmaps, u + 1, v) =
gimp_brush_mipmap_downscale_horz (
GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v));
}
for (; y - v > x - u; v++)
{
GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v + 1) =
gimp_brush_mipmap_downscale_vert (
GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v));
}
for (; u < x; u++, v++)
{
GIMP_BRUSH_MIPMAP (brush, mipmaps, u + 1, v + 1) =
gimp_brush_mipmap_downscale (
GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v));
}
return GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v);
}
u++;
v--;
}
}
g_return_val_if_reached (NULL);
}
template <class T>
struct MipmapTraits;
template <>
struct MipmapTraits<guint8>
{
static guint8
mix (guint8 a,
guint8 b)
{
return ((guint32) a + (guint32) b + 1) / 2;
}
static guint8
mix (guint8 a,
guint8 b,
guint8 c,
guint8 d)
{
return ((guint32) a + (guint32) b + (guint32) c + (guint32) d + 2) / 4;
}
};
template <>
struct MipmapTraits<gfloat>
{
static gfloat
mix (gfloat a,
gfloat b)
{
return (a + b) / 2.0;
}
static gfloat
mix (gfloat a,
gfloat b,
gfloat c,
gfloat d)
{
return (a + b + c + d) / 4.0;
}
};
template <class T,
gint N>
struct MipmapAlgorithms
{
static GimpTempBuf *
downscale (const GimpTempBuf *source)
{
GimpTempBuf *destination;
gint width = gimp_temp_buf_get_width (source);
gint height = gimp_temp_buf_get_height (source);
width /= 2;
height /= 2;
destination = gimp_temp_buf_new (width, height,
gimp_temp_buf_get_format (source));
gegl_parallel_distribute_area (
GEGL_RECTANGLE (0, 0, width, height), PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
const T *src0 = (const T *) gimp_temp_buf_get_data (source);
T *dest0 = (T *) gimp_temp_buf_get_data (destination);
gint src_stride = N * gimp_temp_buf_get_width (source);
gint dest_stride = N * gimp_temp_buf_get_width (destination);
gint y;
src0 += 2 * (area->y * src_stride + N * area->x);
dest0 += area->y * dest_stride + N * area->x;
for (y = 0; y < area->height; y++)
{
const T *src = src0;
T *dest = dest0;
gint x;
for (x = 0; x < area->width; x++)
{
gint c;
for (c = 0; c < N; c++)
{
dest[c] = MipmapTraits<T>::mix (src[c],
src[N + c],
src[src_stride + c],
src[src_stride + N + c]);
}
src += 2 * N;
dest += N;
}
src0 += 2 * src_stride;
dest0 += dest_stride;
}
});
return destination;
}
static GimpTempBuf *
downscale_horz (const GimpTempBuf *source)
{
GimpTempBuf *destination;
gint width = gimp_temp_buf_get_width (source);
gint height = gimp_temp_buf_get_height (source);
width /= 2;
destination = gimp_temp_buf_new (width, height,
gimp_temp_buf_get_format (source));
gegl_parallel_distribute_range (
height, PIXELS_PER_THREAD / width,
[=] (gint offset,
gint size)
{
const T *src0 = (const T *) gimp_temp_buf_get_data (source);
T *dest0 = (T *) gimp_temp_buf_get_data (destination);
gint src_stride = N * gimp_temp_buf_get_width (source);
gint dest_stride = N * gimp_temp_buf_get_width (destination);
gint y;
src0 += offset * src_stride;
dest0 += offset * dest_stride;
for (y = 0; y < size; y++)
{
const T *src = src0;
T *dest = dest0;
gint x;
for (x = 0; x < width; x++)
{
gint c;
for (c = 0; c < N; c++)
dest[c] = MipmapTraits<T>::mix (src[c], src[N + c]);
src += 2 * N;
dest += N;
}
src0 += src_stride;
dest0 += dest_stride;
}
});
return destination;
}
static GimpTempBuf *
downscale_vert (const GimpTempBuf *source)
{
GimpTempBuf *destination;
gint width = gimp_temp_buf_get_width (source);
gint height = gimp_temp_buf_get_height (source);
height /= 2;
destination = gimp_temp_buf_new (width, height,
gimp_temp_buf_get_format (source));
gegl_parallel_distribute_range (
width, PIXELS_PER_THREAD / height,
[=] (gint offset,
gint size)
{
const T *src0 = (const T *) gimp_temp_buf_get_data (source);
T *dest0 = (T *) gimp_temp_buf_get_data (destination);
gint src_stride = N * gimp_temp_buf_get_width (source);
gint dest_stride = N * gimp_temp_buf_get_width (destination);
gint x;
src0 += offset * N;
dest0 += offset * N;
for (x = 0; x < size; x++)
{
const T *src = src0;
T *dest = dest0;
gint y;
for (y = 0; y < height; y++)
{
gint c;
for (c = 0; c < N; c++)
dest[c] = MipmapTraits<T>::mix (src[c], src[src_stride + c]);
src += 2 * src_stride;
dest += dest_stride;
}
src0 += N;
dest0 += N;
}
});
return destination;
}
};
template <class Func>
static GimpTempBuf *
gimp_brush_mipmap_dispatch (const GimpTempBuf *source,
Func func)
{
const Babl *format = gimp_temp_buf_get_format (source);
const Babl *type;
gint n_components;
type = babl_format_get_type (format, 0);
n_components = babl_format_get_n_components (format);
if (type == babl_type ("u8"))
{
switch (n_components)
{
case 1:
return func (MipmapAlgorithms<guint8, 1> ());
case 3:
return func (MipmapAlgorithms<guint8, 3> ());
}
}
else if (type == babl_type ("float"))
{
switch (n_components)
{
case 1:
return func (MipmapAlgorithms<gfloat, 1> ());
case 3:
return func (MipmapAlgorithms<gfloat, 3> ());
}
}
g_return_val_if_reached (NULL);
}
static GimpTempBuf *
gimp_brush_mipmap_downscale (const GimpTempBuf *source)
{
return gimp_brush_mipmap_dispatch (
source,
[&] (auto algorithms)
{
return algorithms.downscale (source);
});
}
static GimpTempBuf *
gimp_brush_mipmap_downscale_horz (const GimpTempBuf *source)
{
return gimp_brush_mipmap_dispatch (
source,
[&] (auto algorithms)
{
return algorithms.downscale_horz (source);
});
}
static GimpTempBuf *
gimp_brush_mipmap_downscale_vert (const GimpTempBuf *source)
{
return gimp_brush_mipmap_dispatch (
source,
[&] (auto algorithms)
{
return algorithms.downscale_vert (source);
});
}
/* public functions */
void
gimp_brush_mipmap_clear (GimpBrush *brush)
{
gimp_brush_mipmap_clear (brush, &brush->priv->mask_mipmaps);
gimp_brush_mipmap_clear (brush, &brush->priv->pixmap_mipmaps);
}
const GimpTempBuf *
gimp_brush_mipmap_get_mask (GimpBrush *brush,
gdouble *scale_x,
gdouble *scale_y)
{
return gimp_brush_mipmap_get (brush,
brush->priv->mask,
&brush->priv->mask_mipmaps,
scale_x, scale_y);
}
const GimpTempBuf *
gimp_brush_mipmap_get_pixmap (GimpBrush *brush,
gdouble *scale_x,
gdouble *scale_y)
{
return gimp_brush_mipmap_get (brush,
brush->priv->pixmap,
&brush->priv->pixmap_mipmaps,
scale_x, scale_y);
}
gsize
gimp_brush_mipmap_get_memsize (GimpBrush *brush)
{
gsize memsize = 0;
if (brush->priv->mask_mipmaps)
{
gint i;
for (i = 1;
i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps;
i++)
{
memsize += gimp_temp_buf_get_memsize (brush->priv->mask_mipmaps[i]);
}
}
if (brush->priv->pixmap_mipmaps)
{
gint i;
for (i = 1;
i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps;
i++)
{
memsize += gimp_temp_buf_get_memsize (brush->priv->pixmap_mipmaps[i]);
}
}
return memsize;
}