RE: Troubles with porting a script to Gimp3 - nchen - 03-19-2025
(03-05-2025, 10:44 PM)alvk Wrote: (03-05-2025, 05:26 PM)Ofnuts Wrote: I can help with the API, but not with Scheme... If I had to do something like this, i would rewrite the whole thing in Python...
I agree with you, that Python would be better, but I'm not a programmer and rewriting this code in Python is far beyond my expertise. Anyway, thank you for your suggestions. You helped me a lot.
I took a crack at it, but I have no idea how fonts work in 3.0.
So here's some code that almost works, but can't read fonts or apply vectors
Maybe someone can suggest improvements or fix it from here... 3.0 API is a learning curve.
Also sorry for the bad UI, just threw it together without aesthetics in mind.
Code:
#!/usr/bin/env python3
import sys
from gi.repository import Gimp, GLib
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gegl
class text_plugin_window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Text Label")
self.set_default_size(600, 700) # width, height
# Apply CSS
self.apply_css()
# Main layout
self.main_box = Gtk.VBox(spacing=10)
self.add(self.main_box)
# Scrolled Window
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.main_box.pack_start(scrolled_window, True, True, 0)
content_box = Gtk.VBox(spacing=10)
content_box.set_border_width(15)
content_box.get_style_context().add_class("content_box")
scrolled_window.add(content_box)
self.input_textview = self.add_labeled_textview(content_box, "Text Input:")
self.font_chooser = Gtk.FontChooserWidget()
self.font_chooser.set_font("Arial")
content_box.pack_start(self.font_chooser, False, False, 0)
self.font_color_button = Gtk.Button(label="Choose Color")
self.font_color_button.connect("clicked", self.on_font_color_button_clicked)
content_box.pack_start(self.font_color_button, False, False, 0)
x_offset_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
x_offset_box.get_style_context().add_class("offset_box")
x_offset_label = Gtk.Label(label="Offset X:")
x_offset_label.get_style_context().add_class("offset_label")
x_offset_box.pack_start(x_offset_label, False, False, 0)
self.x_offset_spin = Gtk.SpinButton.new_with_range(0, 99999, 1)
self.x_offset_spin.set_value(0)
x_offset_box.pack_start(self.x_offset_spin, True, True, 0)
content_box.pack_start(x_offset_box, False, False, 0)
y_offset_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
y_offset_box.get_style_context().add_class("offset_box")
y_offset_label = Gtk.Label(label="Offset Y:")
y_offset_label.get_style_context().add_class("offset_label")
y_offset_box.pack_start(y_offset_label, False, False, 0)
self.y_offset_spin = Gtk.SpinButton.new_with_range(0, 99999, 1)
self.y_offset_spin.set_value(0)
y_offset_box.pack_start(self.y_offset_spin, True, True, 0)
content_box.pack_start(y_offset_box, False, False, 0)
outline_toggle_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
outline_toggle_box.get_style_context().add_class("toggle_box")
outline_label = Gtk.Label(label="Outline:")
outline_toggle_box.pack_start(outline_label, False, False, 0)
self.toggle_outline = Gtk.CheckButton()
outline_toggle_box.pack_start(self.toggle_outline, False, False, 0)
content_box.pack_start(outline_toggle_box, False, False, 0)
self.outline_color_button = Gtk.Button(label="Choose Color")
self.outline_color_button.connect("clicked", self.on_outline_color_button_clicked)
content_box.pack_start(self.outline_color_button, False, False, 0)
outline_thickness_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
x_offset_box.get_style_context().add_class("thick_box")
outline_thickness_label = Gtk.Label(label="Outline Thickness:")
outline_thickness_label.get_style_context().add_class("offset_label")
outline_thickness_box.pack_start(outline_thickness_label, False, False, 0)
self.outline_thickness_spin = Gtk.SpinButton.new_with_range(0, 99999, 1)
self.outline_thickness_spin.set_value(0)
outline_thickness_box.pack_start(self.outline_thickness_spin, True, True, 0)
content_box.pack_start(outline_thickness_box, False, False, 0)
# OK Button
ok_button_box = Gtk.Box(spacing=0)
ok_button_box.get_style_context().add_class("ok_button_box")
ok_button_box.set_border_width(15)
self.ok_button = Gtk.Button(label="OK")
self.ok_button.connect("clicked", self.on_ok_clicked)
self.ok_button.get_style_context().add_class("ok_button")
ok_button_box.pack_start(self.ok_button, True, False, 0)
self.main_box.pack_start(ok_button_box, False, False, 0)
# Set result storage
self.results = []
self.connect("destroy", Gtk.main_quit)
def on_font_color_button_clicked(self, widget):
color_dialog = Gtk.ColorChooserDialog("Select Color", self)
response = color_dialog.run()
if response == Gtk.ResponseType.OK:
rgba = color_dialog.get_rgba()
self.selected_font_color = rgba
color_dialog.destroy()
def on_outline_color_button_clicked(self, widget):
color_dialog = Gtk.ColorChooserDialog("Select Color", self)
response = color_dialog.run()
if response == Gtk.ResponseType.OK:
rgba = color_dialog.get_rgba()
self.selected_outline_color = rgba
color_dialog.destroy()
def apply_css(self):
css = """
window {
background-color: #3C3C3C;
}
label {
color: #DCDCDC;
}
textview {
background-color: #1A1A1A; color: #DCDCDC;
min-height: 100px;
}
.content_box {
padding-left: 20px;
padding-right: 20px;
}
.textview_box {
margin-top: 1rem;
min-height: 100px;
}
.textview_text {
padding: 10px;
border-radius: 3px;
}
.textview_text text {
background-color: #1A1A1A;
color: #DCDCDC;
caret-color: white;
}
.ok_button {
color: #DCDCDC;
background-color: #1A1A1A;
background-image:none;
}
.ok_button:hover {
background-color: #3C3C3C;
}
"""
style_provider = Gtk.CssProvider()
style_provider.load_from_data(css.encode("utf-8"))
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def add_labeled_textview(self, parent, label_text, default_value=""):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
box.get_style_context().add_class("textview_box")
label = Gtk.Label(label=label_text)
textview = Gtk.TextView()
textview.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) # Wrap text at characters (ensures horizontal wrapping)
textview.get_style_context().add_class("textview_text")
# Set default text
buffer = textview.get_buffer()
buffer.set_text(default_value)
box.pack_start(label, False, False, 0)
box.pack_start(textview, False, False, 0)
parent.pack_start(box, False, False, 0)
return textview
def get_text_from_textview(self, textview):
buffer = textview.get_buffer()
start_iter = buffer.get_start_iter()
end_iter = buffer.get_end_iter()
return buffer.get_text(start_iter, end_iter, True)
def on_ok_clicked(self, widget):
try:
self.results = {
"text": self.get_text_from_textview(self.input_textview),
# "font": self.font_chooser.get_font() if self.font_chooser.get_font() else "Arial Regular",
"font": self.font_chooser.get_font_desc(),
"font_size": self.font_chooser.get_font_size(),
"font_color": getattr(self, 'selected_font_color', Gdk.RGBA(0, 0, 0, 1)),
"x_offset": self.x_offset_spin.get_value(),
"y_offset": self.y_offset_spin.get_value(),
"outline_toggle": self.toggle_outline.get_active(),
"outline_color": getattr(self, 'selected_outline_color', Gdk.RGBA(1, 1, 1, 1)),
"outline_thickness": self.outline_thickness_spin.get_value()
}
except Exception as e:
Gimp.message(f"Error: {e}")
self.destroy() # Close the window
def run(self):
self.show_all()
Gtk.main()
return self.results
class text_plugin(Gimp.PlugIn):
def do_query_procedures(self):
return [ "nc-text-plugin" ]
def do_set_i18n (self, name):
return False
def do_create_procedure(self, name):
procedure = Gimp.ImageProcedure.new(self, name,
Gimp.PDBProcType.PLUGIN,
self.add_labels, None)
procedure.set_image_types("*")
procedure.set_menu_label("Add Labels...")
procedure.add_menu_path('<Image>/Text Label')
procedure.set_documentation("Adds labels to a selected layer in GIMP",
"Adds labels with optional outline.",
name)
procedure.set_attribution("Nicholas Chenevey", "Nicholas Chenevey", "2025")
return procedure
def add_labels(self, procedure, run_mode, image, drawables, config, run_data):
try:
if run_mode == Gimp.RunMode.INTERACTIVE:
window = text_plugin_window()
result = window.run()
user_text = result["text"]
# I don't know how to get Gimp.Font from this...
try:
font = result["font"]
Gimp.message(f"Family: {font.get_family()}, Style: {font.get_style()}, Weight: {font.get_weight()}, Stretch: {font.get_stretch()}")
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
except Exception as e:
Gimp.message(f"Error: {e}")
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
font_parts = result["font"].rsplit(' ', 1)
font_name = font_parts[0]
font_size = int(font_parts[1])
font = Gimp.Font.get_by_name(font_name)
font_color = result["font_color"]
font_color = Gegl.Color.new(font_color.to_string())
x = result["x_offset"]
y = result["y_offset"]
outline_toggle = result["outline_toggle"]
outline_color = result["outline_color"]
outline_color = Gegl.Color.new(outline_color.to_string())
outline_thickness = result["outline_thickness"]
success, off_x, off_y = Gimp.Drawable.get_offsets(drawables[0])
text_layer_x = off_x + x
text_layer_y = off_y + y
Gimp.Image.undo_group_start(image)
Gimp.context_push()
text_layer = Gimp.TextLayer.new(image, user_text, font, font_size, Gimp.Unit.pixel())
image.insert_layer(text_layer, None, -1)
text_layer.set_color(font_color)
if outline_toggle:
text_layer_group = Gimp.GroupLayer.new(image)
text_layer_group.set_name("Text_" + text_layer.get_text())
# text_layer_vectors = image.text_layer_to_vectors(text_layer)
text_layer_outline = text_layer.copy()
text_fg_old = Gimp.Context.get_foreground()
image.insert_layer(text_layer_group, None, -1)
# image.insert_vectors(text_layer_vectors, None, -1)
image.reorder_item(text_layer, text_layer_group, 0)
image.insert_layer(text_layer_outline, text_layer_group, -1)
Gimp.Context.set_foreground(outline_color)
Gimp.Context.set_stroke_method(Gimp.StrokeMethod.PAINT)
Gimp.Context.set_line_width(outline_thickness)
# text_layer_outline.edit_stroke_item(text_layer_vectors)
Gimp.Context.set_foreground(text_fg_old)
text_layer_group.set_expanded(False)
text_layer_outline.set_lock_content(True)
text_layer.set_lock_content(True)
Gimp.Image.undo_group_end(image)
Gimp.displays_flush()
Gimp.context_pop()
except Exception as e:
Gimp.message(f"Error: {font_name} and {e}")
return procedure.new_return_values(Gimp.PDBStatusType.ERROR, GLib.Error())
# return text_layer
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
Gimp.main(text_plugin.__gtype__, sys.argv)
RE: Troubles with porting a script to Gimp3 - CmykStudent - 03-20-2025
alvk: Hi! You can actually create GimpFont property for the plugin, and then GIMP will automatically generate a selector for you. You can see example of how to do this in our test dialogue: https://gitlab.gnome.org/GNOME/gimp/-/blob/master/plug-ins/python/test-dialog.py#L158
You can run the test dialogue in GIMP to what it looks like by going to Filters -> Development -> Examples -> Test Dialog.
RE: Troubles with porting a script to Gimp3 - nchen - 03-20-2025
(03-20-2025, 01:18 PM)CmykStudent Wrote: alvk: Hi! You can actually create GimpFont property for the plugin, and then GIMP will automatically generate a selector for you. You can see example of how to do this in our test dialogue: https://gitlab.gnome.org/GNOME/gimp/-/blob/master/plug-ins/python/test-dialog.py#L158
You can run the test dialogue in GIMP to what it looks like by going to Filters -> Development -> Examples -> Test Dialog.
Ahhh, this is so helpful. Thank you! I had never used GTK so working with it directly was painful. Happy that I can make dialogs look correctly in 3.0 now
I'll try to fix the plugin so maybe it will work as the OP desires.
Would you by chance have any advice on how I can implement the vector graphics from the text layer?
RE: Troubles with porting a script to Gimp3 - alvk - 03-21-2025
(03-20-2025, 01:18 PM)CmykStudent Wrote: alvk: Hi! You can actually create GimpFont property for the plugin, and then GIMP will automatically generate a selector for you. You can see example of how to do this in our test dialogue: https://gitlab.gnome.org/GNOME/gimp/-/blob/master/plug-ins/python/test-dialog.py#L158
You can run the test dialogue in GIMP to what it looks like by going to Filters -> Development -> Examples -> Test Dialog.
Thank you for your message. I haven't used Python for scripting in Gimp, only Script Fu.
nchen thank you for your effort to rewrite my script in Python.
|