[gimp] app: use mipmaps when downscaling raster brushes



commit ee39f0ec13e0cdb701e34835710e00b5286e24ae
Author: Ell <ell_se yahoo com>
Date:   Wed Feb 12 23:50:28 2020 +0200

    app: use mipmaps when downscaling raster brushes
    
    When downscaling raster brushes, we currently resample the original
    brush with no fitlering (other than bilinear interpolation), which
    results in very noticable aliasing when using heavily downscaled
    big brushes.
    
    This commit constructs a box-filtered mipmap hierarchy for the
    original brush on-demand, and the closest mipmap is used as the
    resampling source for downscaled brushes, significantly improving
    the output quality.

 app/core/Makefile.am            |   2 +
 app/core/gimpbrush-mipmap.cc    | 509 ++++++++++++++++++++++++++++++++++++++++
 app/core/gimpbrush-mipmap.h     |  38 +++
 app/core/gimpbrush-private.h    |  35 +--
 app/core/gimpbrush-transform.cc | 156 ++++++------
 app/core/gimpbrush.c            |   7 +
 app/core/meson.build            |   1 +
 7 files changed, 657 insertions(+), 91 deletions(-)
---
diff --git a/app/core/Makefile.am b/app/core/Makefile.am
index 825388a07c..ef8a83f2fe 100644
--- a/app/core/Makefile.am
+++ b/app/core/Makefile.am
@@ -114,6 +114,8 @@ libappcore_a_sources = \
        gimpbrush-header.h                      \
        gimpbrush-load.c                        \
        gimpbrush-load.h                        \
+       gimpbrush-mipmap.cc                     \
+       gimpbrush-mipmap.h                      \
        gimpbrush-private.h                     \
        gimpbrush-save.c                        \
        gimpbrush-save.h                        \
diff --git a/app/core/gimpbrush-mipmap.cc b/app/core/gimpbrush-mipmap.cc
new file mode 100644
index 0000000000..bf2e0f4274
--- /dev/null
+++ b/app/core/gimpbrush-mipmap.cc
@@ -0,0 +1,509 @@
+/* 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>
+
+extern "C"
+{
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.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 *src  = (const T *) gimp_temp_buf_get_data (source);
+        T       *dest = (T       *) gimp_temp_buf_get_data (destination);
+        gint     y;
+
+        src  += offset * gimp_temp_buf_get_width (source)      * N;
+        dest += offset * gimp_temp_buf_get_width (destination) * N;
+
+        for (y = 0; y < size; y++)
+          {
+            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;
+              }
+          }
+      });
+
+    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 = 0;
+           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 = 0;
+           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;
+}
diff --git a/app/core/gimpbrush-mipmap.h b/app/core/gimpbrush-mipmap.h
new file mode 100644
index 0000000000..b2b8fd3cca
--- /dev/null
+++ b/app/core/gimpbrush-mipmap.h
@@ -0,0 +1,38 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-mipmap.h
+ * 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/>.
+ */
+
+#ifndef __GIMP_BRUSH_MIPMAP_H__
+#define __GIMP_BRUSH_MIPMAP_H__
+
+
+void                gimp_brush_mipmap_clear       (GimpBrush *brush);
+
+const GimpTempBuf * gimp_brush_mipmap_get_mask    (GimpBrush *brush,
+                                                   gdouble   *scale_x,
+                                                   gdouble   *scale_y);
+
+const GimpTempBuf * gimp_brush_mipmap_get_pixmap  (GimpBrush *brush,
+                                                   gdouble   *scale_x,
+                                                   gdouble   *scale_y);
+
+gsize               gimp_brush_mipmap_get_memsize (GimpBrush *brush);
+
+
+#endif  /*  __GIMP_BRUSH_MIPMAP_H__  */
diff --git a/app/core/gimpbrush-private.h b/app/core/gimpbrush-private.h
index d96b847d85..c8d4d29294 100644
--- a/app/core/gimpbrush-private.h
+++ b/app/core/gimpbrush-private.h
@@ -21,21 +21,26 @@
 
 struct _GimpBrushPrivate
 {
-  GimpTempBuf    *mask;           /*  the actual mask                    */
-  GimpTempBuf    *blurred_mask;    /*  blurred actual mask cached          */
-  GimpTempBuf    *pixmap;         /*  optional pixmap data               */
-  GimpTempBuf    *blurred_pixmap;  /*  optional pixmap data blurred cache  */
-
-  gdouble         blur_hardness;
-
-  gint            spacing;    /*  brush's spacing                */
-  GimpVector2     x_axis;     /*  for calculating brush spacing  */
-  GimpVector2     y_axis;     /*  for calculating brush spacing  */
-
-  gint            use_count;  /*  for keeping the caches alive   */
-  GimpBrushCache *mask_cache;
-  GimpBrushCache *pixmap_cache;
-  GimpBrushCache *boundary_cache;
+  GimpTempBuf     *mask;           /*  the actual mask                    */
+  GimpTempBuf     *blurred_mask;    /*  blurred actual mask cached          */
+  GimpTempBuf     *pixmap;         /*  optional pixmap data               */
+  GimpTempBuf     *blurred_pixmap;  /*  optional pixmap data blurred cache  */
+
+  gdouble          blur_hardness;
+
+  gint             n_horz_mipmaps;
+  gint             n_vert_mipmaps;
+  GimpTempBuf    **mask_mipmaps;
+  GimpTempBuf    **pixmap_mipmaps;
+
+  gint             spacing;    /*  brush's spacing                */
+  GimpVector2      x_axis;     /*  for calculating brush spacing  */
+  GimpVector2      y_axis;     /*  for calculating brush spacing  */
+
+  gint             use_count;  /*  for keeping the caches alive   */
+  GimpBrushCache  *mask_cache;
+  GimpBrushCache  *pixmap_cache;
+  GimpBrushCache  *boundary_cache;
 };
 
 
