def foggify(procedure, run_mode, image, drawable, args, data): name = args.index(0) turbulence = args.index(1) opacity = args.index(2) if run_mode == Gimp.RunMode.INTERACTIVE: # TODO: add a GUI. This first port works just with default # values. color = Gimp.RGB() color.set(240.0, 180.0, 70.0) Gimp.context_push() image.undo_group_start() if image.base_type() is Gimp.ImageBaseType.RGB: type = Gimp.ImageType.RGBA_IMAGE else: type = Gimp.ImageType.GRAYA_IMAGE fog = Gimp.Layer.new(image, name, drawable.width(), drawable.height(), type, opacity, Gimp.LayerMode.NORMAL) fog.fill(Gimp.FillType.TRANSPARENT) image.insert_layer(fog, None, 0) Gimp.context_set_background(color) fog.edit_fill(Gimp.FillType.BACKGROUND) # create a layer mask for the new layer mask = fog.create_mask(0) fog.add_mask(mask) # add some clouds to the layer Gimp.get_pdb().run_procedure('plug-in-plasma', [ GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE), GObject.Value(Gimp.Image, image), GObject.Value(Gimp.Drawable, mask), GObject.Value(GObject.TYPE_INT, int(time.time())), GObject.Value(GObject.TYPE_DOUBLE, turbulence), ]) # apply the clouds to the layer fog.remove_mask(Gimp.MaskApplyMode.APPLY) fog.set_visible(True) Gimp.displays_flush() image.undo_group_end() Gimp.context_pop() return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def _commit(self): Console._commit(self) Gimp.displays_flush()
def foggify(procedure, run_mode, image, n_drawables, drawables, args, data): config = procedure.create_config() config.begin_run(image, run_mode, args) if run_mode == Gimp.RunMode.INTERACTIVE: GimpUi.init('python-fu-foggify') dialog = GimpUi.ProcedureDialog.new(procedure, config) dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT) dialog.fill(None) if not dialog.run(): dialog.destroy() config.end_run(Gimp.PDBStatusType.CANCEL) return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) else: dialog.destroy() color = config.get_property('color') name = config.get_property('name') turbulence = config.get_property('turbulence') opacity = config.get_property('opacity') Gimp.context_push() image.undo_group_start() if image.get_base_type() is Gimp.ImageBaseType.RGB: type = Gimp.ImageType.RGBA_IMAGE else: type = Gimp.ImageType.GRAYA_IMAGE for drawable in drawables: fog = Gimp.Layer.new(image, name, drawable.get_width(), drawable.get_height(), type, opacity, Gimp.LayerMode.NORMAL) fog.fill(Gimp.FillType.TRANSPARENT) image.insert_layer(fog, drawable.get_parent(), image.get_item_position(drawable)) Gimp.context_set_background(color) fog.edit_fill(Gimp.FillType.BACKGROUND) # create a layer mask for the new layer mask = fog.create_mask(0) fog.add_mask(mask) # add some clouds to the layer Gimp.get_pdb().run_procedure('plug-in-plasma', [ GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE), GObject.Value(Gimp.Image, image), GObject.Value(Gimp.Drawable, mask), GObject.Value(GObject.TYPE_INT, int(time.time())), GObject.Value(GObject.TYPE_DOUBLE, turbulence), ]) # apply the clouds to the layer fog.remove_mask(Gimp.MaskApplyMode.APPLY) fog.set_visible(True) Gimp.displays_flush() image.undo_group_end() Gimp.context_pop() config.end_run(Gimp.PDBStatusType.SUCCESS) return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def run(self, procedure, args, data): runmode = args.index(0) if runmode == Gimp.RunMode.INTERACTIVE: gi.require_version('Gtk', '3.0') from gi.repository import Gtk gi.require_version('Gdk', '3.0') from gi.repository import Gdk Gimp.ui_init("palette-offset.py", False) dialog = Gimp.Dialog(use_header_bar=True, title=_("Exercise a goat (Python 3)"), role="goat-exercise-Python3") dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("_Source", Gtk.ResponseType.APPLY) dialog.add_button("_OK", Gtk.ResponseType.OK) geometry = Gdk.Geometry(); geometry.min_aspect = 0.5; geometry.max_aspect = 1.0; dialog.set_geometry_hints(None, geometry, Gdk.WindowHints.ASPECT); box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) dialog.get_content_area().add(box) box.show() # XXX We use printf-style string for sharing the localized # string. You may just use recommended Python format() or # any style you like in your plug-ins. head_text=("This plug-in is an exercise in '%s' to " "demo plug-in creation.\nCheck out the last " "version of the source code online by clicking " "the \"Source\" button." % ("Python 3")) label = Gtk.Label(label=head_text) box.pack_start(label, False, False, 1) label.show() contents = None # Get the file contents Python-style instead of using # GLib.file_get_contents() which returns bytes result, and # when converting to string, get newlines as text contents. # Rather than wasting time to figure this out, use Python # core API! with open(os.path.realpath(__file__), 'r') as f: contents = f.read() if contents is not None: scrolled = Gtk.ScrolledWindow() scrolled.set_vexpand (True) box.pack_start(scrolled, True, True, 1) scrolled.show() view = Gtk.TextView() view.set_wrap_mode(Gtk.WrapMode.WORD) view.set_editable(False) buffer = view.get_buffer() buffer.set_text(contents, -1) scrolled.add(view) view.show() while (True): response = dialog.run() if response == Gtk.ResponseType.OK: dialog.destroy() break elif response == Gtk.ResponseType.APPLY: Gio.app_info_launch_default_for_uri(url, None) continue else: dialog.destroy() return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) # Parameters are not working fine yet because properties should # be Gimp.ImageID/Gimp.DrawableID but we can't make these with # pygobject. Until I figure out how to make it work, you could # uncomment the following lines instead of using the args value. #images = Gimp.image_list() #image_id = images[0] #drawable_id = Gimp.image_get_active_drawable(image_id) drawable_id = args.index(2) success, x, y, width, height = Gimp.drawable_mask_intersect(drawable_id); if success: Gegl.init(None); buffer = Gimp.drawable_get_buffer(drawable_id) shadow_buffer = Gimp.drawable_get_shadow_buffer(drawable_id) graph = Gegl.Node() input = graph.create_child("gegl:buffer-source") input.set_property("buffer", buffer) invert = graph.create_child("gegl:invert") output = graph.create_child("gegl:write-buffer") output.set_property("buffer", shadow_buffer) input.link(invert) invert.link(output) output.process() # This is extremely important in bindings, since we don't # unref buffers. If we don't explicitly flush a buffer, we # may left hanging forever. This step is usually done # during an unref(). shadow_buffer.flush() Gimp.drawable_merge_shadow(drawable_id, True) Gimp.drawable_update(drawable_id, x, y, width, height) Gimp.displays_flush() else: retval = procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error("No pixels to process in the selected area.")) return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def _run_procedure_in_mode(procedure, run_mode, image, list_all_args, original_args, data): ''' Understands run_mode. Different ways to invoke procedure batch, or interactive. Hides run_mode from Authors. I.E. their run_func signature does not have run_mode. require procedure is-a Gimp.Procedure. require original_args is-a Gimp.ValueArray. require list_all_args is a list of GValues ''' list_wrapped_args = Marshal.wrap_args(list_all_args) # To know the Python name of a Gimp.Procedure method (e.g. gimp_procedure_get_name) # see gimp/libgimp/gimpprocedure.h, and then remove the prefix gimp_procedure_ name = procedure.get_name() FuRunner.logger.info( f"_run_procedure_in_mode: {name}, {run_mode}, {list_wrapped_args}") ''' list_wrapped_args are one-to-one with formal params. list_wrapped_args may include some args that are not guiable (i.e. image, drawable) ''' fuProcedure = FuProcedures.get_by_name(name) isBatch = (run_mode == Gimp.RunMode.NONINTERACTIVE) ''' Else so-called interactive mode, with GUI dialog of params. Note that the v2 mode RUN_WITH_LAST_VALS is obsolete since Gimp 3 persists settings, i.e. actual arg values can be from last invocation. If not from last invocation, they are the formal parameter defaults. ''' func = fuProcedure.get_authors_function() """ The ProcedureConfig should have length equal to ???? original_args is-a GimpValueArray """ # config = FuProcedureConfig(procedure, len(list_wrapped_args)-2 ) config = FuProcedureConfig(fuProcedure, procedure, original_args.length()) config.begin_run(image, run_mode, original_args) if isBatch: try: # invoke func with unpacked args. Since Python3, apply() gone. # TODO is this the correct set of args? E.G. Gimp is handling RUN_WITH_LAST_VALS, etc. ??? runfunc_result = func(*list_wrapped_args) final_result = FuResult.makeSuccess(procedure, runfunc_result) except Exception as err: # TODO print the Exception type err_message = f"In plugin: {name}, function: {func}, Exception: {err}" FuRunner.logger.warning(err_message) final_result = FuResult.makeException(procedure, err_message) else: ''' Not enclosed in try:except: since then you don't get a good traceback. Any exceptions in showing a dialog are hard programming errors. Any exception in executing the run_func should be shown to user, either by calling our own dialog or by calling a Gimp.ErrorDialog (not exist) or by passing the exception string back to Gimp. ''' was_canceled, runfunc_result = FuRunner._interact( procedure, list_wrapped_args, config) if was_canceled: final_result = FuResult.makeCancel(procedure) config.end_run(Gimp.PDBStatusType.CANCEL) else: final_result = FuResult.makeSuccess(procedure, runfunc_result) config.end_run(Gimp.PDBStatusType.SUCCESS) """ OLD above was enclosed in try try: except Exception as err: ''' Probably GimpFu module programming error (e.g. bad calls to GTK) According to GLib docs, should be a warning, since this is not recoverable. But it might be author programming code (e.g. invalid PARAMS) ''' proceed(f"Exception opening plugin dialog: {err}") final_result = FuResult.make(Gimp.PDBStatusType.EXECUTION_ERROR, GLib.Error()) """ ''' Make visible any alterations to user created images. GimpFu promises to hide the need for this. ''' Gimp.displays_flush() # !!! Gimp, not gimp did_suggest_or_deprecate = Suggest.summarize() did_suggest_or_deprecate = did_suggest_or_deprecate or Deprecation.summarize( ) if did_suggest_or_deprecate: # TODO make this go to the status bar, not a dialog # Gimp.message("See console for suggestions and deprecations.") pass if summarize_proceed_errors(): # side effect is writing to console """ Gimpfu proceeded past earlier exceptions. Display GIMP dialog. """ msg = "GimpFu detected errors. See console for a summary." Gimp.message(msg) final_result = FuResult.makeException(procedure, msg) # Alternatively: raise Exception(msg) but that is confusing to Author FuRunner.logger.debug( f"Returning from: {name} with result:{final_result}") # ensure final_result is type GimpValueArray assert isinstance(final_result, Gimp.ValueArray) return final_result
def run(self, procedure, run_mode, image, drawable, args, run_data): if run_mode == Gimp.RunMode.INTERACTIVE: gi.require_version('Gtk', '3.0') from gi.repository import Gtk gi.require_version('Gdk', '3.0') from gi.repository import Gdk GimpUi.ui_init("palette-offset.py") dialog = GimpUi.Dialog(use_header_bar=True, title=_("Exercise a goat (Python 3)"), role="goat-exercise-Python3") dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("_Source", Gtk.ResponseType.APPLY) dialog.add_button("_OK", Gtk.ResponseType.OK) geometry = Gdk.Geometry() geometry.min_aspect = 0.5 geometry.max_aspect = 1.0 dialog.set_geometry_hints(None, geometry, Gdk.WindowHints.ASPECT) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) dialog.get_content_area().add(box) box.show() # XXX We use printf-style string for sharing the localized # string. You may just use recommended Python format() or # any style you like in your plug-ins. head_text = ("This plug-in is an exercise in '%s' to " "demo plug-in creation.\nCheck out the last " "version of the source code online by clicking " "the \"Source\" button." % ("Python 3")) label = Gtk.Label(label=head_text) box.pack_start(label, False, False, 1) label.show() contents = None # Get the file contents Python-style instead of using # GLib.file_get_contents() which returns bytes result, and # when converting to string, get newlines as text contents. # Rather than wasting time to figure this out, use Python # core API! with open(os.path.realpath(__file__), 'r') as f: contents = f.read() if contents is not None: scrolled = Gtk.ScrolledWindow() scrolled.set_vexpand(True) box.pack_start(scrolled, True, True, 1) scrolled.show() view = Gtk.TextView() view.set_wrap_mode(Gtk.WrapMode.WORD) view.set_editable(False) buffer = view.get_buffer() buffer.set_text(contents, -1) scrolled.add(view) view.show() while (True): response = dialog.run() if response == Gtk.ResponseType.OK: dialog.destroy() break elif response == Gtk.ResponseType.APPLY: url = "https://gitlab.gnome.org/GNOME/gimp/-/blob/master/plug-ins/goat-exercises/goat-exercise-py3.py" Gio.app_info_launch_default_for_uri(url, None) continue else: dialog.destroy() return procedure.new_return_values( Gimp.PDBStatusType.CANCEL, GLib.Error()) intersect, x, y, width, height = drawable.mask_intersect() if intersect: Gegl.init(None) buffer = drawable.get_buffer() shadow_buffer = drawable.get_shadow_buffer() graph = Gegl.Node() input = graph.create_child("gegl:buffer-source") input.set_property("buffer", buffer) invert = graph.create_child("gegl:invert") output = graph.create_child("gegl:write-buffer") output.set_property("buffer", shadow_buffer) input.link(invert) invert.link(output) output.process() # This is extremely important in bindings, since we don't # unref buffers. If we don't explicitly flush a buffer, we # may left hanging forever. This step is usually done # during an unref(). shadow_buffer.flush() drawable.merge_shadow(True) drawable.update(x, y, width, height) Gimp.displays_flush() return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def benchmark(procedure, args, data): if args.length() != 3: error = 'Wrong parameters given' return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) run_mode = args.index(0) folder = args.index(1) save_output = args.index(2) folder = os.path.abspath(os.path.expanduser(folder)) if not os.path.exists(folder): error = "Folder '" + folder + "' doesn't exist.\n" return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) total_unclassified = 0 total_misclassified = 0 total_time = 0.0 images = os.path.join(folder, "images") for name in os.listdir(images): try: image_display.delete() mask_display.delete() except NameError: pass image_name = os.path.join(images, name) # Remove suffix, assuming it has three characters name = re.sub(r'\....$', '', name) mask_name = os.path.join(folder, "cm_bmp", name + '.png') truth_name = os.path.join(folder, "truth", name + '.bmp') image = Gimp.file_load(run_mode, Gio.file_new_for_path(image_name)) image_layer = image.get_active_layer() mask = Gimp.file_load(run_mode, Gio.file_new_for_path(mask_name)) convert_grayscale(mask) mask_layer = mask.get_active_layer() truth = Gimp.file_load(run_mode, Gio.file_new_for_path(truth_name)) convert_grayscale(truth) truth_layer = truth.get_active_layer() unclassified = unclassified_pixels(mask_layer, truth_layer) sys.stderr.write(os.path.basename(image_name)) start = time.time() image_layer.foreground_extract(Gimp.ForegroundExtractMode.MATTING, mask_layer) end = time.time() sys.stderr.write(" ") # This line was in the gimp 2 implementation, and probably isn't needed anymore. # mask_layer.flush () # Ignore errors when creating image displays; # allows us to be used without a display. try: image_display = Gimp.Display.new(image) mask_display = Gimp.Display.new(mask) Gimp.displays_flush() time.sleep(1.0) except: pass image.delete() misclassified = misclassified_pixels(mask_layer, truth_layer) sys.stderr.write("%d %d %.2f%% %.3fs\n" % (unclassified, misclassified, (misclassified * 100.0 / unclassified), end - start)) total_unclassified += unclassified total_misclassified += misclassified total_time += end - start truth.delete() if save_output: filename = os.path.join(folder, "output", name + '.png') Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, mask, mask_layer, Gio.file_new_for_path(filename)) mask.delete() # for loop ends try: image_display.delete() mask_display.delete() except NameError: pass sys.stderr.write( "Total: %d %d %.2f%% %.3fs\n" % (total_unclassified, total_misclassified, (total_misclassified * 100.0 / total_unclassified), total_time)) return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())