/* * Wavelet decompose plug-in by Miroslav Talasek, miroslav.talasek@seznam.cz */ /* * 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 . */ #include "config.h" #include #include #include "libpika/stdplugins-intl.h" #define PLUG_IN_PROC "plug-in-wavelet-decompose" #define PLUG_IN_ROLE "pika-wavelet-decompose" #define PLUG_IN_BINARY "wavelet-decompose" typedef struct _Wavelet Wavelet; typedef struct _WaveletClass WaveletClass; struct _Wavelet { PikaPlugIn parent_instance; }; struct _WaveletClass { PikaPlugInClass parent_class; }; #define WAVELET_TYPE (wavelet_get_type ()) #define WAVELET (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WAVELET_TYPE, Wavelet)) GType wavelet_get_type (void) G_GNUC_CONST; static GList * wavelet_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * wavelet_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * wavelet_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, const PikaValueArray *args, gpointer run_data); static void wavelet_blur (PikaDrawable *drawable, gint radius); static gboolean wavelet_decompose_dialog (PikaProcedure *procedure, GObject *config); G_DEFINE_TYPE (Wavelet, wavelet, PIKA_TYPE_PLUG_IN) PIKA_MAIN (WAVELET_TYPE) DEFINE_STD_SET_I18N static void wavelet_class_init (WaveletClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = wavelet_query_procedures; plug_in_class->create_procedure = wavelet_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void wavelet_init (Wavelet *wavelet) { } static GList * wavelet_query_procedures (PikaPlugIn *plug_in) { return g_list_append (NULL, g_strdup (PLUG_IN_PROC)); } static PikaProcedure * wavelet_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, PLUG_IN_PROC)) { procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, wavelet_run, NULL, NULL); pika_procedure_set_image_types (procedure, "RGB*, GRAY*"); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE); pika_procedure_set_menu_label (procedure, _("_Wavelet-decompose...")); pika_procedure_add_menu_path (procedure, "/Filters/Enhance"); pika_procedure_set_documentation (procedure, _("Wavelet decompose"), "Compute and render wavelet scales", name); pika_procedure_set_attribution (procedure, "Miroslav Talasek ", "Miroslav Talasek ", "19 January 2017"); PIKA_PROC_ARG_INT (procedure, "scales", _("Scal_es"), _("Number of scales"), 1, 7, 5, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "create-group", _("Create a layer group to store the " "_decomposition"), "Create a layer group to store the " "decomposition", TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "create-masks", _("_Add a layer mask to each scales layer"), "Add a layer mask to each scales layer", FALSE, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * wavelet_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, const PikaValueArray *args, gpointer run_data) { PikaProcedureConfig *config; PikaLayer **scale_layers; PikaLayer *new_scale; PikaLayer *parent = NULL; PikaDrawable *drawable; PikaLayerMode grain_extract_mode = PIKA_LAYER_MODE_GRAIN_EXTRACT; PikaLayerMode grain_merge_mode = PIKA_LAYER_MODE_GRAIN_MERGE; gint id; gint scales; gboolean create_group; gboolean create_masks; gegl_init (NULL, NULL); if (n_drawables != 1) { GError *error = NULL; g_set_error (&error, PIKA_PLUG_IN_ERROR, 0, _("Procedure '%s' only works with one drawable."), pika_procedure_get_name (procedure)); return pika_procedure_new_return_values (procedure, PIKA_PDB_CALLING_ERROR, error); } else { drawable = drawables[0]; } config = pika_procedure_create_config (procedure); pika_procedure_config_begin_run (config, NULL, run_mode, args); switch (run_mode) { case PIKA_RUN_INTERACTIVE: if (! wavelet_decompose_dialog (procedure, G_OBJECT (config))) { pika_procedure_config_end_run (config, PIKA_PDB_CANCEL); g_object_unref (config); return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); } break; case PIKA_RUN_NONINTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: break; default: break; } g_object_get (config, "scales", &scales, "create-group", &create_group, "create-masks", &create_masks, NULL); pika_progress_init (_("Wavelet-Decompose")); pika_image_undo_group_start (image); pika_image_freeze_layers (image); if (create_group) { parent = pika_layer_group_new (image); pika_item_set_name (PIKA_ITEM (parent), _("Decomposition")); pika_item_set_visible (PIKA_ITEM (parent), FALSE); pika_image_insert_layer (image, parent, PIKA_LAYER (pika_item_get_parent (PIKA_ITEM (drawable))), pika_image_get_item_position (image, PIKA_ITEM (drawable))); } scale_layers = g_new (PikaLayer *, scales); new_scale = pika_layer_copy (PIKA_LAYER (drawable)); pika_image_insert_layer (image, new_scale, parent, pika_image_get_item_position (image, PIKA_ITEM (drawable))); /* the exact result of the grain-extract and grain-merge modes * depends on the choice of (gamma-corrected) midpoint intensity * value. for the non-legacy modes, the midpoint value is 0.5, * which isn't representable exactly using integer precision. for * the legacy modes, the midpoint value is 128/255 (i.e., 0x80), * which is representable exactly using (gamma-corrected) integer * precision. we therefore use the legacy modes when the input * image precision is integer, and only use the (preferable) * non-legacy modes when the input image precision is floating * point. * * this avoids imperfect reconstruction of the image when using * integer precision. see bug #786844. */ switch (pika_image_get_precision (image)) { case PIKA_PRECISION_U8_LINEAR: case PIKA_PRECISION_U8_NON_LINEAR: case PIKA_PRECISION_U8_PERCEPTUAL: case PIKA_PRECISION_U16_LINEAR: case PIKA_PRECISION_U16_NON_LINEAR: case PIKA_PRECISION_U16_PERCEPTUAL: case PIKA_PRECISION_U32_LINEAR: case PIKA_PRECISION_U32_NON_LINEAR: case PIKA_PRECISION_U32_PERCEPTUAL: grain_extract_mode = PIKA_LAYER_MODE_GRAIN_EXTRACT_LEGACY; grain_merge_mode = PIKA_LAYER_MODE_GRAIN_MERGE_LEGACY; break; case PIKA_PRECISION_HALF_LINEAR: case PIKA_PRECISION_HALF_NON_LINEAR: case PIKA_PRECISION_HALF_PERCEPTUAL: case PIKA_PRECISION_FLOAT_LINEAR: case PIKA_PRECISION_FLOAT_NON_LINEAR: case PIKA_PRECISION_FLOAT_PERCEPTUAL: case PIKA_PRECISION_DOUBLE_LINEAR: case PIKA_PRECISION_DOUBLE_NON_LINEAR: case PIKA_PRECISION_DOUBLE_PERCEPTUAL: grain_extract_mode = PIKA_LAYER_MODE_GRAIN_EXTRACT; grain_merge_mode = PIKA_LAYER_MODE_GRAIN_MERGE; break; } for (id = 0 ; id < scales; id++) { PikaLayer *blur; PikaLayer *tmp; gchar scale_name[20]; pika_progress_update ((gdouble) id / (gdouble) scales); scale_layers[id] = new_scale; g_snprintf (scale_name, sizeof (scale_name), _("Scale %d"), id + 1); pika_item_set_name (PIKA_ITEM (new_scale), scale_name); tmp = pika_layer_copy (new_scale); pika_image_insert_layer (image, tmp, parent, pika_image_get_item_position (image, PIKA_ITEM (new_scale))); wavelet_blur (PIKA_DRAWABLE (tmp), pow (2.0, id)); blur = pika_layer_copy (tmp); pika_image_insert_layer (image, blur, parent, pika_image_get_item_position (image, PIKA_ITEM (tmp))); pika_layer_set_mode (tmp, grain_extract_mode); new_scale = pika_image_merge_down (image, tmp, PIKA_EXPAND_AS_NECESSARY); scale_layers[id] = new_scale; pika_item_set_visible (PIKA_ITEM (new_scale), FALSE); new_scale = blur; } pika_item_set_name (PIKA_ITEM (new_scale), _("Residual")); for (id = 0; id < scales; id++) { pika_image_reorder_item (image, PIKA_ITEM (scale_layers[id]), PIKA_ITEM (parent), pika_image_get_item_position (image, PIKA_ITEM (new_scale))); pika_layer_set_mode (scale_layers[id], grain_merge_mode); if (create_masks) { PikaLayerMask *mask = pika_layer_create_mask (scale_layers[id], PIKA_ADD_MASK_WHITE); pika_layer_add_mask (scale_layers[id], mask); } pika_item_set_visible (PIKA_ITEM (scale_layers[id]), TRUE); } if (create_group) pika_item_set_visible (PIKA_ITEM (parent), TRUE); g_free (scale_layers); pika_image_thaw_layers (image); pika_image_undo_group_end (image); pika_progress_update (1.0); pika_displays_flush (); pika_procedure_config_end_run (config, PIKA_PDB_SUCCESS); g_object_unref (config); gegl_exit (); return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); } static void wavelet_blur (PikaDrawable *drawable, gint radius) { gint x, y, width, height; if (pika_drawable_mask_intersect (drawable, &x, &y, &width, &height)) { GeglBuffer *buffer = pika_drawable_get_buffer (drawable); GeglBuffer *shadow = pika_drawable_get_shadow_buffer (drawable); gegl_render_op (buffer, shadow, "gegl:wavelet-blur", "radius", (gdouble) radius, NULL); gegl_buffer_flush (shadow); pika_drawable_merge_shadow (drawable, FALSE); pika_drawable_update (drawable, x, y, width, height); g_object_unref (buffer); g_object_unref (shadow); } } static gboolean wavelet_decompose_dialog (PikaProcedure *procedure, GObject *config) { GtkWidget *dialog; gboolean run; pika_ui_init (PLUG_IN_BINARY); dialog = pika_procedure_dialog_new (procedure, PIKA_PROCEDURE_CONFIG (config), _("Wavelet decompose")); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); pika_window_set_transient (GTK_WINDOW (dialog)); /* scales */ pika_procedure_dialog_get_scale_entry (PIKA_PROCEDURE_DIALOG (dialog), "scales", 1.0); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), NULL); gtk_widget_show (dialog); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); return run; }