libgimp, pdb: replacing gimp_brush_get_pixels() by gimp_brush_get_buffer()…

… and gimp_brush_get_mask().

gimp_brush_get_pixels() was a bit crappy, returning raw data with only
dimensions and bpp to go with (no color model/space, no bit depth…). So the
assumption is that we work with 8-bit per channel data, possibly with alpha
depending of number of channels as deduced from bpp, and very likely in sRGB
color space. It might be globally ok with many of the brush formats (and
historical brushes) but won't fare well as we improve brush capabilities.

- gimp_brush_get_pixels() is in fact made private.
- The 2 new functions are using this old PDB call _gimp_brush_get_pixels() to
  construct buffers. This has some limitations, in particular that it returns
  only 8-bit per channel sRGB data, but at least the signature won't change when
  we will improve things in the future (so if some day, we pass fancy brushes in
  high-bit depth, the method will stay the same).
- This new implementation also allows scaling down the brush (keeping aspect
  ratio) which is useful when you need to fit a brush preview into a drawing
  widget.
- Current implementation stores the buffers at native size in the libgimp's
  GimpBrush object, hence save re-querying the core every time you need an
  update. This can be improved as current implementation also means that you
  don't get updates if the brush changed. This should handle most common use
  cases for now, though.
- Also with this change, I move GimpBrush class implementation into its own
  dedicated file.
This commit is contained in:
Jehan 2023-08-18 16:39:11 +02:00
parent ead5d01d27
commit 1ff9c12b1e
10 changed files with 383 additions and 71 deletions

View File

@ -9,10 +9,11 @@ EXPORTS
gimp_brush_application_mode_get_type
gimp_brush_get_angle
gimp_brush_get_aspect_ratio
gimp_brush_get_buffer
gimp_brush_get_by_name
gimp_brush_get_hardness
gimp_brush_get_info
gimp_brush_get_pixels
gimp_brush_get_mask
gimp_brush_get_radius
gimp_brush_get_shape
gimp_brush_get_spacing

View File

@ -37,6 +37,7 @@
#include <libgimp/gimptypes.h>
#include <libgimp/gimpbatchprocedure.h>
#include <libgimp/gimpbrush.h>
#include <libgimp/gimpchannel.h>
#include <libgimp/gimpdisplay.h>
#include <libgimp/gimpdrawable.h>

269
libgimp/gimpbrush.c Normal file
View File