diff --git a/app/core/gimpbrush-transform.cc b/app/core/gimpbrush-transform.cc
index 0a97fd61b4..4b4f4ce01c 100644
--- a/app/core/gimpbrush-transform.cc
+++ b/app/core/gimpbrush-transform.cc
@@ -34,6 +34,7 @@ extern "C"
 #include "gegl/gimp-gegl-loops.h"
 
 #include "gimpbrush.h"
+#include "gimpbrush-mipmap.h"
 #include "gimpbrush-transform.h"
 #include "gimptempbuf.h"
 
@@ -44,7 +45,7 @@ extern "C"
 
 /*  local function prototypes  */
 
-static void    gimp_brush_transform_bounding_box           (GimpBrush         *brush,
+static void    gimp_brush_transform_bounding_box           (const GimpTempBuf *temp_buf,
                                                             const GimpMatrix3 *matrix,
                                                             gint              *x,
                                                             gint              *y,
@@ -73,18 +74,21 @@ gimp_brush_real_transform_size (GimpBrush *brush,
                                 gint      *width,
                                 gint      *height)
 {
-  GimpMatrix3 matrix;
-  gdouble     scale_x, scale_y;
-  gint        x, y;
+  const GimpTempBuf *source;
+  GimpMatrix3        matrix;
+  gdouble            scale_x, scale_y;
+  gint               x, y;
 
   gimp_brush_transform_get_scale (scale, aspect_ratio,
                                   &scale_x, &scale_y);
 
-  gimp_brush_transform_matrix (gimp_brush_get_width  (brush),
-                               gimp_brush_get_height (brush),
+  source = gimp_brush_mipmap_get_mask (brush, &scale_x, &scale_y);
+
+  gimp_brush_transform_matrix (gimp_temp_buf_get_width  (source),
+                               gimp_temp_buf_get_height (source),
                                scale_x, scale_y, angle, reflect, &matrix);
 
-  gimp_brush_transform_bounding_box (brush, &matrix, &x, &y, width, height);
+  gimp_brush_transform_bounding_box (source, &matrix, &x, &y, width, height);
 }
 
 /*
@@ -121,33 +125,33 @@ gimp_brush_real_transform_mask (GimpBrush *brush,
                                 gboolean   reflect,
                                 gdouble    hardness)
 {
-  GimpTempBuf  *result;
-  GimpTempBuf  *source;
-  const guchar *src;
-  GimpMatrix3   matrix;
-  gdouble       scale_x, scale_y;
-  gint          src_width;
-  gint          src_height;
-  gint          src_width_minus_one;
-  gint          src_height_minus_one;
-  gint          dest_width;
-  gint          dest_height;
-  gint          blur_radius;
-  gint          x, y;
-  gdouble       b_lx, b_rx, t_lx, t_rx;
-  gdouble       b_ly, b_ry, t_ly, t_ry;
-  gdouble       src_tl_to_tr_delta_x;
-  gdouble       src_tl_to_tr_delta_y;
-  gdouble       src_tl_to_bl_delta_x;
-  gdouble       src_tl_to_bl_delta_y;
-  gint          src_walk_ux_i;
-  gint          src_walk_uy_i;
-  gint          src_walk_vx_i;
-  gint          src_walk_vy_i;
-  gint          src_x_min_i;
-  gint          src_y_min_i;
-  gint          src_x_max_i;
-  gint          src_y_max_i;
+  GimpTempBuf       *result;
+  const GimpTempBuf *source;
+  const guchar      *src;
+  GimpMatrix3        matrix;
+  gdouble            scale_x, scale_y;
+  gint               src_width;
+  gint               src_height;
+  gint               src_width_minus_one;
+  gint               src_height_minus_one;
+  gint               dest_width;
+  gint               dest_height;
+  gint               blur_radius;
+  gint               x, y;
+  gdouble            b_lx, b_rx, t_lx, t_rx;
+  gdouble            b_ly, b_ry, t_ly, t_ry;
+  gdouble            src_tl_to_tr_delta_x;
+  gdouble            src_tl_to_tr_delta_y;
+  gdouble            src_tl_to_bl_delta_x;
+  gdouble            src_tl_to_bl_delta_y;
+  gint               src_walk_ux_i;
+  gint               src_walk_uy_i;
+  gint               src_walk_vx_i;
+  gint               src_walk_vy_i;
+  gint               src_x_min_i;
+  gint               src_y_min_i;
+  gint               src_x_max_i;
+  gint               src_y_max_i;
 
   /*
    * tl, tr etc are used because it is easier to visualize top left,
@@ -182,10 +186,10 @@ gimp_brush_real_transform_mask (GimpBrush *brush,
   gimp_brush_transform_get_scale (scale, aspect_ratio,
                                   &scale_x, &scale_y);
 
-  source = gimp_brush_get_mask (brush);
+  source = gimp_brush_mipmap_get_mask (brush, &scale_x, &scale_y);
 
-  src_width  = gimp_brush_get_width  (brush);
-  src_height = gimp_brush_get_height (brush);
+  src_width  = gimp_temp_buf_get_width  (source);
+  src_height = gimp_temp_buf_get_height (source);
 
   gimp_brush_transform_matrix (src_width, src_height,
                                scale_x, scale_y, angle, reflect, &matrix);
@@ -196,7 +200,7 @@ gimp_brush_real_transform_mask (GimpBrush *brush,
   src_width_minus_one  = src_width  - 1;
   src_height_minus_one = src_height - 1;
 
-  gimp_brush_transform_bounding_box (brush, &matrix,
+  gimp_brush_transform_bounding_box (source, &matrix,
                                      &x, &y, &dest_width, &dest_height);
 
   blur_radius = 0;
@@ -213,7 +217,7 @@ gimp_brush_real_transform_mask (GimpBrush *brush,
                                    scale_x, scale_y, 0.0, FALSE,
                                    &unrotated_matrix);
 
-      gimp_brush_transform_bounding_box (brush, &unrotated_matrix,
+      gimp_brush_transform_bounding_box (source, &unrotated_matrix,
                                          &unrotated_x, &unrotated_y,
                                          &unrotated_dest_width,
                                          &unrotated_dest_height);
@@ -435,33 +439,33 @@ gimp_brush_real_transform_pixmap (GimpBrush *brush,
                                   gboolean   reflect,
                                   gdouble    hardness)
 {
-  GimpTempBuf  *result;
-  GimpTempBuf  *source;
-  const guchar *src;
-  GimpMatrix3   matrix;
-  gdouble       scale_x, scale_y;
-  gint          src_width;
-  gint          src_height;
-  gint          src_width_minus_one;
-  gint          src_height_minus_one;
-  gint          dest_width;
-  gint          dest_height;
-  gint          blur_radius;
-  gint          x, y;
-  gdouble       b_lx, b_rx, t_lx, t_rx;
-  gdouble       b_ly, b_ry, t_ly, t_ry;
-  gdouble       src_tl_to_tr_delta_x;
-  gdouble       src_tl_to_tr_delta_y;
-  gdouble       src_tl_to_bl_delta_x;
-  gdouble       src_tl_to_bl_delta_y;
-  gint          src_walk_ux_i;
-  gint          src_walk_uy_i;
-  gint          src_walk_vx_i;
-  gint          src_walk_vy_i;
-  gint          src_x_min_i;
-  gint          src_y_min_i;
-  gint          src_x_max_i;
-  gint          src_y_max_i;
+  GimpTempBuf       *result;
+  const GimpTempBuf *source;
+  const guchar      *src;
+  GimpMatrix3        matrix;
+  gdouble            scale_x, scale_y;
+  gint               src_width;
+  gint               src_height;
+  gint               src_width_minus_one;
+  gint               src_height_minus_one;
+  gint               dest_width;
+  gint               dest_height;
+  gint               blur_radius;
+  gint               x, y;
+  gdouble            b_lx, b_rx, t_lx, t_rx;
+  gdouble            b_ly, b_ry, t_ly, t_ry;
+  gdouble            src_tl_to_tr_delta_x;
+  gdouble            src_tl_to_tr_delta_y;
+  gdouble            src_tl_to_bl_delta_x;
+  gdouble            src_tl_to_bl_delta_y;
+  gint               src_walk_ux_i;
+  gint               src_walk_uy_i;
+  gint               src_walk_vx_i;
+  gint               src_walk_vy_i;
+  gint               src_x_min_i;
+  gint               src_y_min_i;
+  gint               src_x_max_i;
+  gint               src_y_max_i;
 
   /*
    * tl, tr etc are used because it is easier to visualize top left,
@@ -496,10 +500,10 @@ gimp_brush_real_transform_pixmap (GimpBrush *brush,
   gimp_brush_transform_get_scale (scale, aspect_ratio,
                                   &scale_x, &scale_y);
 
-  source = gimp_brush_get_pixmap (brush);
+  source = gimp_brush_mipmap_get_pixmap (brush, &scale_x, &scale_y);
 
-  src_width  = gimp_brush_get_width  (brush);
-  src_height = gimp_brush_get_height (brush);
+  src_width  = gimp_temp_buf_get_width  (source);
+  src_height = gimp_temp_buf_get_height (source);
 
   gimp_brush_transform_matrix (src_width, src_height,
                                scale_x, scale_y, angle, reflect, &matrix);
@@ -510,7 +514,7 @@ gimp_brush_real_transform_pixmap (GimpBrush *brush,
   src_width_minus_one  = src_width  - 1;
   src_height_minus_one = src_height - 1;
 
-  gimp_brush_transform_bounding_box (brush, &matrix,
+  gimp_brush_transform_bounding_box (source, &matrix,
                                      &x, &y, &dest_width, &dest_height);
 
   blur_radius = 0;
@@ -527,7 +531,7 @@ gimp_brush_real_transform_pixmap (GimpBrush *brush,
                                    scale_x, scale_y, 0.0, FALSE,
                                    &unrotated_matrix);
 
-      gimp_brush_transform_bounding_box (brush, &unrotated_matrix,
+      gimp_brush_transform_bounding_box (source, &unrotated_matrix,
                                          &unrotated_x, &unrotated_y,
                                          &unrotated_dest_width,
                                          &unrotated_dest_height);
@@ -767,17 +771,17 @@ gimp_brush_transform_matrix (gdouble      width,
 /*  private functions  */
 
 static void
-gimp_brush_transform_bounding_box (GimpBrush         *brush,
+gimp_brush_transform_bounding_box (const GimpTempBuf *temp_buf,
                                    const GimpMatrix3 *matrix,
                                    gint              *x,
                                    gint              *y,
                                    gint              *width,
                                    gint              *height)
 {
-  const gdouble w = gimp_brush_get_width  (brush);
-  const gdouble h = gimp_brush_get_height (brush);
-  gdouble       x1, x2, x3, x4;
-  gdouble       y1, y2, y3, y4;
+  const gdouble  w = gimp_temp_buf_get_width  (temp_buf);
+  const gdouble  h = gimp_temp_buf_get_height (temp_buf);
+  gdouble        x1, x2, x3, x4;
+  gdouble        y1, y2, y3, y4;
 
   gimp_matrix3_transform_point (matrix, 0, 0, &x1, &y1);
   gimp_matrix3_transform_point (matrix, w, 0, &x2, &y2);
diff --git a/app/core/gimpbrush.c b/app/core/gimpbrush.c
index ddb1e2be59..8e151158ed 100644
--- a/app/core/gimpbrush.c
+++ b/app/core/gimpbrush.c
@@ -30,6 +30,7 @@
 #include "gimpbrush.h"
 #include "gimpbrush-boundary.h"
 #include "gimpbrush-load.h"
+#include "gimpbrush-mipmap.h"
 #include "gimpbrush-private.h"
 #include "gimpbrush-save.h"
 #include "gimpbrush-transform.h"
@@ -189,6 +190,8 @@ gimp_brush_finalize (GObject *object)
   g_clear_pointer (&brush->priv->blurred_mask,   gimp_temp_buf_unref);
   g_clear_pointer (&brush->priv->blurred_pixmap, gimp_temp_buf_unref);
 
+  gimp_brush_mipmap_clear (brush);
+
   g_clear_object (&brush->priv->mask_cache);
   g_clear_object (&brush->priv->pixmap_cache);
   g_clear_object (&brush->priv->boundary_cache);
@@ -246,6 +249,8 @@ gimp_brush_get_memsize (GimpObject *object,
   memsize += gimp_temp_buf_get_memsize (brush->priv->mask);
   memsize += gimp_temp_buf_get_memsize (brush->priv->pixmap);
 
+  memsize += gimp_brush_mipmap_get_memsize (brush);
+
   return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                   gui_size);
 }
@@ -410,6 +415,8 @@ gimp_brush_dirty (GimpData *data)
   if (brush->priv->boundary_cache)
     gimp_brush_cache_clear (brush->priv->boundary_cache);
 
+  gimp_brush_mipmap_clear (brush);
+
   g_clear_pointer (&brush->priv->blurred_mask,   gimp_temp_buf_unref);
   g_clear_pointer (&brush->priv->blurred_pixmap, gimp_temp_buf_unref);
 
diff --git a/app/core/meson.build b/app/core/meson.build
index 2164b727a5..89a205d32e 100644
--- a/app/core/meson.build
+++ b/app/core/meson.build
@@ -59,6 +59,7 @@ libappcore_sources = [
   'gimpboundary.c',
   'gimpbrush-boundary.c',
   'gimpbrush-load.c',
+  'gimpbrush-mipmap.cc',
   'gimpbrush-save.c',
   'gimpbrush-transform.cc',
   'gimpbrush.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]