예제 #1
0
    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())
예제 #2
0
    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
예제 #3
0
파일: dialog.py 프로젝트: bootchk/GimpFu-v3
    def create(fuProcedure, gimpProcedure=None):
        # Configure use_header_bar from Gtk settings, not Gimp settings?
        use_header_bar = Gtk.Settings.get_default().get_property(
            "gtk-dialogs-use-header")

        # TODO i18n ?
        """
        This dialog is only for the plugin/procedure we are implementing
        (not for procedures we may call.)
        So we can use the local FuProcedure instead of the Gimp.Procedure.
        """
        # Configure title the same as the menu label
        # title = gimpProcedure.get_menu_label()  # method of gimpProcedure
        title = fuProcedure.menu_label  # property of fuProcedure

        dialog = GimpUi.Dialog(use_header_bar=use_header_bar, title=title)
        return dialog
예제 #4
0
        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)
예제 #5
0
    def blob_dialog(self):
        """Bring up a dialog prompting for blobipy options.
           Return (response, blur)
        """
        use_header_bar = Gtk.Settings.get_default().get_property(
            "gtk-dialogs-use-header")
        dialog = GimpUi.Dialog(use_header_bar=use_header_bar,
                               title="Give a puffy, 3-D appearance",
                               role="blobypy")

        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()

        self.blurspin = Gtk.SpinButton.new_with_range(0., 50., 1.)
        self.blurspin.set_value(5)
        vbox.pack_start(self.blurspin, False, False, 0)
        self.blurspin.show()

        return dialog
예제 #6
0
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())
예제 #7
0
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())
예제 #8
0
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
예제 #9
0
    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())
예제 #10
0
    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.ui_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
예제 #11
0
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())))
예제 #12
0
    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())
예제 #13
0
    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())
예제 #14
0
def run(procedure, args, data):
    GimpUi.ui_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())
예제 #15
0
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)])