@ -0,0 +1,269 @@
/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* gimpbrush.c
* Copyright (C) 2023 Jehan
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gimp.h"
#include "gimpbrush.h"
struct _GimpBrush
{
GimpResource parent_instance;
/* Native size buffers of the brush contents. */
GeglBuffer *buffer;
GeglBuffer *mask;
};
G_DEFINE_TYPE (GimpBrush, gimp_brush, GIMP_TYPE_RESOURCE);
static void gimp_brush_finalize (GObject *object);
static void gimp_brush_get_data (GimpBrush *brush);
static GeglBuffer * gimp_brush_scale (GeglBuffer *buffer,
gint max_width,
gint max_height);
static const Babl * gimp_brush_data_get_format (gint bpp);
static void gimp_brush_class_init (GimpBrushClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gimp_brush_finalize;
}
static void gimp_brush_init (GimpBrush *brush)
{
brush->buffer = NULL;
brush->mask = NULL;
}
static void
gimp_brush_finalize (GObject *object)
{
GimpBrush *brush = GIMP_BRUSH (object);
g_clear_object (&brush->buffer);
g_clear_object (&brush->mask);
G_OBJECT_CLASS (gimp_brush_parent_class)->finalize (object);
}
/**
* gimp_brush_get_buffer:
* @brush: a [class@Brush].
* @max_width: a maximum width for the returned buffer.
* @max_height: a maximum height for the returned buffer.
* @format: an optional Babl format.
*
* Gets pixel data of the brush within the bounding box specified by @max_width
* and @max_height. The data will be scaled down so that it fits within this
* size without changing its ratio. If the brush is smaller than this size to
* begin with, it will not be scaled up.
*
* If @max_width or @max_height are %NULL, the buffer is returned in the brush's
* native size.
*
* When the brush is parametric or a raster mask, only the mask (as returned by
* [method@Gimp.Brush.get_mask]) will be set. The returned buffer will be NULL.
*
* Make sure you called [func@Gegl.Init] before calling any function using
* `GEGL`.
*
* Returns: (transfer full): a [Gegl@Buffer] of %NULL if the brush is parametric
* or mask only.
*/
GeglBuffer *
gimp_brush_get_buffer (GimpBrush *brush,
gint max_width,
gint max_height,
const Babl *format)
{
gimp_brush_get_data (brush);
if (brush->buffer == NULL)
return NULL;
if (max_width == 0 || max_height == 0 ||
(gegl_buffer_get_width (brush->buffer) <= max_width &&
gegl_buffer_get_height (brush->buffer) <= max_height))
return gegl_buffer_dup (brush->buffer);
return gimp_brush_scale (brush->buffer, max_width, max_height);
}
/**
* gimp_brush_get_mask:
* @brush: a [class@Brush].
* @max_width: a maximum width for the returned buffer.
* @max_height: a maximum height for the returned buffer.
* @format: an optional Babl format.
*
* Gets mask data of the brush within the bounding box specified by @max_width
* and @max_height. The data will be scaled down so that it fits within this
* size without changing its ratio. If the brush is smaller than this size to
* begin with, it will not be scaled up.
*
* If @max_width or @max_height are %NULL, the buffer is returned in the brush's
* native size.
*
* Make sure you called [func@Gegl.Init] before calling any function using
* `GEGL`.
*
* Returns: (transfer full): a [Gegl@Buffer] representing the @brush mask.
*/
GeglBuffer *
gimp_brush_get_mask (GimpBrush *brush,
gint max_width,
gint max_height,
const Babl *format)
{
gimp_brush_get_data (brush);
g_return_val_if_fail (brush->mask != NULL, NULL);
if (max_width == 0 || max_height == 0 ||
(gegl_buffer_get_width (brush->mask) <= max_width &&
gegl_buffer_get_height (brush->mask) <= max_height))
return gegl_buffer_dup (brush->mask);
return gimp_brush_scale (brush->mask, max_width, max_height);
}
static void
gimp_brush_get_data (GimpBrush *brush)
{
gint width;
gint height;
gint mask_bpp;
GBytes *mask_bytes;
gint color_bpp;
GBytes *color_bytes;
const guchar *mask;
gsize mask_size;
const guchar *color;
gsize color_size;
/* We check the mask because the buffer might be NULL.
*
* This check assumes that the brush contents doesn't change, which is not a
* perfect assumption. We could maybe add a PDB call which would return
* the new brush data only if it changed since last call (which can be
* verified with some kind of internal runtime version to pass from one call
* to another). TODO
*/
if (brush->mask != NULL)
return;
g_clear_object (&brush->buffer);
g_clear_object (&brush->mask);
_gimp_brush_get_pixels (brush, &width, &height,
&mask_bpp, &mask_bytes,
&color_bpp, &color_bytes);
mask = g_bytes_unref_to_data (mask_bytes, &mask_size);
color = g_bytes_unref_to_data (color_bytes, &color_size);
brush->mask = gegl_buffer_linear_new_from_data ((const gpointer) mask,
gimp_brush_data_get_format (mask_bpp),
GEGL_RECTANGLE (0, 0, width, height),
0, g_free, NULL);
if (color_bpp > 0)
brush->buffer = gegl_buffer_linear_new_from_data ((const gpointer) color,
gimp_brush_data_get_format (color_bpp),
GEGL_RECTANGLE (0, 0, width, height),
0, g_free, NULL);
}
static GeglBuffer *
gimp_brush_scale (GeglBuffer *buffer,
gint max_width,
gint max_height)
{
GeglBuffer *scaled = NULL;
GeglNode *graph;
GeglNode *source;
GeglNode *op;
GeglNode *sink;
gdouble width;
gdouble height;
gdouble scale;
height = (gdouble) max_height;
width = (gdouble) gegl_buffer_get_width (buffer) / gegl_buffer_get_height (buffer) * height;
if (width > (gdouble) max_width)
{
width = (gdouble) max_width;
height = (gdouble) gegl_buffer_get_height (buffer) / gegl_buffer_get_width (buffer) * width;
}
scale = width / gegl_buffer_get_width (buffer);
graph = gegl_node_new ();
source = gegl_node_new_child (graph,
"operation", "gegl:buffer-source",
"buffer", buffer,
NULL);
op = gegl_node_new_child (graph,
"operation", "gegl:scale-ratio",
"origin-x", 0.0,
"origin-y", 0.0,
"sampler", GIMP_INTERPOLATION_LINEAR,
"abyss-policy", GEGL_ABYSS_CLAMP,
"x", scale,
"y", scale,
NULL);
sink = gegl_node_new_child (graph,
"operation", "gegl:buffer-sink",
"buffer", &scaled,
NULL);
gegl_node_link_many (source, op, sink, NULL);
gegl_node_process (sink);
g_object_unref (graph);
return scaled;
}
static const Babl *
gimp_brush_data_get_format (gint bpp)
{
/* It's an ugly way to determine the proper format but gimp_brush_get_pixels()
* doesn't give more info.
*/
switch (bpp)
{
case 1:
return babl_format ("Y' u8");
case 2:
return babl_format ("Y'A u8");
case 3:
return babl_format ("R'G'B' u8");
case 4:
return babl_format ("R'G'B'A u8");
default:
g_return_val_if_reached (NULL);
}
g_return_val_if_reached (NULL);
}

