app, libgimp, pdb: new gimp_drawable_filter_update() public API.

It is now possible to sync the GimpDrawableFilterConfig with core.

Another (simpler on usage) possibility could have been to sync
automatically when a property is updated. But considering that some
filters can be quite slow to render (especially in real-life usage when
working on possibly very big files), and especially that on bindings
with no variable args, scripts will likely have to edit properties one
by one, it could make editing multiple properties very slow. Therefore
the chosen solution is that editing properties stay local on libgimp and
all changed properties are synced with core at once (with a frozen
render until the end for single computation) when calling
gimp_drawable_filter_update().
This commit is contained in:
Jehan 2024-11-29 17:37:23 +09:00
parent 4d9d17504c
commit 7715a875e0
16 changed files with 436 additions and 14 deletions

View File

@ -30,6 +30,7 @@
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include <gegl-paramspecs.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
@ -44,6 +45,7 @@
#include "gimpchannel.h"
#include "gimpdrawable-filters.h"
#include "gimpdrawablefilter.h"
#include "gimperror.h"
#include "gimpidtable.h"
#include "gimpimage.h"
#include "gimplayer.h"
@ -708,6 +710,146 @@ gimp_drawable_filter_set_preview_split (GimpDrawableFilter *filter,
}
}
gboolean
gimp_drawable_filter_update (GimpDrawableFilter *filter,
const gchar **propnames,
const GimpValueArray *values,
GError **error)
{
GParamSpec **pspecs;
gchar *opname;
guint n_pspecs;
gint n_values;
gboolean changed = FALSE;
g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), FALSE);
g_return_val_if_fail (error != NULL && *error == NULL, FALSE);
n_values = gimp_value_array_length (values);
if (n_values != g_strv_length ((gchar **) propnames))
{
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
"%s: the number of property names and values differ.",
G_STRFUNC);
return FALSE;
}
g_object_freeze_notify (G_OBJECT (filter));
gegl_node_get (filter->operation, "operation", &opname, NULL);
pspecs = gegl_operation_list_properties (opname, &n_pspecs);
for (gint i = 0; i < n_pspecs; i++)
{
GParamSpec *pspec = pspecs[i];
GValue old_value = G_VALUE_INIT;
gint j;
gegl_node_get_property (filter->operation, pspec->name, &old_value);
for (j = 0; j < n_values; j++)
if (g_strcmp0 (pspec->name, propnames[j]) == 0)
break;
if (j < n_values)
{
GValue *new_value;
GValue replaced_value = G_VALUE_INIT;
gboolean new_value_initialized = FALSE;
new_value = gimp_value_array_index (values, j);
if (GEGL_IS_PARAM_SPEC_ENUM (pspec) && G_VALUE_HOLDS_STRING (new_value))
{
/* GEGL enum types are special-cased to be passed as
* GimpChoice (string) param specs on libgimp.
*/
GeglParamSpecEnum *gespec = GEGL_PARAM_SPEC_ENUM (pspec);
GEnumClass *enum_class;
GEnumValue *enum_value;
const gchar *new_str;
new_str = g_value_get_string (new_value);
enum_class = g_type_class_ref (G_PARAM_SPEC_VALUE_TYPE (pspec));
for (enum_value = enum_class->values; enum_value->value_name; enum_value++)
{
GSList *iter;
if (enum_value->value < enum_class->minimum || enum_value->value > enum_class->maximum)
continue;
for (iter = gespec->excluded_values; iter; iter = iter->next)
if (GPOINTER_TO_INT (iter->data) == enum_value->value)
break;
if (iter != NULL)
/* Excluded value. */
continue;
if (g_strcmp0 (enum_value->value_nick, new_str) == 0)
{
g_value_init (&replaced_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
g_value_set_enum (&replaced_value, enum_value->value);
new_value = &replaced_value;
new_value_initialized = TRUE;
}
}
g_type_class_unref (enum_class);
}
else if (! G_TYPE_CHECK_VALUE_TYPE (new_value, G_PARAM_SPEC_VALUE_TYPE (pspec)))
{
g_set_error (error, GIMP_ERROR, GIMP_FAILED,
/* TODO: localize after string freeze. */
"GEGL operation '%s' has been called with a "
"wrong value type for argument '%s' (#%d). "
"Expected %s, got %s.",
opname, pspec->name, i,
g_type_name (pspec->value_type),
g_type_name (G_VALUE_TYPE (new_value)));
break;
}
if (g_param_values_cmp (pspec, new_value, &old_value) != 0)
{
gegl_node_set_property (filter->operation, pspec->name, new_value);
changed = TRUE;
}
if (new_value_initialized)
g_value_unset (new_value);
continue;
}
if (! g_param_value_defaults (pspec, &old_value))
{
GValue default_value = G_VALUE_INIT;
g_value_init (&default_value, pspec->value_type);
g_param_value_set_default (pspec, &default_value);
gegl_node_set_property (filter->operation, pspec->name, &default_value);
changed = TRUE;
g_value_unset (&default_value);
}
g_value_unset (&old_value);
}
g_object_thaw_notify (G_OBJECT (filter));
g_free (pspecs);
g_free (opname);
if (changed && gimp_drawable_filter_is_active (filter))
gimp_drawable_filter_update_drawable (filter, NULL);
return (*error != NULL);
}
void
gimp_drawable_filter_set_opacity (GimpDrawableFilter *filter,
gdouble opacity)

