def run(self, procedure, run_mode, image, drawable, args, data): """Called when the user invokes the plug-in from the menu or as "Repeat", or when another plug-in calls it. """ config = procedure.create_config() config.begin_run(image, run_mode, args) blur = config.get_property("blur") blur = args.index(0) if run_mode == Gimp.RunMode.INTERACTIVE: GimpUi.init("blobi.py") dialog = self.blob_dialog() while (True): response = dialog.run() if response == Gtk.ResponseType.OK: dialog.destroy() blur = self.blurspin.get_value() break else: dialog.destroy() return procedure.new_return_values( Gimp.PDBStatusType.CANCEL, GLib.Error()) # Gimp.RunMode.WITH_LAST_VALS should take care of itself print("Running with blur =", blur) self.python_blobify(image, drawable, blur) config.set_property('blur', blur) config.end_run(Gimp.PDBStatusType.SUCCESS) return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def run(fuProcedure, gimpProcedure, config): # , list_wrapped_args, guiable_formal_params): """ assert config is a FuProcedureConfig which wraps a Gimp.ProcedureConfig Is initialized for the given gimpProcedure and config.begin_run(image, run_mode, args) has been called """ GimpUi.init('foo1') # TODO what is this string? procedureDialog = GimpUi.ProcedureDialog.new( gimpProcedure, config._config, # pass the wrapped config fuProcedure.name) # title of dialog is the name of the plugin procedureDialog.fill(None) # ??? # run() returns True if OK was chosen, else Cancel was chosen. was_canceled = not procedureDialog.run() procedureDialog.destroy() # assert (not canceled) => the config contains the user's choice of control values from the dialog users_chosen_control_values = [] if not was_canceled: users_chosen_control_values = config.get_list_of_wrapped_values() # ensure returned values is a list of Python primitives or wrapped Gimp objects return was_canceled, users_chosen_control_values
def run(procedure, args, data): GimpUi.init("python-console.py") namespace = { '__builtins__': __builtins__, '__name__': '__main__', '__doc__': None, 'Babl': gi.repository.Babl, 'cairo': gi.repository.cairo, 'Gdk': gi.repository.Gdk, 'Gegl': gi.repository.Gegl, 'Gimp': gi.repository.Gimp, 'Gio': gi.repository.Gio, 'Gtk': gi.repository.Gtk, 'GdkPixbuf': gi.repository.GdkPixbuf, 'GLib': gi.repository.GLib, 'GObject': gi.repository.GObject, 'Pango': gi.repository.Pango } class GimpConsole(pyconsole.Console): def __init__(self, quit_func=None): banner = ('GIMP %s Python Console\nPython %s\n' % (Gimp.version(), sys.version)) pyconsole.Console.__init__(self, locals=namespace, banner=banner, quit_func=quit_func) def _commit(self): pyconsole.Console._commit(self) Gimp.displays_flush() class ConsoleDialog(GimpUi.Dialog): def __init__(self): use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") GimpUi.Dialog.__init__(self, use_header_bar=use_header_bar) self.set_property("help-id", PROC_NAME) Gtk.Window.set_title(self, _("Python Console")) Gtk.Window.set_role(self, PROC_NAME) Gtk.Dialog.add_button(self, "_Save", Gtk.ResponseType.OK) Gtk.Dialog.add_button(self, "Cl_ear", RESPONSE_CLEAR) Gtk.Dialog.add_button(self, _("_Browse..."), RESPONSE_BROWSE) Gtk.Dialog.add_button(self, "_Close", Gtk.ResponseType.CLOSE) Gtk.Widget.set_name(self, PROC_NAME) GimpUi.Dialog.set_alternative_button_order_from_array( self, [ Gtk.ResponseType.CLOSE, RESPONSE_BROWSE, RESPONSE_CLEAR, Gtk.ResponseType.OK ]) self.cons = GimpConsole(quit_func=lambda: Gtk.main_quit()) self.style_set(None, None) self.connect('response', self.response) self.connect('style-set', self.style_set) self.browse_dlg = None self.save_dlg = None vbox = Gtk.VBox(homogeneous=False, spacing=12) vbox.set_border_width(12) contents_area = Gtk.Dialog.get_content_area(self) contents_area.pack_start(vbox, True, True, 0) scrl_win = Gtk.ScrolledWindow() scrl_win.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS) vbox.pack_start(scrl_win, True, True, 0) scrl_win.add(self.cons) width, height = self.cons.get_default_size() minreq, requisition = Gtk.Widget.get_preferred_size( scrl_win.get_vscrollbar()) sb_width = requisition.width sb_height = requisition.height # Account for scrollbar width and border width to ensure # the text view gets a width of 80 characters. We don't care # so much whether the height will be exactly 40 characters. Gtk.Window.set_default_size(self, width + sb_width + 2 * 12, height) def style_set(self, old_style, user_data): pass #style = Gtk.Widget.get_style (self) #self.cons.stdout_tag.set_property ("foreground", style.text[Gtk.StateType.NORMAL]) #self.cons.stderr_tag.set_property ("foreground", style.text[Gtk.StateType.INSENSITIVE]) def response(self, dialog, response_id): if response_id == RESPONSE_BROWSE: self.browse() elif response_id == RESPONSE_CLEAR: self.cons.banner = None self.cons.clear() elif response_id == Gtk.ResponseType.OK: self.save_dialog() else: Gtk.main_quit() self.cons.grab_focus() def command_for_procedure(self, proc_name): ''' Assemble string of Python code that when eval'd will call proc_name with contrived arguments. The purpose is to generate a template for a call to the PDB procedure the user has selected. The call MIGHT work as is. Otherwise, the names of the arguments might be enough that the user can figure out how to edit the template. The code will run in the environment of the console/browser, which is not the GIMP v2 GimpFu environment but the GIMP v3 PyGObject introspected environment. If ever GimpFu module is resurrected, and Python console imports it, then revert this code to its v2 form. ''' proc = Gimp.get_pdb().lookup_procedure(proc_name) if proc is None: return None cmd = '' return_values = proc.get_return_values() # assert is list of GParamSpec ''' Cat str of variable names to which unpack return values Variable names same as return value name, mangled Str like: 'retval_1, ret_val_2 = ' ''' if len(return_values) > 0: cmd += ', '.join( x.name.replace('-', '_') for x in return_values) cmd += ' = ' # else is a void PDB procedure ''' Cat prefix of str for a call to procedure name Prefix like: Gimp.get_pdb().run_procedure('<foo>', Note: - proc name is quoted, run_procedure wants a string. - proc name has hyphens. Not a Python name. Matches name in PDB. - trailing comma, another arg to follow: run_procedure takes two args: string name, and GValueArray of args ''' cmd += f"Gimp.get_pdb().run_procedure('{proc_name}', " ''' Assemble argument string. Using names of formal args, which might not match names already defined in the browsing environment (the REPL). Args are passed to a PDB procedure in a GValueArray. Assemble a string like '[arg_1, arg_2]'. When eval'd, the Python binding will convert to a GValueArray. ''' param_specs = proc.get_arguments() cmd += '[ ' ''' Special handling for run mode. GIMP v2: GimpFu had different handling for run mode. Insure run mode interactive, i.e. called procedure may open a GUI. This assumes that procedures use the same formal name for runmode arg. There might be rare other cases, especially for third party plugins? E.G. See formal signature of file-gex-load There is no other way to distinguish the run mode formal argument, as its formal type is GimpParamEnum, a generic enum. ''' if len(param_specs) > 0 and param_specs[0].name == 'run-mode': cmd += 'Gimp.RunMode.INTERACTIVE, ' param_specs = param_specs[1:] # else doesn't take a run mode arg # Cat string of arg names to a call # Like: 'arg_1, arg_2' where formal names arg-1 and arg-2 cmd += ', '.join(x.name.replace('-', '_') for x in param_specs) # terminate the arg array, and close parens the call cmd += '])' return cmd def browse_response(self, dlg, response_id): if response_id != Gtk.ResponseType.APPLY: Gtk.Widget.hide(dlg) return proc_name = dlg.get_selected() if not proc_name: # Apply button was enabled without a selection? return cmd = self.command_for_procedure(proc_name) if cmd is None: # Should not happen. We browsed a name not in the PDB? return buffer = self.cons.buffer lines = buffer.get_line_count() iter = buffer.get_iter_at_line_offset(lines - 1, 4) buffer.delete(iter, buffer.get_end_iter()) buffer.place_cursor(buffer.get_end_iter()) buffer.insert_at_cursor(cmd) # not insert a newline, user can edit and then "enter" the command def browse(self): if not self.browse_dlg: use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") dlg = GimpUi.ProcBrowserDialog(use_header_bar=use_header_bar) Gtk.Window.set_title(dlg, _("Python Procedure Browser")) Gtk.Window.set_role(dlg, PROC_NAME) Gtk.Dialog.add_button(dlg, "Apply", Gtk.ResponseType.APPLY) Gtk.Dialog.add_button(dlg, "Close", Gtk.ResponseType.CLOSE) Gtk.Dialog.set_default_response(self, Gtk.ResponseType.OK) GimpUi.Dialog.set_alternative_button_order_from_array( dlg, [Gtk.ResponseType.CLOSE, Gtk.ResponseType.APPLY]) dlg.connect('response', self.browse_response) dlg.connect('row-activated', lambda dlg: dlg.response(Gtk.ResponseType.APPLY)) self.browse_dlg = dlg Gtk.Window.present(self.browse_dlg) def save_response(self, dlg, response_id): if response_id == Gtk.ResponseType.DELETE_EVENT: self.save_dlg = None return elif response_id == Gtk.ResponseType.OK: filename = dlg.get_filename() try: logfile = open(filename, 'w') except IOError as e: Gimp.message( _("Could not open '%s' for writing: %s") % (filename, e.strerror)) return buffer = self.cons.buffer start = buffer.get_start_iter() end = buffer.get_end_iter() log = buffer.get_text(start, end, False) try: logfile.write(log) logfile.close() except IOError as e: Gimp.message( _("Could not write to '%s': %s") % (filename, e.strerror)) return Gtk.Widget.hide(dlg) def save_dialog(self): if not self.save_dlg: dlg = Gtk.FileChooserDialog() Gtk.Window.set_title(dlg, _("Save Python-Fu Console Output")) Gtk.Window.set_transient_for(dlg, self) Gtk.Dialog.add_button(dlg, "_Cancel", Gtk.ResponseType.CANCEL) Gtk.Dialog.add_button(dlg, "_Save", Gtk.ResponseType.OK) Gtk.Dialog.set_default_response(self, Gtk.ResponseType.OK) GimpUi.Dialog.set_alternative_button_order_from_array( dlg, [Gtk.ResponseType.OK, Gtk.ResponseType.CANCEL]) dlg.connect('response', self.save_response) self.save_dlg = dlg self.save_dlg.present() def run(self): Gtk.Widget.show_all(self) Gtk.main() ConsoleDialog().run() return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
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(procedure, run_mode, image, n_layers, layers, args, data): gio_file = args.index(0) bucket_size = args.index(1) sample_average = args.index(2) output_format = args.index(3) progress_bar = None config = None if run_mode == Gimp.RunMode.INTERACTIVE: config = procedure.create_config() # Set properties from arguments. These properties will be changed by the UI. #config.set_property("file", gio_file) #config.set_property("bucket_size", bucket_size) #config.set_property("sample_average", sample_average) #config.set_property("output_format", output_format) config.begin_run(image, run_mode, args) GimpUi.init("histogram-export.py") use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") dialog = GimpUi.Dialog(use_header_bar=use_header_bar, title=_("Histogram Export...")) dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("_OK", Gtk.ResponseType.OK) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10) dialog.get_content_area().add(vbox) vbox.show() # Create grid to set all the properties inside. grid = Gtk.Grid() grid.set_column_homogeneous(False) grid.set_border_width(10) grid.set_column_spacing(10) grid.set_row_spacing(10) vbox.add(grid) grid.show() # UI for the file parameter def choose_file(widget): if file_chooser_dialog.run() == Gtk.ResponseType.OK: if file_chooser_dialog.get_file() is not None: config.set_property("file", file_chooser_dialog.get_file()) file_entry.set_text( file_chooser_dialog.get_file().get_path()) file_chooser_dialog.hide() file_chooser_button = Gtk.Button.new_with_mnemonic(label=_("_File...")) grid.attach(file_chooser_button, 0, 0, 1, 1) file_chooser_button.show() file_chooser_button.connect("clicked", choose_file) file_entry = Gtk.Entry.new() grid.attach(file_entry, 1, 0, 1, 1) file_entry.set_width_chars(40) file_entry.set_placeholder_text(_("Choose export file...")) if gio_file is not None: file_entry.set_text(gio_file.get_path()) file_entry.show() file_chooser_dialog = Gtk.FileChooserDialog( use_header_bar=use_header_bar, title=_("Histogram Export file..."), action=Gtk.FileChooserAction.SAVE) file_chooser_dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) file_chooser_dialog.add_button("_OK", Gtk.ResponseType.OK) # Bucket size parameter label = Gtk.Label.new_with_mnemonic(_("_Bucket Size")) grid.attach(label, 0, 1, 1, 1) label.show() spin = GimpUi.prop_spin_button_new(config, "bucket_size", step_increment=0.001, page_increment=0.1, digits=3) grid.attach(spin, 1, 1, 1, 1) spin.show() # Sample average parameter spin = GimpUi.prop_check_button_new(config, "sample_average", _("Sample _Average")) spin.set_tooltip_text( _("If checked, the histogram is generated from merging all visible layers." " Otherwise, the histogram is only for the current layer.")) grid.attach(spin, 1, 2, 1, 1) spin.show() # Output format parameter label = Gtk.Label.new_with_mnemonic(_("_Output Format")) grid.attach(label, 0, 3, 1, 1) label.show() combo = GimpUi.prop_string_combo_box_new( config, "output_format", output_format_enum.get_tree_model(), 0, 1) grid.attach(combo, 1, 3, 1, 1) combo.show() progress_bar = Gtk.ProgressBar() vbox.add(progress_bar) progress_bar.show() dialog.show() if dialog.run() != Gtk.ResponseType.OK: return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) # Extract values from UI gio_file = Gio.file_new_for_path( file_entry.get_text()) # config.get_property("file") bucket_size = config.get_property("bucket_size") sample_average = config.get_property("sample_average") output_format = config.get_property("output_format") if gio_file is None: error = 'No file given' return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) result = histogram_export(procedure, image, layers, gio_file, bucket_size, sample_average, output_format, progress_bar) # If the execution was successful, save parameters so they will be restored next time we show dialog. if result.index(0) == Gimp.PDBStatusType.SUCCESS and config is not None: config.end_run(Gimp.PDBStatusType.SUCCESS) return result
def run(self, procedure, args, run_data): GimpUi.init("pydev-client") dialog = GimpUi.Dialog(use_header_bar=True, title=_("PyDev Client"), role="pydev-client") 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() label = Gtk.Label(label=head_text) box.pack_start(label, False, False, 1) label.show() contents = "Hola Mundo!" 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() th = mp.Process(target=set_trace) th.start() while True: response = dialog.run() if response == Gtk.ResponseType.OK: dialog.destroy() break elif response == Gtk.ResponseType.APPLY: url = "https://github.com/isman7/gimp-python-development" Gio.app_info_launch_default_for_uri(url, None) continue else: th.terminate() dialog.destroy() return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) th.terminate() return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def gradient_css_save(procedure, args, data): if args.length() != 3: error = 'Wrong parameters given' return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) runmode = args.index(0) gradient = args.index(1) file = args.index(2) if runmode == Gimp.RunMode.INTERACTIVE: # Interactive mode works on active gradient. gradient = Gimp.context_get_gradient() # Pop-up a file chooser for target file. gi.require_version('Gtk', '3.0') from gi.repository import Gtk GimpUi.init("gradients-save-as-css.py") use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") dialog = Gtk.FileChooserDialog(use_header_bar=use_header_bar, title=_("CSS file..."), action=Gtk.FileChooserAction.SAVE) dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("_OK", Gtk.ResponseType.OK) dialog.show() if dialog.run() == Gtk.ResponseType.OK: file = dialog.get_file() else: return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) if file is None: error = 'No file given' return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) stops = [] wk_stops = [] n_segments = Gimp.gradient_get_number_of_segments(gradient) last_stop = None for index in range(n_segments): success, lcolor, lopacity = Gimp.gradient_segment_get_left_color( gradient, index) success, rcolor, ropacity = Gimp.gradient_segment_get_right_color( gradient, index) success, lpos = Gimp.gradient_segment_get_left_pos(gradient, index) success, rpos = Gimp.gradient_segment_get_right_pos(gradient, index) lstop = color_to_html(lcolor) + " %d%%" % int(100 * lpos) wk_lstop = "color-stop(%.03f, %s)" % (lpos, color_to_html(lcolor)) if lstop != last_stop: stops.append(lstop) wk_stops.append(wk_lstop) rstop = color_to_html(rcolor) + " %d%%" % int(100 * rpos) wk_rstop = "color-stop(%.03f, %s)" % (rpos, color_to_html(rcolor)) stops.append(rstop) wk_stops.append(wk_rstop) last_stop = rstop final_text = w3c_template % ", ".join(stops) final_text += moz_template % ",".join(stops) final_text += webkit_template % ",".join(wk_stops) success, etag = file.replace_contents( bytes(format_text(final_text), encoding='utf-8'), etag=None, make_backup=False, flags=Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable=None) if success: return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error()) else: return procedure.new_return_values( Gimp.PDBStatusType.EXECUTION_ERROR, GLib.Error('File saving failed: {}'.format(file.get_path())))
def run(self, procedure, run_mode, image, n_drawables, drawables, args, run_data): if n_drawables != 1: msg = _("Procedure '{}' only works with one drawable.").format( procedure.get_name()) error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), msg, 0) return procedure.new_return_values( Gimp.PDBStatusType.CALLING_ERROR, error) else: drawable = drawables[0] 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.init("goat-exercise-py3.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/extensions/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 run(self, procedure, run_mode, image, n_drawables, drawables, args, run_data): if n_drawables != 1: msg = _( f"Procedure '{procedure.get_name()}' only works with one drawable." ) error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), msg, 0) return procedure.new_return_values( Gimp.PDBStatusType.CALLING_ERROR, error) else: drawable = drawables[0] # check if selection exist selection = image.get_selection() flag, non_empty, x1, y1, x2, y2 = selection.bounds(image) if not non_empty: msg = _( f"The selection is empty, create a selection box and precede with the use of this plugin." ) error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), msg, 0) return procedure.new_return_values( Gimp.PDBStatusType.CALLING_ERROR, error) 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.init("add_balloon.py") dialog = GimpUi.Dialog(use_header_bar=True, title=_("Add Balloon)"), role="add_balloon-Python3") dialog.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) 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() # Label text content label = Gtk.Label(label='Text:') box.pack_start(label, False, False, 1) label.show() # scroll area for text scrolled = Gtk.ScrolledWindow() scrolled.set_vexpand(True) box.pack_start(scrolled, True, True, 1) scrolled.show() # text content box text_content = Gtk.TextView() contents = 'text' buffer = text_content.get_buffer() buffer.set_text(contents, -1) scrolled.add(text_content) text_content.show() # Improve UI font_chooser = Gtk.FontChooserWidget() box.pack_start(font_chooser, False, False, 1) font_chooser.show() # TODO add spinner for waiting while (True): response = dialog.run() if response == Gtk.ResponseType.OK: # TODO enable spinner and lock all other values # layer position position = Gimp.get_pdb().run_procedure( 'gimp-image-get-item-position', [image, drawable]).index(1) # Create group layer_group = Gimp.get_pdb().run_procedure( 'gimp-layer-group-new', [image]).index(1) image.insert_layer(layer_group, None, position) # add new trasparent layer overlay_layer = Gimp.Layer.new(image, 'hide_background', drawable.get_width(), drawable.get_height(), Gimp.ImageType.RGBA_IMAGE, 100.0, Gimp.LayerMode.NORMAL) image.insert_layer(overlay_layer, layer_group, position) overlay_layer.fill(Gimp.FillType.TRANSPARENT) # add white fill the selection Gimp.get_pdb().run_procedure( 'gimp-drawable-edit-fill', [overlay_layer, Gimp.FillType.WHITE]) # add text layer buffer = text_content.get_buffer() text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True) font_str = font_chooser.get_font() font_size = float(font_str.split(' ')[-1]) font_name = ' '.join(font_str.split(' ')[0:-2]) text_layer = Gimp.get_pdb().run_procedure( 'gimp-text-layer-new', [image, text, font_name, font_size, 3]).index(1) image.insert_layer(text_layer, layer_group, position - 1) # center text layer Gimp.get_pdb().run_procedure( 'gimp-text-layer-set-justification', [text_layer, 2]) cx = (x1 + x2) / 2 - text_layer.get_width() / 2 cy = (y1 + y2) / 2 - text_layer.get_height() / 2 Gimp.get_pdb().run_procedure( 'gimp-item-transform-translate', [text_layer, cx, cy]) # set selected layer image.set_selected_layers([layer_group]) dialog.destroy() break else: dialog.destroy() return procedure.new_return_values( Gimp.PDBStatusType.CANCEL, GLib.Error()) return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
def run(self, procedure, args, data): palette = None amount = 1 # Get the parameters if args.length() < 1: error = 'No parameters given' return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) runmode = args.index(0) if args.length() > 1: palette = args.index(1) if palette == '' or palette is None: palette = Gimp.context_get_palette() (exists, num_colors) = Gimp.palette_get_info(palette) if not exists: error = 'Unknown palette: {}'.format(palette) return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) if args.length() > 2: amount = args.index(2) if runmode == Gimp.RunMode.INTERACTIVE: gi.require_version('Gtk', '3.0') from gi.repository import Gtk GimpUi.init ("palette-offset.py") use_header_bar = Gtk.Settings.get_default().get_property("gtk-dialogs-use-header") dialog = GimpUi.Dialog(use_header_bar=use_header_bar, title=_("Offset Palette...")) dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("_OK", Gtk.ResponseType.OK) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=12) dialog.get_content_area().add(box) box.show() label = Gtk.Label.new_with_mnemonic("Off_set") box.pack_start(label, False, False, 1) label.show() amount = self.set_property("amount", amount) spin = GimpUi.prop_spin_button_new(self, "amount", 1.0, 5.0, 0) spin.set_activates_default(True) box.pack_end(spin, False, False, 1) spin.show() dialog.show() if dialog.run() != Gtk.ResponseType.OK: return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) amount = self.get_property("amount") #If palette is read only, work on a copy: editable = Gimp.palette_is_editable(palette) if not editable: palette = Gimp.palette_duplicate (palette) tmp_entry_array = [] for i in range (num_colors): tmp_entry_array.append ((Gimp.palette_entry_get_name (palette, i)[1], Gimp.palette_entry_get_color (palette, i)[1])) for i in range (num_colors): target_index = i + amount if target_index >= num_colors: target_index -= num_colors elif target_index < 0: target_index += num_colors Gimp.palette_entry_set_name (palette, target_index, tmp_entry_array[i][0]) Gimp.palette_entry_set_color (palette, target_index, tmp_entry_array[i][1]) retval = procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error()) value = GObject.Value(GObject.TYPE_STRING, palette) retval.remove(1) retval.insert(1, value) return retval
def save_colorxhtml(procedure, run_mode, image, n_layers, layers, file, args, data): source_file = args.index(0) characters = args.index(1) size = args.index(2) separate = args.index(3) if file is None: error = 'No file given' return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(error)) if run_mode == Gimp.RunMode.INTERACTIVE: gi.require_version('Gtk', '3.0') from gi.repository import Gtk GimpUi.init("file-colorxhtml-save") use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") dialog = Gtk.Dialog(use_header_bar=use_header_bar, title=_("Save as colored HTML text...")) dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("_OK", Gtk.ResponseType.OK) choose_file_dialog = Gtk.FileChooserDialog( use_header_bar=use_header_bar, title=_("Read characters from file..."), action=Gtk.FileChooserAction.OPEN) choose_file_dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL) choose_file_dialog.add_button("_OK", Gtk.ResponseType.OK) def choose_file(button, user_data=None): choose_file_dialog.show() if choose_file_dialog.run() == Gtk.ResponseType.OK: characters_entry.set_text(choose_file_dialog.get_filename()) choose_file_dialog.hide() grid = Gtk.Grid() grid.set_column_homogeneous(False) grid.set_border_width(10) grid.set_column_spacing(10) grid.set_row_spacing(10) row = 0 label = Gtk.Label(label=_("Characters")) label.set_tooltip_text( _("Characters that will be used as colored pixels. ")) grid.attach(label, 0, row, 1, 1) label.show() characters_entry = Gtk.Entry() characters_entry.set_width_chars(20) characters_entry.set_max_width_chars(80) characters_entry.set_text(characters) characters_entry.set_placeholder_text(_("Characters or file location")) grid.attach(characters_entry, 1, row, 1, 1) characters_entry.show() row += 1 characters_checkbox = Gtk.CheckButton( label=_("Read characters from file")) characters_checkbox.set_active(source_file) characters_checkbox.set_tooltip_text( _("If set, the Characters text entry will be used as a file name, " "from which the characters will be read. Otherwise, the characters " "in the text entry will be used to render the image.")) grid.attach(characters_checkbox, 0, row, 1, 1) characters_checkbox.show() choose_file_button = Gtk.Button(label=_("Choose file")) grid.attach(choose_file_button, 1, row, 1, 1) choose_file_button.connect("clicked", choose_file) choose_file_button.show() row += 1 label = Gtk.Label(label=_("Font Size(px)")) grid.attach(label, 0, row, 1, 1) label.show() font_size_adj = Gtk.Adjustment.new(size, 0.0, 100.0, 1.0, 0.0, 0.0) font_size_spin = Gtk.SpinButton.new(font_size_adj, climb_rate=1.0, digits=0) font_size_spin.set_numeric(True) grid.attach(font_size_spin, 1, row, 1, 1) font_size_spin.show() row += 1 separate_checkbox = Gtk.CheckButton(label=_("Write separate CSS file")) separate_checkbox.set_active(separate) grid.attach(separate_checkbox, 0, row, 2, 1) separate_checkbox.show() dialog.get_content_area().add(grid) grid.show() dialog.show() if dialog.run() == Gtk.ResponseType.OK: separate = separate_checkbox.get_active() size = font_size_spin.get_value_as_int() source_file = characters_checkbox.get_active() characters = characters_entry.get_text() else: return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error()) #For now, work with a single layer layer = layers[0] width = layer.get_width() height = layer.get_height() bpp = layer.get_bpp() html = open(file.peek_path(), 'w') if separate: dirname, cssfile = os.path.split(file.peek_path()) cssfile = os.path.splitext(cssfile)[0] + '.css' cssname = os.path.join(dirname, cssfile) css = open(cssname, 'w') if source_file: characters_file = open(characters, 'r') chars = characters_file.read() characters_file.close() else: chars = characters # Remove unprintable characters from "chars". # TODO: This only handles ascii files. It would be nice to handle unicode # files, so this could work for any language. goodchars = string.digits + string.ascii_letters + string.punctuation badchars = ''.join(chr(i) for i in range(256) if chr(i) not in goodchars) allchars = str.maketrans('', '', badchars) chars = chars.translate(allchars) data = [escape_table.get(c, c) for c in chars] if data: data.reverse() else: data = list('X' * 80) Gimp.progress_init(_("Saving as colored XHTML")) style = style_def % size if separate: ss = '<link rel="stylesheet" type="text/css" href="%s" />' % cssfile css.write(style) else: ss = '<style type="text/css">\n%s</style>' % style html.write(preamble % ss) colors = {} chars = [] # Constants used for formatting the pixel color. We can handle image # types where each color is 8 bits, 16 bits, or 32 bit integers. fmt = fmt_from_bpp[bpp] pixel_shift = 8 * (bpp // 3 - 1) for y in range(0, height): # The characters in "chars" will be used to draw the next row. # Lets fill "chars" with data so it has at least enough characters. while len(chars) < width: chars[0:0] = data for x in range(0, width): pixel_bytes = layer.get_pixel(x, y) pixel_tuple = struct.unpack(fmt, pixel_bytes) if bpp > 3: pixel_tuple = ( pixel_tuple[0] >> pixel_shift, pixel_tuple[1] >> pixel_shift, pixel_tuple[2] >> pixel_shift, ) color = '%02x%02x%02x' % pixel_tuple style = 'background-color:black; color:#%s;' % color char = chars.pop() if separate: if color not in colors: css.write('span.N%s { %s }\n' % (color, style)) colors[color] = 1 html.write('<span class="N%s">%s</span>' % (color, char)) else: html.write('<span style="%s">%s</span>' % (style, char)) html.write('\n') Gimp.progress_update(y / float(height)) html.write(postamble) html.close() if separate: css.close() return Gimp.ValueArray.new_from_values( [GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS)])
def run(procedure, args, data): GimpUi.init("python-console.py") namespace = { '__builtins__': __builtins__, '__name__': '__main__', '__doc__': None, 'Babl': gi.repository.Babl, 'cairo': gi.repository.cairo, 'Gdk': gi.repository.Gdk, 'Gegl': gi.repository.Gegl, 'Gimp': gi.repository.Gimp, 'Gio': gi.repository.Gio, 'Gtk': gi.repository.Gtk, 'GdkPixbuf': gi.repository.GdkPixbuf, 'GLib': gi.repository.GLib, 'GObject': gi.repository.GObject, 'Pango': gi.repository.Pango } class GimpConsole(pyconsole.Console): def __init__(self, quit_func=None): banner = ('GIMP %s Python Console\nPython %s\n' % (Gimp.version(), sys.version)) pyconsole.Console.__init__(self, locals=namespace, banner=banner, quit_func=quit_func) def _commit(self): pyconsole.Console._commit(self) Gimp.displays_flush() class ConsoleDialog(GimpUi.Dialog): def __init__(self): use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") GimpUi.Dialog.__init__(self, use_header_bar=use_header_bar) self.set_property("help-id", PROC_NAME) Gtk.Window.set_title(self, _("Python Console")) Gtk.Window.set_role(self, PROC_NAME) Gtk.Dialog.add_button(self, "_Save", Gtk.ResponseType.OK) Gtk.Dialog.add_button(self, "Cl_ear", RESPONSE_CLEAR) Gtk.Dialog.add_button(self, _("_Browse..."), RESPONSE_BROWSE) Gtk.Dialog.add_button(self, "_Close", Gtk.ResponseType.CLOSE) Gtk.Widget.set_name(self, PROC_NAME) GimpUi.Dialog.set_alternative_button_order_from_array( self, [ Gtk.ResponseType.CLOSE, RESPONSE_BROWSE, RESPONSE_CLEAR, Gtk.ResponseType.OK ]) self.cons = GimpConsole(quit_func=lambda: Gtk.main_quit()) self.style_set(None, None) self.connect('response', self.response) self.connect('style-set', self.style_set) self.browse_dlg = None self.save_dlg = None vbox = Gtk.VBox(homogeneous=False, spacing=12) vbox.set_border_width(12) contents_area = Gtk.Dialog.get_content_area(self) contents_area.pack_start(vbox, True, True, 0) scrl_win = Gtk.ScrolledWindow() scrl_win.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS) vbox.pack_start(scrl_win, True, True, 0) scrl_win.add(self.cons) width, height = self.cons.get_default_size() minreq, requisition = Gtk.Widget.get_preferred_size( scrl_win.get_vscrollbar()) sb_width = requisition.width sb_height = requisition.height # Account for scrollbar width and border width to ensure # the text view gets a width of 80 characters. We don't care # so much whether the height will be exactly 40 characters. Gtk.Window.set_default_size(self, width + sb_width + 2 * 12, height) def style_set(self, old_style, user_data): pass #style = Gtk.Widget.get_style (self) #self.cons.stdout_tag.set_property ("foreground", style.text[Gtk.StateType.NORMAL]) #self.cons.stderr_tag.set_property ("foreground", style.text[Gtk.StateType.INSENSITIVE]) def response(self, dialog, response_id): if response_id == RESPONSE_BROWSE: self.browse() elif response_id == RESPONSE_CLEAR: self.cons.banner = None self.cons.clear() elif response_id == Gtk.ResponseType.OK: self.save_dialog() else: Gtk.main_quit() self.cons.grab_focus() def browse_response(self, dlg, response_id): if response_id != Gtk.ResponseType.APPLY: Gtk.Widget.hide(dlg) return proc_name = dlg.get_selected() if not proc_name: return proc = pdb[proc_name] cmd = '' if len(proc.return_vals) > 0: cmd = ', '.join(x[1].replace('-', '_') for x in proc.return_vals) + ' = ' cmd = cmd + 'pdb.%s' % proc.proc_name.replace('-', '_') if len(proc.params) > 0 and proc.params[0][1] == 'run-mode': params = proc.params[1:] else: params = proc.params cmd = cmd + '(%s)' % ', '.join(x[1].replace('-', '_') for x in params) buffer = self.cons.buffer lines = buffer.get_line_count() iter = buffer.get_iter_at_line_offset(lines - 1, 4) buffer.delete(iter, buffer.get_end_iter()) buffer.place_cursor(buffer.get_end_iter()) buffer.insert_at_cursor(cmd) def browse(self): if not self.browse_dlg: use_header_bar = Gtk.Settings.get_default().get_property( "gtk-dialogs-use-header") dlg = GimpUi.ProcBrowserDialog(use_header_bar=use_header_bar) Gtk.Window.set_title(dlg, _("Python Procedure Browser")) Gtk.Window.set_role(dlg, PROC_NAME) Gtk.Dialog.add_button(dlg, "Apply", Gtk.ResponseType.APPLY) Gtk.Dialog.add_button(dlg, "Close", Gtk.ResponseType.CLOSE) Gtk.Dialog.set_default_response(self, Gtk.ResponseType.OK) GimpUi.Dialog.set_alternative_button_order_from_array( dlg, [Gtk.ResponseType.CLOSE, Gtk.ResponseType.APPLY]) dlg.connect('response', self.browse_response) dlg.connect('row-activated', lambda dlg: dlg.response(Gtk.ResponseType.APPLY)) self.browse_dlg = dlg Gtk.Window.present(self.browse_dlg) def save_response(self, dlg, response_id): if response_id == Gtk.ResponseType.DELETE_EVENT: self.save_dlg = None return elif response_id == Gtk.ResponseType.OK: filename = dlg.get_filename() try: logfile = open(filename, 'w') except IOError as e: Gimp.message( _("Could not open '%s' for writing: %s") % (filename, e.strerror)) return buffer = self.cons.buffer start = buffer.get_start_iter() end = buffer.get_end_iter() log = buffer.get_text(start, end, False) try: logfile.write(log) logfile.close() except IOError as e: Gimp.message( _("Could not write to '%s': %s") % (filename, e.strerror)) return Gtk.Widget.hide(dlg) def save_dialog(self): if not self.save_dlg: dlg = Gtk.FileChooserDialog() Gtk.Window.set_title(dlg, _("Save Python-Fu Console Output")) Gtk.Window.set_transient_for(dlg, self) Gtk.Dialog.add_button(dlg, "_Cancel", Gtk.ResponseType.CANCEL) Gtk.Dialog.add_button(dlg, "_Save", Gtk.ResponseType.OK) Gtk.Dialog.set_default_response(self, Gtk.ResponseType.OK) GimpUi.Dialog.set_alternative_button_order_from_array( dlg, [Gtk.ResponseType.OK, Gtk.ResponseType.CANCEL]) dlg.connect('response', self.save_response) self.save_dlg = dlg self.save_dlg.present() def run(self): Gtk.Widget.show_all(self) Gtk.main() ConsoleDialog().run() return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())