52
libgimp/gimpbrush.h Normal file
View File

@ -0,0 +1,52 @@
/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* gimpbrush.h
* Copyright (C) 2023 Jehan
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <https://www.gnu.org/licenses/>.
*/
#if !defined (__GIMP_H_INSIDE__) && !defined (GIMP_COMPILATION)
#error "Only <libgimp/gimp.h> can be included directly."
#endif
#ifndef __GIMP_BRUSH_H__
#define __GIMP_BRUSH_H__
G_BEGIN_DECLS
/* For information look into the C source or the html documentation */
#include <libgimp/gimpresource.h>
#define GIMP_TYPE_BRUSH (gimp_brush_get_type ())
G_DECLARE_FINAL_TYPE (GimpBrush, gimp_brush, GIMP, BRUSH, GimpResource)
GeglBuffer * gimp_brush_get_buffer (GimpBrush *brush,
gint max_width,
gint max_height,
const Babl *format) G_GNUC_WARN_UNUSED_RESULT;
GeglBuffer * gimp_brush_get_mask (GimpBrush *brush,
gint max_width,
gint max_height,
const Babl *format) G_GNUC_WARN_UNUSED_RESULT;
G_END_DECLS
#endif /* __GIMP_BRUSH_H__ */

View File

@ -204,7 +204,7 @@ gimp_brush_get_info (GimpBrush *brush,
}
/**
* gimp_brush_get_pixels:
* _gimp_brush_get_pixels:
* @brush: The brush.
* @width: (out): The brush width.
* @height: (out): The brush height.
@ -224,13 +224,13 @@ gimp_brush_get_info (GimpBrush *brush,
* Since: 2.2
**/
gboolean
gimp_brush_get_pixels (GimpBrush *brush,
gint *width,
gint *height,
gint *mask_bpp,
GBytes **mask_bytes,
gint *color_bpp,
GBytes **color_bytes)
_gimp_brush_get_pixels (GimpBrush *brush,
gint *width,
gint *height,
gint *mask_bpp,
GBytes **mask_bytes,
gint *color_bpp,
GBytes **color_bytes)
{
GimpValueArray *args;
GimpValueArray *return_vals;

View File

@ -32,54 +32,54 @@ G_BEGIN_DECLS
/* For information look into the C source or the html documentation */
GimpBrush* gimp_brush_new (const gchar *name);
GimpBrush* gimp_brush_get_by_name (const gchar *name);
gboolean gimp_brush_is_generated (GimpBrush *brush);
gboolean gimp_brush_get_info (GimpBrush *brush,
gint *width,
gint *height,
gint *mask_bpp,
gint *color_bpp);
gboolean gimp_brush_get_pixels (GimpBrush *brush,
gint *width,
gint *height,
gint *mask_bpp,
GBytes **mask_bytes,
gint *color_bpp,
GBytes **color_bytes);
gint gimp_brush_get_spacing (GimpBrush *brush);
gboolean gimp_brush_set_spacing (GimpBrush *brush,
gint spacing);
gboolean gimp_brush_get_shape (GimpBrush *brush,
GimpBrushGeneratedShape *shape);
gboolean gimp_brush_set_shape (GimpBrush *brush,
GimpBrushGeneratedShape shape_in,
GimpBrushGeneratedShape *shape_out);
gboolean gimp_brush_get_radius (GimpBrush *brush,
gdouble *radius);
gboolean gimp_brush_set_radius (GimpBrush *brush,
gdouble radius_in,
gdouble *radius_out);
gboolean gimp_brush_get_spikes (GimpBrush *brush,
gint *spikes);
gboolean gimp_brush_set_spikes (GimpBrush *brush,
gint spikes_in,
gint *spikes_out);
gboolean gimp_brush_get_hardness (GimpBrush *brush,
gdouble *hardness);
gboolean gimp_brush_set_hardness (GimpBrush *brush,
gdouble hardness_in,
gdouble *hardness_out);
gboolean gimp_brush_get_aspect_ratio (GimpBrush *brush,
gdouble *aspect_ratio);
gboolean gimp_brush_set_aspect_ratio (GimpBrush *brush,
gdouble aspect_ratio_in,
gdouble *aspect_ratio_out);
gboolean gimp_brush_get_angle (GimpBrush *brush,
gdouble *angle);
gboolean gimp_brush_set_angle (GimpBrush *brush,
gdouble angle_in,
gdouble *angle_out);
GimpBrush* gimp_brush_new (const gchar *name);
GimpBrush* gimp_brush_get_by_name (const gchar *name);
gboolean gimp_brush_is_generated (GimpBrush *brush);
gboolean gimp_brush_get_info (GimpBrush *brush,
gint *width,
gint *height,
gint *mask_bpp,
gint *color_bpp);
G_GNUC_INTERNAL gboolean _gimp_brush_get_pixels (GimpBrush *brush,
gint *width,
gint *height,
gint *mask_bpp,
GBytes **mask_bytes,
gint *color_bpp,
GBytes **color_bytes);
gint gimp_brush_get_spacing (GimpBrush *brush);
gboolean gimp_brush_set_spacing (GimpBrush *brush,
gint spacing);
gboolean gimp_brush_get_shape (GimpBrush *brush,
GimpBrushGeneratedShape *shape);
gboolean gimp_brush_set_shape (GimpBrush *brush,
GimpBrushGeneratedShape shape_in,
GimpBrushGeneratedShape *shape_out);
gboolean gimp_brush_get_radius (GimpBrush *brush,
gdouble *radius);
gboolean gimp_brush_set_radius (GimpBrush *brush,
gdouble radius_in,
gdouble *radius_out);
gboolean gimp_brush_get_spikes (GimpBrush *brush,
gint *spikes);
gboolean gimp_brush_set_spikes (GimpBrush *brush,
gint spikes_in,
gint *spikes_out);
gboolean gimp_brush_get_hardness (GimpBrush *brush,
gdouble *hardness);
gboolean gimp_brush_set_hardness (GimpBrush *brush,
gdouble hardness_in,
gdouble *hardness_out);
gboolean gimp_brush_get_aspect_ratio (GimpBrush *brush,
gdouble *aspect_ratio);
gboolean gimp_brush_set_aspect_ratio (GimpBrush *brush,
gdouble aspect_ratio_in,
gdouble *aspect_ratio_out);
gboolean gimp_brush_get_angle (GimpBrush *brush,
gdouble *angle);
gboolean gimp_brush_set_angle (GimpBrush *brush,
gdouble angle_in,
gdouble *angle_out);
G_END_DECLS

View File

@ -460,17 +460,6 @@ gimp_resource_is_font (GimpResource *resource)
}
struct _GimpBrush
{
GimpResource parent_instance;
};
G_DEFINE_TYPE (GimpBrush, gimp_brush, GIMP_TYPE_RESOURCE);
static void gimp_brush_class_init (GimpBrushClass *klass) {}
static void gimp_brush_init (GimpBrush *self) {}
struct _GimpPattern
{
GimpResource parent_instance;

View File

@ -60,9 +60,6 @@ gboolean gimp_resource_is_palette (GimpResource *resource);
gboolean gimp_resource_is_font (GimpResource *resource);
#define GIMP_TYPE_BRUSH (gimp_brush_get_type ())
G_DECLARE_FINAL_TYPE (GimpBrush, gimp_brush, GIMP, BRUSH, GimpResource)
#define GIMP_TYPE_PATTERN (gimp_pattern_get_type ())
G_DECLARE_FINAL_TYPE (GimpPattern, gimp_pattern, GIMP, PATTERN, GimpResource)

View File

@ -168,6 +168,7 @@ pdb_wrappers_headers = [
libgimp_sources_introspectable = [
'gimp.c',
'gimpbatchprocedure.c',
'gimpbrush.c',
'gimpchannel.c',
'gimpdisplay.c',
'gimpdrawable.c',
@ -222,6 +223,7 @@ libgimp_headers_introspectable = [
# Other headers
'gimpbatchprocedure.h',
'gimpbrush.h',
'gimpchannel.h',
'gimpdisplay.h',
'gimpdrawable.h',

View File

@ -167,6 +167,7 @@ The color bpp is zero and pixels empty when the brush is parametric versus raste
HELP
&mitch_pdb_misc('2004', '2.2');
$lib_private = 1;
@inargs = (
${brush_arg_spec}