View File

@ -98,6 +98,11 @@ void gimp_drawable_filter_set_preview_split
gboolean enabled,
GimpAlignmentType alignment,
gint split_position);
gboolean gimp_drawable_filter_update (GimpDrawableFilter *filter,
const gchar **propnames,
const GimpValueArray *values,
GError **error);
void gimp_drawable_filter_set_opacity (GimpDrawableFilter *filter,
gdouble opacity);
void gimp_drawable_filter_set_mode (GimpDrawableFilter *filter,

View File

@ -186,6 +186,32 @@ drawable_filter_set_visible_invoker (GimpProcedure *procedure,
error ? *error : NULL);
}
static GimpValueArray *
drawable_filter_update_settings_invoker (GimpProcedure *procedure,
Gimp *gimp,
GimpContext *context,
GimpProgress *progress,
const GimpValueArray *args,
GError **error)
{
gboolean success = TRUE;
GimpDrawableFilter *filter;
const gchar **propnames;
const GimpValueArray *propvalues;
filter = g_value_get_object (gimp_value_array_index (args, 0));
propnames = g_value_get_boxed (gimp_value_array_index (args, 1));
propvalues = g_value_get_boxed (gimp_value_array_index (args, 2));
if (success)
{
success = gimp_drawable_filter_update (filter, propnames, propvalues, error);
}
return gimp_procedure_get_return_values (procedure, success,
error ? *error : NULL);
}
static GimpValueArray *
drawable_filter_get_number_arguments_invoker (GimpProcedure *procedure,
Gimp *gimp,
@ -465,6 +491,42 @@ register_drawable_filter_procs (GimpPDB *pdb)
gimp_pdb_register_procedure (pdb, procedure);
g_object_unref (procedure);
/*
* gimp-drawable-filter-update-settings
*/
procedure = gimp_procedure_new (drawable_filter_update_settings_invoker);
gimp_object_set_static_name (GIMP_OBJECT (procedure),
"gimp-drawable-filter-update-settings");
gimp_procedure_set_static_help (procedure,
"Update the settings of the specified filter.",
"This procedure updates the settings of the specified filter all at once.\n"
"In particular, update will be frozen and will happen only once for all changed settings.",
NULL);
gimp_procedure_set_static_attribution (procedure,
"Jehan",
"Jehan",
"2024");
gimp_procedure_add_argument (procedure,
gimp_param_spec_drawable_filter ("filter",
"filter",
"The filter",
FALSE,
GIMP_PARAM_READWRITE));
gimp_procedure_add_argument (procedure,
g_param_spec_boxed ("propnames",
"propnames",
"Array of property names",
G_TYPE_STRV,
GIMP_PARAM_READWRITE));
gimp_procedure_add_argument (procedure,
gimp_param_spec_value_array ("propvalues",
"propvalues",
"Array of values, one per property in propnames",
NULL,
GIMP_PARAM_READWRITE));
gimp_pdb_register_procedure (pdb, procedure);
g_object_unref (procedure);
/*
* gimp-drawable-filter-get-number-arguments
*/

View File

@ -30,7 +30,7 @@
#include "internal-procs.h"
/* 727 procedures registered total */
/* 728 procedures registered total */
void
internal_procs_init (GimpPDB *pdb)

View File

@ -223,6 +223,7 @@ EXPORTS
gimp_drawable_filter_id_is_valid
gimp_drawable_filter_is_valid
gimp_drawable_filter_set_visible
gimp_drawable_filter_update
gimp_drawable_foreground_extract
gimp_drawable_free_shadow
gimp_drawable_get_bpp

View File

@ -255,3 +255,51 @@ gimp_drawable_filter_get_config (GimpDrawableFilter *filter)
return filter->config;
}
void
gimp_drawable_filter_update (GimpDrawableFilter *filter)
{
GStrvBuilder *builder = g_strv_builder_new ();
GimpValueArray *values = NULL;
GStrv propnames;
if (filter->config)
{
GObjectClass *klass;
GParamSpec **pspecs;
guint n_pspecs;
klass = G_OBJECT_GET_CLASS (filter->config);
pspecs = g_object_class_list_properties (klass, &n_pspecs);
values = gimp_value_array_new (n_pspecs);
for (guint i = 0; i < n_pspecs; i++)
{
GParamSpec *pspec = pspecs[i];
GValue value = G_VALUE_INIT;
g_value_init (&value, pspec->value_type);
g_object_get_property (G_OBJECT (filter->config), pspec->name, &value);
if (g_param_value_defaults (pspec, &value))
{
g_value_unset (&value);
continue;
}
g_strv_builder_add (builder, pspec->name);
gimp_value_array_append (values, &value);
g_value_unset (&value);
}
g_free (pspecs);
}
propnames = g_strv_builder_end (builder);
_gimp_drawable_filter_update_settings (filter, (const gchar **) propnames, values);
g_strfreev (propnames);
g_strv_builder_unref (builder);
gimp_value_array_unref (values);
}

View File

@ -43,6 +43,9 @@ gboolean gimp_drawable_filter_is_valid (GimpDrawableFilter *
GimpDrawableFilterConfig * gimp_drawable_filter_get_config (GimpDrawableFilter *filter);
void gimp_drawable_filter_update (GimpDrawableFilter *filter);
G_END_DECLS
#endif /* __GIMP_DRAWABLE_FILTER_H__ */

View File

@ -227,6 +227,50 @@ gimp_drawable_filter_set_visible (GimpDrawableFilter *filter,
return success;
}
/**
* _gimp_drawable_filter_update_settings:
* @filter: The filter.
* @propnames: (array zero-terminated=1): Array of property names.
* @propvalues: Array of values, one per property in propnames.
*
* Update the settings of the specified filter.
*
* This procedure updates the settings of the specified filter all at
* once.
* In particular, update will be frozen and will happen only once for
* all changed settings.
*
* Returns: TRUE on success.
*
* Since: 3.0
**/
gboolean
_gimp_drawable_filter_update_settings (GimpDrawableFilter *filter,
const gchar **propnames,
const GimpValueArray *propvalues)
{
GimpValueArray *args;
GimpValueArray *return_vals;
gboolean success = TRUE;
args = gimp_value_array_new_from_types (NULL,
GIMP_TYPE_DRAWABLE_FILTER, filter,
G_TYPE_STRV, propnames,
GIMP_TYPE_VALUE_ARRAY, propvalues,
G_TYPE_NONE);
return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (),
"gimp-drawable-filter-update-settings",
args);
gimp_value_array_unref (args);
success = GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS;
gimp_value_array_unref (return_vals);
return success;
}
/**
* _gimp_drawable_filter_get_number_arguments:
* @operation_name: The procedure name.

View File

@ -32,16 +32,19 @@ G_BEGIN_DECLS
/* For information look into the C source or the html documentation */
gboolean gimp_drawable_filter_id_is_valid (gint filter_id);
gchar* gimp_drawable_filter_get_name (GimpDrawableFilter *filter);
gchar* gimp_drawable_filter_get_operation_name (GimpDrawableFilter *filter);
gboolean gimp_drawable_filter_get_visible (GimpDrawableFilter *filter);
gboolean gimp_drawable_filter_set_visible (GimpDrawableFilter *filter,
gboolean visible);
G_GNUC_INTERNAL gint _gimp_drawable_filter_get_number_arguments (const gchar *operation_name);
G_GNUC_INTERNAL GParamSpec* _gimp_drawable_filter_get_argument (const gchar *operation_name,
gint arg_num);
gboolean gimp_drawable_filter_delete (GimpDrawableFilter *filter);
gboolean gimp_drawable_filter_id_is_valid (gint filter_id);
gchar* gimp_drawable_filter_get_name (GimpDrawableFilter *filter);
gchar* gimp_drawable_filter_get_operation_name (GimpDrawableFilter *filter);
gboolean gimp_drawable_filter_get_visible (GimpDrawableFilter *filter);
gboolean gimp_drawable_filter_set_visible (GimpDrawableFilter *filter,
gboolean visible);
G_GNUC_INTERNAL gboolean _gimp_drawable_filter_update_settings (GimpDrawableFilter *filter,
const gchar **propnames,
const GimpValueArray *propvalues);
G_GNUC_INTERNAL gint _gimp_drawable_filter_get_number_arguments (const gchar *operation_name);
G_GNUC_INTERNAL GParamSpec* _gimp_drawable_filter_get_argument (const gchar *operation_name,
gint arg_num);
gboolean gimp_drawable_filter_delete (GimpDrawableFilter *filter);
G_END_DECLS

View File

@ -1109,6 +1109,16 @@ gimp_gp_param_to_value (gpointer gimp,
g_value_take_param (value, pspec);
}
else if (GIMP_VALUE_HOLDS_VALUE_ARRAY (value))
{
GimpValueArray *values;
values = _gimp_gp_params_to_value_array (gimp, NULL, 0,
param->data.d_value_array.values,
param->data.d_value_array.n_values,
FALSE);
g_value_take_boxed (value, values);
}
else
{
g_warning ("%s: unsupported deserialization to GValue of type '%s'\n",
@ -1639,6 +1649,17 @@ gimp_value_to_gp_param (const GValue *value,
_gimp_param_spec_to_gp_param_def (g_value_get_param (value),
&param->data.d_param_def);
}
else if (GIMP_VALUE_HOLDS_VALUE_ARRAY (value))
{
GimpValueArray *array = g_value_get_boxed (value);
param->param_type = GP_PARAM_TYPE_VALUE_ARRAY;
param->data.d_value_array.n_values = gimp_value_array_length (array);
param->data.d_value_array.values = NULL;
if (param->data.d_value_array.n_values > 0)
param->data.d_value_array.values = _gimp_value_array_to_gp_params (array, full_copy);
}
if (param->param_type == -1)
g_warning ("%s: GValue holds unsupported type '%s'", G_STRFUNC, param->type_name);
@ -1742,6 +1763,12 @@ _gimp_gp_params_free (GPParam *params,
g_strcmp0 (params[i].data.d_param_def.type_name, "GeglParamEnum") == 0)
g_clear_object (&params[i].data.d_param_def.meta.m_choice.choice);
break;
case GP_PARAM_TYPE_VALUE_ARRAY:
_gimp_gp_params_free (params[i].data.d_value_array.values,
params[i].data.d_value_array.n_values,
full_copy);
break;
}
}

View File

@ -2134,6 +2134,19 @@ _gp_params_read (GIOChannel *channel,
user_data))
goto cleanup;
break;
case GP_PARAM_TYPE_VALUE_ARRAY:
{
guint n_values = 0;
(*params)[i].data.d_value_array.values = NULL;
_gp_params_read (channel,
&(*params)[i].data.d_value_array.values,
&n_values,
user_data);
(*params)[i].data.d_value_array.n_values = (guint32) n_values;
break;
}
}
}
@ -2357,6 +2370,14 @@ _gp_params_write (GIOChannel *channel,
user_data))
return;
break;
case GP_PARAM_TYPE_VALUE_ARRAY:
_gp_params_write (channel,
params[i].data.d_value_array.values,
params[i].data.d_value_array.n_values,
user_data);
break;
}
}
}
@ -2430,6 +2451,11 @@ _gp_params_destroy (GPParam *params,
case GP_PARAM_TYPE_PARAM_DEF:
_gp_param_def_destroy (&params[i].data.d_param_def);
break;
case GP_PARAM_TYPE_VALUE_ARRAY:
_gp_params_destroy (params[i].data.d_value_array.values,
params[i].data.d_value_array.n_values);
break;
}
}

View File

@ -78,7 +78,8 @@ typedef enum
GP_PARAM_TYPE_ARRAY,
GP_PARAM_TYPE_ID_ARRAY,
GP_PARAM_TYPE_EXPORT_OPTIONS,
GP_PARAM_TYPE_PARAM_DEF
GP_PARAM_TYPE_PARAM_DEF,
GP_PARAM_TYPE_VALUE_ARRAY,
} GPParamType;
@ -106,6 +107,7 @@ typedef struct _GPParamFormat GPParamFormat;
typedef struct _GPParamColor GPParamColor;
typedef struct _GPParamColorArray GPParamColorArray;
typedef struct _GPParamExportOptions GPParamExportOptions;
typedef struct _GPParamValueArray GPParamValueArray;
typedef struct _GPProcRun GPProcRun;
typedef struct _GPProcReturn GPProcReturn;
typedef struct _GPProcInstall GPProcInstall;
@ -306,6 +308,12 @@ struct _GPParamExportOptions
*/
};
struct _GPParamValueArray
{
guint32 n_values;
GPParam *values;
};
struct _GPParam
{
GPParamType param_type;
@ -326,6 +334,7 @@ struct _GPParam
GPParamIDArray d_id_array;
GPParamExportOptions d_export_options;
GPParamDef d_param_def;
GPParamValueArray d_value_array;
} data;
};

View File

@ -35,7 +35,8 @@ G_BEGIN_DECLS
*
* Since: 2.10
*/
#define GIMP_TYPE_VALUE_ARRAY (gimp_value_array_get_type ())
#define GIMP_TYPE_VALUE_ARRAY (gimp_value_array_get_type ())
#define GIMP_VALUE_HOLDS_VALUE_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_VALUE_ARRAY))
GType gimp_value_array_get_type (void) G_GNUC_CONST;

View File

@ -846,6 +846,15 @@ gimp_param_spec_core_object_array ("$name",
"$blurb",
GIMP_TYPE_PATTERN,
$flags)
CODE
}
elsif ($pdbtype eq 'valuearray') {
$pspec = <<CODE;
gimp_param_spec_value_array ("$name",
"$nick",
"$blurb",
NULL,
$flags)
CODE
}
else {

View File

@ -160,6 +160,37 @@ CODE
);
}
sub drawable_filter_update_settings {
$blurb = "Update the settings of the specified filter.";
$help = <<'HELP';
This procedure updates the settings of the specified filter all at once.
In particular, update will be frozen and will happen only once for all changed settings.
HELP
&jehan_pdb_misc('2024', '3.0');
$lib_private = 1;
@inargs = (
{ name => 'filter', type => 'filter',
desc => 'The filter' },
{ name => 'propnames', type => 'strv',
desc => 'Array of property names' },
{ name => 'propvalues', type => 'valuearray',
desc => 'Array of values, one per property in propnames' },
);
%invoke = (
code => <<'CODE'
{
success = gimp_drawable_filter_update (filter, propnames, propvalues, error);
}
CODE
);
}
sub drawable_filter_get_number_arguments {
$blurb = <<'BLURB';
Queries for the number of arguments on the specified filter.
@ -310,6 +341,7 @@ CODE
drawable_filter_get_operation_name
drawable_filter_get_visible
drawable_filter_set_visible
drawable_filter_update_settings
drawable_filter_get_number_arguments
drawable_filter_get_argument
drawable_filter_delete);

View File

@ -624,7 +624,17 @@ package Gimp::CodeGen::pdb;
dup_value_func => '$var = GIMP_VALUES_GET_FONT ($value)',
set_value_func => 'g_value_set_object ($value, $var)',
take_value_func => 'g_value_set_object ($value, $var)',
headers => [ qw("text/gimpfont.h") ] }
headers => [ qw("text/gimpfont.h") ] },
valuearray => { name => 'VALUEARRAY',
gtype => 'GIMP_TYPE_VALUE_ARRAY',
type => 'GimpValueArray *',
const_type => 'const GimpValueArray *',
init_value => 'NULL',
get_value_func => '$var = g_value_get_boxed ($value)',
dup_value_func => '$var = g_value_dup_boxed (gimp_value_array_index ($value))',
set_value_func => 'g_value_set_boxed ($value, $var)',
take_value_func => 'g_value_take_boxed ($value, $var)' },
);
# Split out the parts of an arg constraint