It is currently Tue Apr 30, 2024 2:46 am


All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 30 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: How to write python plugin preview?
PostPosted: Thu Jan 23, 2014 1:22 pm  (#1) 
Offline
GimpChat Member

Joined: Jan 20, 2014
Posts: 66
Hi, I wrote a simple plugin in to do some artistic transform (see attachement) and would like to change the parameter while looking at the effect applied on the image without leaving the dialog box.
Any sample code that I can follow? Thanks.
Attachment:
capture-20140124-020535.jpg
capture-20140124-020535.jpg [ 257.67 KiB | Viewed 5877 times ]


Share on Facebook Share on Twitter Share on Orkut Share on Digg Share on MySpace Share on Delicious Share on Technorati
Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Thu Jan 23, 2014 1:54 pm  (#2) 
Offline
GimpChat Member
User avatar

Joined: Jan 20, 2013
Posts: 14816
Location: roma, italy
the attached python plug-in has a preview mechanism.
how to imitate it, it's up to you (I don't understand the code!)
good luck!


Attachments:
high-end-sharpen.7z [3.01 KiB]
Downloaded 342 times

_________________
"Where am I ?"
Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Thu Jan 23, 2014 2:49 pm  (#3) 
Offline
Script Coder

Joined: Apr 10, 2011
Posts: 532
You could also take a look at my selection bevel plugin. It has a preview feature (although I don't even know if anyone uses it...)

http://gimpscripts.com/2011/10/selection-bevel/


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Thu Jan 23, 2014 3:46 pm  (#4) 
Offline
GimpChat Member
User avatar

Joined: Jan 03, 2011
Posts: 1656
Another solution (but it's not Python), would be to convert your effect into a G'MIC script, so we could add it to the G'MIC plug-in with a working preview flawlessly.
Is it something you are interested in ?
If so, I would be interested to convert your algorithm into a G'MIC script (if possible).


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Thu Jan 23, 2014 3:54 pm  (#5) 
Offline
Script Coder
User avatar

Joined: Oct 25, 2010
Posts: 4739
okyl168 wrote:
Hi, I wrote a simple plugin in to do some artistic transform (see attachement) and would like to change the parameter while looking at the effect applied on the image without leaving the dialog box.
Any sample code that I can follow? Thanks.
Attachment:
capture-20140124-020535.jpg


It should only be a matter of hooking on the callback that tell you when when the user changes a setting to apply your script (with maybe a short delay). Then the OK button just becomes a "Quit", and the Cancel one does an "Undo".

(by the way, the UI "canon" says that your yes/no button should really be a checkbox).

_________________
Image


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Thu Jan 23, 2014 4:57 pm  (#6) 
Offline
Script Coder

Joined: Apr 10, 2011
Posts: 532
Also, you can't use the gimppython helper library "gimpfu", which creates the UI for you - you have to write "closer to the metal" python and write all of your own UI code. And all you get is a button that does pretty much the same thing as the "ok" button except that the plugin window stays open and you can cancel the preview. It's not even a live preview, just a hackish way of doing a sort-of-preview.

The problem is that since python plugins are really just external scripts that mostly leverage GIMP's existing procedures and functions to do their job, any accurate preview can only be achieved by running that same code as you'd run on executing the plugin - and thus takes the same amount of time, which kind of defeats the idea of a "preview".

Although I guess you could do something like copying the current layer/image and scaling it to a smaller version and then running the plugin on that for preview, but I have to seriously question whether going through all that trouble is worth the payoff...


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Fri Jan 24, 2014 6:18 am  (#7) 
Offline
GimpChat Member

Joined: Jan 20, 2014
Posts: 66
dinasset/dd Thanks, the coding is a lot more complicated than I thought. Without the relevant documentation is hard to guess. The code is fast but take time to find the right color concentration, preview will save time jumping in and out of the dialog box.

Ronounours, sure attached is the code.
Attachment:
ob_jobs.7z [929 Bytes]
Downloaded 188 times


ofnuts/canon, yes, I know, but I don't see any PF_CHECKBOX in the document. If you point me to the document, that would be great.


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Fri Jan 24, 2014 7:25 am  (#8) 
Offline
Script Coder
User avatar

Joined: Oct 25, 2010
Posts: 4739
okyl168 wrote:

ofnuts/canon, yes, I know, but I don't see any PF_CHECKBOX in the document. If you point me to the document, that would be great.


Sorry, I though the dialog was your own dialog, I didn't notice that this was the auto-generated Python-fu one. So I have it extend my remark above, if you want a preview, you have to make your own input dialog. There are rumors about a preview API for python in Gimp, but I have never seen it in action.

_________________
Image


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Fri Jan 24, 2014 8:59 am  (#9) 
Offline
Script Coder
User avatar

Joined: Nov 06, 2012
Posts: 239
Location: Italy
I think you might also find it useful to take a look at the "Layer Effects" plugin:
http://registry.gimp.org/node/186

The dialog window of the version written in Python is provided with a preview widget represented by a checkbox that, when enabled/disabled, causes a preview of the specified effect to be shown/hidden in the form of a temporary layer added to the processed image.

_________________
Gino D's GIMP scripts: https://sites.google.com/site/ginodonig/gimp-scripts


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sat Jan 25, 2014 6:54 am  (#10) 
Offline
GimpChat Member
User avatar

Joined: Jan 03, 2011
Posts: 1656
Hi.
I've transcribed your python script into a G'MIC filter, you can already access it bu updating your filters.
I've put this filter in Testing / Okyl168 /

Attachment:
gmic_jobs_colors.png
gmic_jobs_colors.png [ 120.85 KiB | Viewed 5641 times ]


For information, here is what I wrote for doing this (13 lines in total) :

#@gimp Jobs colors : gimp_jobs_colors, gimp_jobs_colors_preview
#@gimp : Gamma = float(0,-2,2)
#@gimp : Equalize = bool(0)
#@gimp : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate horizontal","Duplicate vertical")
#@gimp : sep = separator(), note = note("<small>Author: <i>Oberon Leung</i>.      Latest update: <i>2014/01/25</i>.</small>")
gimp_jobs_colors :
  -n 0,255 -if $2 -equalize 256 -endif
  -luminance -apply_gamma {10^$1} -n 0,9 -round
  (22,187,20,36,235,240,245,243,247,209^18,97,147,157,79,110,121,142,170,200^19,158,192,60,67,65,64,64,64,185)
  -map[^-1] [-1] -rm[-1]

gimp_jobs_colors_preview :
  -gimp_split_preview "-gimp_jobs_colors $*",$-1


Let me know if this is what intended.


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sat Jan 25, 2014 10:13 am  (#11) 
Offline
GimpChat Member

Joined: Jan 20, 2014
Posts: 66
Ronounours, Great! How do I download your version? I am new here, I tried the latest beta 1.5.8.3 from sourceforge, but doesn't have the entry under Testing. Oh I found it by pressing the refresh button. LOL

Ronounours wrote:
Hi.
I've transcribed your python script into a G'MIC filter, you can already access it by updating your filters.
I've put this filter in Testing / Okyl168 /

For information, here is what I wrote for doing this (13 lines in total) :

Let me know if this is what intended.


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Tue Nov 07, 2023 10:35 am  (#12) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 3975
Location: Canada
While I was looking for a way to do live-preview, this topic came up.
And I wanted to try out my own (of course it has to gtk) which is kind'a ok I guess (wish there was a way to do with PF_variables but doesn't look like you can connect to PF_variables.
So this example is gtk (I have always been afraid of poking around in gtk) but this time I used help from ChatGPT and I tried to comment as much as I can so if I read it later I'll know what needs changing/fiddling.

At first I was going to try calling gegl c2g but the python_fu wrapper to gegl c2g is too slow to call using this preview so I just changed it to ellipse select and invert just to show me that this works on the fly. (see demo video below).
#!/usr/bin/env python

from gimpfu import *
import gtk

# Global variables to store the parameters used for our effect/work to show preview or actual layer when user OK it
global_param1 = 300 #in this example it's radius
global_param2 = 4   #in this example it's samples
global_param3 = 10  #in this example it's iterations
global_param4 = 0   #in this example it's enhance_shadows

image = 0 #we'll set these when dialog() is called so that we can access them later
drawable = 0
has_preview = False
preview_layer = 0

def apply_effect(layer): #function to do work on either preview layer or actual drawable when user clicks OK
    global image
    width = 200
    height = global_param1
    pdb.gimp_ellipse_select(image,image.width/2-width/2,image.height/2-height/2,width,height,CHANNEL_OP_REPLACE,TRUE,FALSE,0)
    pdb.gimp_drawable_invert(layer,TRUE)
    pdb.gimp_selection_none(image)
    gimp.displays_flush()

def apply_final(layer): #wrapper to apply effect on final and remove preview_layer meant to be called by on_ok_button_clicked
    global preview_layer
    if has_preview:
        pdb.gimp_image_remove_layer(image,preview_layer)

    pdb.gimp_image_undo_enable(image) #so that user can undo this next step
    pdb.gimp_image_undo_group_start(image) #so it's undone in Ctrl+Z
    apply_effect(layer)
    pdb.gimp_image_undo_group_end(image)
   


# Function to update the live preview
def update_live_preview(): #this is called everytime some parameter changes
    global global_param1, global_param2, global_param3, global_param4
    global image,drawable
    global has_preview,preview_layer #deal with preview layer
   
    # Apply your plugin's effect using the current parameters
    # Use global_param1 and global_param2 to access the user's inputs
    if not has_preview: #create a preview layer
        #pdb.gimp_message("Creating preview")
        preview_layer = pdb.gimp_layer_new_from_drawable(drawable,image)
        pdb.gimp_image_insert_layer(image,preview_layer,None,0) #insert top most so we see it
        has_preview = True #now set it true so we can deal with existing layer in later calls
    else: # already have preview layer
        #pdb.gimp_message("Removing existing and creating new Preview")
        pdb.gimp_image_remove_layer(image,preview_layer) #remove it to create a new one to work on
        preview_layer = pdb.gimp_layer_new_from_drawable(drawable,image)
        pdb.gimp_image_insert_layer(image,preview_layer,None,0) #insert top most so we see it

    #debug message
    #pdb.gimp_message(str(global_param1)+","+str(global_param2)+","+str(global_param3)+","+str(global_param4))

    apply_effect(preview_layer)
   
    # Update the live preview layer with the modified image
   

def dialog(image_, drawable_):
    global image, drawable
    #save these for updates
    image = image_
    pdb.gimp_image_undo_disable(image) #for speed and also when user undo it doesn't see our preview creations/deletions
    drawable = drawable_ 

    dialog = gtk.Dialog("My Live Dialog Test Window", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
    dialog.set_default_size(600, 100)

    # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox = gtk.HBox()
    dialog.vbox.pack_start(hbox, expand=True, fill=True)

    # Create a label on the left-hand side
    label1 = gtk.Label("radius:")
    hbox.pack_start(label1, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment1 = gtk.Adjustment(value=50, lower=2, upper=1000, step_incr=1, page_incr=0)
    param1_scale = gtk.HScale(adjustment=adjustment1)
    param1_scale.set_digits(0)  # Display only integers
    hbox.pack_start(param1_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param1_scale.connect("value-changed", on_param1_changed)

    # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox2 = gtk.HBox()
    dialog.vbox.pack_start(hbox2, expand=True, fill=True)

    # Create a label on the left-hand side
    label2 = gtk.Label("samples:")
    hbox2.pack_start(label2, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment2 = gtk.Adjustment(value=4, lower=3, upper=17, step_incr=1, page_incr=0)
    param2_scale = gtk.HScale(adjustment=adjustment2)
    param2_scale.set_digits(0)  # Display only integers
    hbox2.pack_start(param2_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param2_scale.connect("value-changed", on_param2_changed)

    # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox3 = gtk.HBox()
    dialog.vbox.pack_start(hbox3, expand=True, fill=True)

    # Create a label on the left-hand side
    label3 = gtk.Label("iterations:")
    hbox3.pack_start(label3, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment3 = gtk.Adjustment(value=10, lower=1, upper=30, step_incr=1, page_incr=0)
    param3_scale = gtk.HScale(adjustment=adjustment3)
    param3_scale.set_digits(0)  # Display only integers
    hbox3.pack_start(param3_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param3_scale.connect("value-changed", on_param3_changed)

    # Add an OK button
    ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
    ok_button.connect("clicked", on_ok_button_clicked)
    # Show the dialog
    dialog.show_all()
    dialog.run()

# Callback function for updating the live preview when param1 changes
def on_param1_changed(scale):
    global global_param1
    global_param1 = scale.get_value()
    update_live_preview()

# Callback function for updating the live preview when param2 changes
def on_param2_changed(scale):
    global global_param2
    global_param2 = scale.get_value()
    update_live_preview()

def on_param3_changed(scale):
    global global_param3
    global_param3 = scale.get_value()
    update_live_preview()

# Callback function for the OK button
def on_ok_button_clicked(button, data=None):
    global drawable
    apply_final(drawable)
    button.get_toplevel().destroy() #destroys the gtk dialog window
# Register the Python-Fu plugin
register(
    "python_fu_live_preview_test",
    "Live Preview Plugin",
    "Create a live preview of an effect",
    "Your Name",
    "Your Name",
    "2023",
    "<Image>/Python-Fu/Live Preview/Test 1",  # Menu location
    "*",  # Image type
    [],
    [],
    dialog
)

main()

_________________
TinT


Last edited by trandoductin on Sat Nov 25, 2023 12:30 pm, edited 1 time in total.

Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Tue Nov 07, 2023 10:39 am  (#13) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 3975
Location: Canada
this was my first ever preview try I think and it's great for something simple or fast.
working with gtk is well unknown territory to me. Don't think i would be able to do it without asking chatGPT a bunch of questions

_________________
TinT


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Tue Nov 07, 2023 3:15 pm  (#14) 
Offline
GimpChat Member
User avatar

Joined: Jan 13, 2011
Posts: 2249
Location: Poland
For me it's a total revolution (although it seems that the samples & iteration parameters are not active)


Attachments:
Test Live Dialog.jpg
Test Live Dialog.jpg [ 21.51 KiB | Viewed 2140 times ]

_________________
Image

Slava
Ukraini!
Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Tue Nov 07, 2023 4:43 pm  (#15) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 3975
Location: Canada
yeah I didn't use the samples and iterations which were initially for gegl g2c
I was making it but it seemed super slow so I just decided to play with selection and invert and only the first parameter
This one is much cooler and more useful I think viewtopic.php?f=9&t=20616

Not trying to start revolution, just poking around.

_________________
TinT


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sat Nov 25, 2023 1:26 am  (#16) 
Offline
GimpChat Member
User avatar

Joined: Jan 13, 2011
Posts: 2249
Location: Poland
I'm very surprised by the lack of interest in this great feature - now you can preview plugins directly.
My first attempt of course with G'mic (I know it has its preview, but not on canvas...)

Tin:
Do you know how to change the slider type?


Attachments:
G'MIC - Whirl Drawing Live Previewt.jpg
G'MIC - Whirl Drawing Live Previewt.jpg [ 206.46 KiB | Viewed 2067 times ]
G'MIC - Whirl Drawing Live Preview.zip [2.21 KiB]
Downloaded 14 times

_________________
Image

Slava
Ukraini!
Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sat Nov 25, 2023 2:01 am  (#17) 
Offline
GimpChat Member
User avatar

Joined: Jan 20, 2013
Posts: 14816
Location: roma, italy
@TT (and @MrQ)
if in your try the "function" whose effect has to be "pre-viewed" is too slow, why not to apply the G'MIC technique which scales down the image before showing the preview?
Sometimes renders the preview inusable, but...

_________________
"Where am I ?"


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sat Nov 25, 2023 9:37 am  (#18) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 3975
Location: Canada
MareroQ wrote:
I'm very surprised by the lack of interest in this great feature - now you can preview plugins directly.
My first attempt of course with G'mic (I know it has its preview, but not on canvas...)

Tin:
Do you know how to change the slider type?

I do not but if I needed any other type I just paste in my code into chatGPT and ask it to give me or add a gtk 2 integer type for example and it would give me something that I can partially copy and play with. I just haven't had the need to learn more.
But here's some code I got from chatGPT for integer/float entry and color picker
#!/usr/bin/env python
#grow-shrink-live.py
# Creator: TT
# This should allow user to adjust grow/shrink current selection with live preview
# Open Source
from gimpfu import *
import gtk

# Global variables to store the parameters used for our effect/work to show preview or actual layer when user OK it
global_param1 = 0 #in this example it's shrinkgrow radius
global_param2 = 0   #in this example it's feather_radius
global_param3 = 10  #in this example it's iterations
global_param4 = 0   #in this example it's enhance_shadows

chosen_color = 0
sample_integer = 13
image = 0 #we'll set these when dialog() is called so that we can access them later
drawable = 0
has_preview = False
preview_layer = 0
#for this operation
selection_channel = 0

def apply_effect(layer): #function to do work on either preview layer or actual drawable when user clicks OK
    global image
    radius = global_param1
    feather_radius = global_param2
    pdb.gimp_image_select_item(image,CHANNEL_OP_REPLACE,selection_channel) #first we selected the original saved channel
    if radius < 0:
        pdb.gimp_selection_shrink(image,-radius)
    else:
        pdb.gimp_selection_grow(image,radius)
    pdb.gimp_selection_feather(image,feather_radius)
    #do something to it to show it's effect so that user can distinguish between selected area or not
    pdb.gimp_drawable_edit_fill(layer,FILL_FOREGROUND)

    #pdb.gimp_ellipse_select(image,image.width/2-width/2,image.height/2-height/2,width,height,CHANNEL_OP_REPLACE,TRUE,FALSE,0)
    #pdb.gimp_drawable_invert(layer,TRUE)
    #pdb.gimp_selection_none(image)
    gimp.displays_flush()

def apply_final(layer): #wrapper to apply effect on final and remove preview_layer meant to be called by on_ok_button_clicked
    global preview_layer
    #pdb.gimp_image_undo_group_start(image) #so it's undone in Ctrl+Z
    pdb.gimp_image_undo_enable(image) #so that user can undo this next step
    apply_effect(preview_layer)
    #pdb.gimp_image_undo_group_end(image)
    if has_preview:
        pdb.gimp_image_remove_channel(image,selection_channel) #so that we don't leave a saved channel laying around
        pdb.gimp_image_remove_layer(image,preview_layer)
    pdb.gimp_image_set_active_layer(image,drawable)
    pdb.gimp_context_set_foreground(save_foreground)
    gimp.displays_flush()
   
# Function to update the live preview
def update_live_preview(): #this is called everytime some parameter changes
    global global_param1, global_param2, global_param3, global_param4
    global image,drawable
    global has_preview,preview_layer #deal with preview layer
    global selection_channel #this will save our current selection
    # Apply your plugin's effect using the current parameters
    # Use global_param1 and global_param2 to access the user's inputs
    if not has_preview: #create a preview layer
        #pdb.gimp_message("Creating preview")
        preview_layer = pdb.gimp_layer_new(image,image.width,image.height,RGBA_IMAGE,"preview",70,LAYER_MODE_NORMAL)
        pdb.gimp_image_insert_layer(image,preview_layer,None,0) #insert top most so we see it
        non_empty,x1,y1,x2,y2 = pdb.gimp_selection_bounds(image)
        if non_empty == TRUE:
            pass #there's already a selection
        else:
            pdb.gimp_selection_all(image) #if there's no selection we just select the whole image and work with that   
        selection_channel = pdb.gimp_selection_save(image)
        has_preview = True #now set it true so we can deal with existing layer in later calls
    else: # already have preview layer
        pass
        #pdb.gimp_message("Removing existing and creating new Preview")
        pdb.gimp_image_remove_layer(image,preview_layer) #remove it to create a new one to work on
        preview_layer = pdb.gimp_layer_new(image,image.width,image.height,RGBA_IMAGE,"preview",50,LAYER_MODE_NORMAL)
        pdb.gimp_image_insert_layer(image,preview_layer,None,0) #insert top most so we see it
    pdb.gimp_image_set_active_layer(image,preview_layer)
    #debug message
    #pdb.gimp_message(str(global_param1)+","+str(global_param2)+","+str(global_param3)+","+str(global_param4))

    apply_effect(preview_layer)
   
    # Update the live preview layer with the modified image
   
save_foreground = 0
hilightcolor = (255,0,0)
def dialog(image_, drawable_):
    global image, drawable, save_foreground
    if chosen_color==0:
        global chosen_color
        chosen_color = (0, 128, 255)  # Set default color
    #save these for updates
    image = image_
    pdb.gimp_image_undo_disable(image) #for speed and also when user undo it doesn't see our preview creations/deletions
    drawable = drawable_ 
    save_foreground = pdb.gimp_context_get_foreground()
    pdb.gimp_context_set_foreground(hilightcolor)

    dialog = gtk.Dialog("Shrink/Grow Feather Selection Live Preview", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
    dialog.set_default_size(600, 100)
   
    #color_selection.ok_button.connect("clicked", on_color_ok_button_clicked)

    # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox = gtk.HBox()
    dialog.vbox.pack_start(hbox, expand=True, fill=True)

    # Create a label on the left-hand side
    label1 = gtk.Label("Shrink/Grow Radius:")
    hbox.pack_start(label1, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment1 = gtk.Adjustment(value=0, lower=-400, upper=400, step_incr=1, page_incr=0)
    param1_scale = gtk.HScale(adjustment=adjustment1)
    param1_scale.set_digits(0)  # Display only integers
    hbox.pack_start(param1_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param1_scale.connect("value-changed", on_param1_changed)

    # # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox2 = gtk.HBox()
    dialog.vbox.pack_start(hbox2, expand=True, fill=True)

    # Create a label on the left-hand side
    label2 = gtk.Label("Feather Radius:")
    hbox2.pack_start(label2, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment2 = gtk.Adjustment(value=0, lower=0, upper=400, step_incr=1, page_incr=0)
    param2_scale = gtk.HScale(adjustment=adjustment2)
    param2_scale.set_digits(0)  # Display only integers
    hbox2.pack_start(param2_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param2_scale.connect("value-changed", on_param2_changed)

   
    # Create an HBox to hold the label and integer/float input box =====================================================
    hbox4 = gtk.HBox()
    dialog.vbox.pack_start(hbox4, expand=True, fill=True)

    # Create a label for the integer input
    label4 = gtk.Label("Sample Integer:")
    hbox4.pack_start(label4, expand=False, fill=False, padding=5)

    # Create a text entry for the integer input
    entry4 = gtk.Entry()
    entry4.set_text(str(sample_integer))
    entry4.set_width_chars(5)  # Adjust the width of input box as needed
    entry4.connect("changed", on_sample_integer_changed)
    hbox4.pack_start(entry4, expand=False, fill=False, padding=5)

    # Create an HBox to hold the label and color picker button =================
    hbox5 = gtk.HBox()
    dialog.vbox.pack_start(hbox5, expand=True, fill=True)

    # Create a label for the color picker
    label5 = gtk.Label("Color Picker:")
    hbox5.pack_start(label5, expand=False, fill=False, padding=5)

    # Create a color picker button
    color_button = gtk.ColorButton()
    color_button.set_use_alpha(False)  # Set to True if you want to include alpha channel
    #default_color = gtk.gdk.Color(65535, 0, 0)  # Red in RGB, where values are between 0 and 65535
    pdb.gimp_message("set:" + str(chosen_color[0]))
    color_button.set_color(gtk.gdk.Color(chosen_color[0]*256, chosen_color[1]*256, chosen_color[2]*256))
    color_button.connect("color-set", on_color_changed)
    hbox5.pack_start(color_button, expand=False, fill=False, padding=5)

#     color_selection.show_all()
#     color_selection = gtk.ColorSelectionDialog("Select Color")
#     # Run the dialog
#     response = color_selection.run()

#     # Connect the "clicked" signal after the dialog has been shown
#     color_selection.ok_button.connect("clicked", on_color_ok_button_clicked)

# # Check the response and destroy the dialog
#     if response == gtk.RESPONSE_OK:
#         on_color_ok_button_clicked(color_selection)
#     else:
#         color_selection.destroy()

    # hbox3 = gtk.HBox()
    # dialog.vbox.pack_start(hbox3, expand=True, fill=True)

    # # Create a label on the left-hand side
    # label3 = gtk.Label("iterations:")
    # hbox3.pack_start(label3, expand=False, fill=False, padding=5)

    # # Create an adjustment for the HScale (slider) with a range from 10 to 90
    # adjustment3 = gtk.Adjustment(value=10, lower=1, upper=30, step_incr=1, page_incr=0)
    # param3_scale = gtk.HScale(adjustment=adjustment3)
    # param3_scale.set_digits(0)  # Display only integers
    # hbox3.pack_start(param3_scale, expand=True, fill=True, padding=5)
    # # Connect callback functions for user interaction
    # param3_scale.connect("value-changed", on_param3_changed)

    # Add an OK button
    ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
    ok_button.connect("clicked", on_ok_button_clicked)
    # Show the dialog
    dialog.show_all()
    update_live_preview() #call this once so we see effect
    dialog.run()

def on_sample_integer_changed(entry):
    global sample_integer
    try:
        sample_integer = float(entry.get_text()) #int(entry.get_text())
        pdb.gimp_message(sample_integer)
        update_live_preview()
    except ValueError:
        # Handle the case where the input is not a valid integer
        pass   

#need these 2 functions for color picker(s)
def on_color_changed(widget, data=None):
    global chosen_color
    pdb.gimp_message("ran color")
    color = widget.get_color()
    chosen_color = (int(color.red/256), int(color.green/256), int(color.blue/256))
    pdb.gimp_message(str(chosen_color[0]))
    update_live_preview()

# def on_color_ok_button_clicked(dialog, data=None):
#     pdb.gimp_message("color clicked")
#     global selected_color
#     color = color_selection.get_current_color()
#     selected_color = (int(color.red * 255), int(color.green * 255), int(color.blue * 255))
#     pdb.gimp_message(selected_color[0])
#     update_live_preview()
#     dialog.destroy()

# Callback function for updating the live preview when param1 changes
def on_param1_changed(scale):
    global global_param1
    global_param1 = scale.get_value()
    update_live_preview()

# Callback function for updating the live preview when param2 changes
def on_param2_changed(scale):
    global global_param2
    global_param2 = scale.get_value()
    update_live_preview()

def on_param3_changed(scale):
    global global_param3
    global_param3 = scale.get_value()
    update_live_preview()

# Callback function for the OK button
def on_ok_button_clicked(button, data=None):
    global drawable
    apply_final(preview_layer) #preview layer because we don't want to apply the invert to final layer it's just for viewing
    button.get_toplevel().destroy() #destroys the gtk dialog window
# Register the Python-Fu plugin
register(
    "python_fu_grow_shrink_live",
    "Grow/Shrink Current Selection with Live Preview",
    "Grow/Shrink Current Selection with Live Preview",
    "TT",
    "TT",
    "NAME",
    "<Image>/Python-Fu/Live Preview/Grow-Shrink Live",  # Menu location
    "*",  # Image type
    [],
    [],
    dialog
)

main()

_________________
TinT


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sun Nov 26, 2023 12:32 am  (#19) 
Offline
GimpChat Member
User avatar

Joined: Jan 13, 2011
Posts: 2249
Location: Poland
@Diego
Why don't you try to do it?
Knowing nothing about gtk2, I adapted the Tin code to the new plugin (Gmic) in 15 minutes (without any understanding of how it works).
I don't intend to learn gtk from scratch now (lack of time), but I will look for solutions in other plugins.

@Tin.
Thank you for sharing the code.
I'm trying to learn how to use the color picker in Gmic.
I will still want to find equivalents for PF_OPTION and PF_TOGGLE (this is a long way for me - unless someone knows and shares how to use it).

_________________
Image

Slava
Ukraini!


Top
 Post subject: Re: How to write python plugin preview?
PostPosted: Sun Nov 26, 2023 10:06 am  (#20) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 3975
Location: Canada
here's some coded added to existing previous example
that I pulled out from ChatGPT for a YES/NO button (so toggle)
and a combo choice selection (so options)
I do not fully understand it but I can work with what it gave me.
#!/usr/bin/env python
#grow-shrink-live.py
# Creator: TT
# This should allow user to adjust grow/shrink current selection with live preview
# Open Source
from gimpfu import *
import gtk

# Global variables to store the parameters used for our effect/work to show preview or actual layer when user OK it
global_param1 = 0 #in this example it's shrinkgrow radius
global_param2 = 0   #in this example it's feather_radius
global_param3 = 10  #in this example it's iterations
global_param4 = 0   #in this example it's enhance_shadows

chosen_color = 0
sample_integer = 13
image = 0 #we'll set these when dialog() is called so that we can access them later
drawable = 0
has_preview = False
preview_layer = 0
#for this operation
selection_channel = 0
proceed_with_changes = False
def apply_effect(layer): #function to do work on either preview layer or actual drawable when user clicks OK
    global image
    radius = global_param1
    feather_radius = global_param2
    pdb.gimp_image_select_item(image,CHANNEL_OP_REPLACE,selection_channel) #first we selected the original saved channel
    if radius < 0:
        pdb.gimp_selection_shrink(image,-radius)
    else:
        pdb.gimp_selection_grow(image,radius)
    pdb.gimp_selection_feather(image,feather_radius)
    #do something to it to show it's effect so that user can distinguish between selected area or not
    pdb.gimp_drawable_edit_fill(layer,FILL_FOREGROUND)

    #pdb.gimp_ellipse_select(image,image.width/2-width/2,image.height/2-height/2,width,height,CHANNEL_OP_REPLACE,TRUE,FALSE,0)
    #pdb.gimp_drawable_invert(layer,TRUE)
    #pdb.gimp_selection_none(image)
    gimp.displays_flush()

def apply_final(layer): #wrapper to apply effect on final and remove preview_layer meant to be called by on_ok_button_clicked
    global preview_layer
    #pdb.gimp_image_undo_group_start(image) #so it's undone in Ctrl+Z
    pdb.gimp_image_undo_enable(image) #so that user can undo this next step
    apply_effect(preview_layer)
    #pdb.gimp_image_undo_group_end(image)
    if has_preview:
        pdb.gimp_image_remove_channel(image,selection_channel) #so that we don't leave a saved channel laying around
        pdb.gimp_image_remove_layer(image,preview_layer)
    pdb.gimp_image_set_active_layer(image,drawable)
    pdb.gimp_context_set_foreground(save_foreground)
    gimp.displays_flush()
   
# Function to update the live preview
def update_live_preview(): #this is called everytime some parameter changes
    global global_param1, global_param2, global_param3, global_param4
    global image,drawable
    global has_preview,preview_layer #deal with preview layer
    global selection_channel #this will save our current selection
    # Apply your plugin's effect using the current parameters
    # Use global_param1 and global_param2 to access the user's inputs
    if not has_preview: #create a preview layer
        #pdb.gimp_message("Creating preview")
        preview_layer = pdb.gimp_layer_new(image,image.width,image.height,RGBA_IMAGE,"preview",70,LAYER_MODE_NORMAL)
        pdb.gimp_image_insert_layer(image,preview_layer,None,0) #insert top most so we see it
        non_empty,x1,y1,x2,y2 = pdb.gimp_selection_bounds(image)
        if non_empty == TRUE:
            pass #there's already a selection
        else:
            pdb.gimp_selection_all(image) #if there's no selection we just select the whole image and work with that   
        selection_channel = pdb.gimp_selection_save(image)
        has_preview = True #now set it true so we can deal with existing layer in later calls
    else: # already have preview layer
        pass
        #pdb.gimp_message("Removing existing and creating new Preview")
        pdb.gimp_image_remove_layer(image,preview_layer) #remove it to create a new one to work on
        preview_layer = pdb.gimp_layer_new(image,image.width,image.height,RGBA_IMAGE,"preview",50,LAYER_MODE_NORMAL)
        pdb.gimp_image_insert_layer(image,preview_layer,None,0) #insert top most so we see it
    pdb.gimp_image_set_active_layer(image,preview_layer)
    #debug message
    #pdb.gimp_message(str(global_param1)+","+str(global_param2)+","+str(global_param3)+","+str(global_param4))

    apply_effect(preview_layer)
   
    # Update the live preview layer with the modified image
   
save_foreground = 0
hilightcolor = (255,0,0)
def dialog(image_, drawable_):
    global image, drawable, save_foreground
    if chosen_color==0:
        global chosen_color
        chosen_color = (0, 128, 255)  # Set default color
    #save these for updates
    image = image_
    pdb.gimp_image_undo_disable(image) #for speed and also when user undo it doesn't see our preview creations/deletions
    drawable = drawable_ 
    save_foreground = pdb.gimp_context_get_foreground()
    pdb.gimp_context_set_foreground(hilightcolor)

    dialog = gtk.Dialog("Shrink/Grow Feather Selection Live Preview", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
    dialog.set_default_size(600, 100)
   
    #color_selection.ok_button.connect("clicked", on_color_ok_button_clicked)

    # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox = gtk.HBox()
    dialog.vbox.pack_start(hbox, expand=True, fill=True)

    # Create a label on the left-hand side
    label1 = gtk.Label("Shrink/Grow Radius:")
    hbox.pack_start(label1, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment1 = gtk.Adjustment(value=0, lower=-400, upper=400, step_incr=1, page_incr=0)
    param1_scale = gtk.HScale(adjustment=adjustment1)
    param1_scale.set_digits(0)  # Display only integers
    hbox.pack_start(param1_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param1_scale.connect("value-changed", on_param1_changed)

    # # Create an HBox to hold the label and slider -------------------------------------------------------------
    hbox2 = gtk.HBox()
    dialog.vbox.pack_start(hbox2, expand=True, fill=True)

    # Create a label on the left-hand side
    label2 = gtk.Label("Feather Radius:")
    hbox2.pack_start(label2, expand=False, fill=False, padding=5)

    # Create an adjustment for the HScale (slider) with a range from 10 to 90
    adjustment2 = gtk.Adjustment(value=0, lower=0, upper=400, step_incr=1, page_incr=0)
    param2_scale = gtk.HScale(adjustment=adjustment2)
    param2_scale.set_digits(0)  # Display only integers
    hbox2.pack_start(param2_scale, expand=True, fill=True, padding=5)
    # Connect callback functions for user interaction
    param2_scale.connect("value-changed", on_param2_changed)

   
    # Create an HBox to hold the label and integer/float input box =====================================================
    hbox4 = gtk.HBox()
    dialog.vbox.pack_start(hbox4, expand=True, fill=True)

    # Create a label for the integer input
    label4 = gtk.Label("Sample Integer:")
    hbox4.pack_start(label4, expand=False, fill=False, padding=5)

    # Create a text entry for the integer input
    entry4 = gtk.Entry()
    entry4.set_text(str(sample_integer))
    entry4.set_width_chars(5)  # Adjust the width of input box as needed
    entry4.connect("changed", on_sample_integer_changed)
    hbox4.pack_start(entry4, expand=False, fill=False, padding=5)

    # Create an HBox to hold the label and color picker button =================
    hbox5 = gtk.HBox()
    dialog.vbox.pack_start(hbox5, expand=True, fill=True)

    # Create a label for the color picker
    label5 = gtk.Label("Color Picker:")
    hbox5.pack_start(label5, expand=False, fill=False, padding=5)

    # Create a color picker button
    color_button = gtk.ColorButton()
    color_button.set_use_alpha(False)  # Set to True if you want to include alpha channel
    #default_color = gtk.gdk.Color(65535, 0, 0)  # Red in RGB, where values are between 0 and 65535
    pdb.gimp_message("set:" + str(chosen_color[0]))
    color_button.set_color(gtk.gdk.Color(chosen_color[0]*256, chosen_color[1]*256, chosen_color[2]*256))
    color_button.connect("color-set", on_color_changed)
    hbox5.pack_start(color_button, expand=False, fill=False, padding=5)

    #toggle button ==============
    hbox6 = gtk.HBox()
    dialog.vbox.pack_start(hbox6, expand=True, fill=True)

    label6 = gtk.Label("Sample Toggle:")
    hbox6.pack_start(label6, expand=False, fill=False, padding=5)   
    toggle_button = gtk.ToggleButton()

    # Set initial label
    update_toggle_button_label(toggle_button)
    # Connect the toggle event
    toggle_button.connect("toggled", on_toggle_button_toggled)
    hbox6.pack_start(toggle_button, expand=False, fill=False, padding=5)   

   
    #Combo box ===================================
    hbox7 = gtk.HBox()
    dialog.vbox.pack_start(hbox7, expand=True, fill=True)

    label7 = gtk.Label("Sample Choice:")
    hbox7.pack_start(label7, expand=False, fill=False, padding=5)   

    toggle_button = gtk.ToggleButton()
    # Create a ComboBox
    combo_box = gtk.ComboBox()
    hbox7.pack_start(combo_box, expand=False, fill=False, padding=5)

    # Create a ListStore model for ComboBox
    list_store = gtk.ListStore(str)
    options = ["Option 1", "Option 2", "Option 3"]  # Add your options here
    for option in options:
        list_store.append([option])

    # Set the model for ComboBox
    combo_box.set_model(list_store)

    # Create a CellRendererText to render the text in the ComboBox
    cell_renderer = gtk.CellRendererText()

    # Pack the CellRendererText into the ComboBox
    combo_box.pack_start(cell_renderer, True)
    combo_box.add_attribute(cell_renderer, 'text', 0)

    # Set up the "changed" signal handler   
    combo_box.connect("changed", on_combobox_changed)

    # Set the default option to "Option 1" ======================================
    default_option = "Option 1"
    default_iter = list_store.get_iter_first()
    while default_iter is not None:
        if list_store.get_value(default_iter, 0) == default_option:
            combo_box.set_active_iter(default_iter)
            break
        default_iter = list_store.iter_next(default_iter)
    #===========================================================================

#     color_selection.show_all()
#     color_selection = gtk.ColorSelectionDialog("Select Color")
#     # Run the dialog
#     response = color_selection.run()

#     # Connect the "clicked" signal after the dialog has been shown
#     color_selection.ok_button.connect("clicked", on_color_ok_button_clicked)

# # Check the response and destroy the dialog
#     if response == gtk.RESPONSE_OK:
#         on_color_ok_button_clicked(color_selection)
#     else:
#         color_selection.destroy()

    # hbox3 = gtk.HBox()
    # dialog.vbox.pack_start(hbox3, expand=True, fill=True)

    # # Create a label on the left-hand side
    # label3 = gtk.Label("iterations:")
    # hbox3.pack_start(label3, expand=False, fill=False, padding=5)

    # # Create an adjustment for the HScale (slider) with a range from 10 to 90
    # adjustment3 = gtk.Adjustment(value=10, lower=1, upper=30, step_incr=1, page_incr=0)
    # param3_scale = gtk.HScale(adjustment=adjustment3)
    # param3_scale.set_digits(0)  # Display only integers
    # hbox3.pack_start(param3_scale, expand=True, fill=True, padding=5)
    # # Connect callback functions for user interaction
    # param3_scale.connect("value-changed", on_param3_changed)

    # Add an OK button
    ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
    ok_button.connect("clicked", on_ok_button_clicked)
    # Show the dialog
    dialog.show_all()
    update_live_preview() #call this once so we see effect
    dialog.run()

def on_combobox_changed(combobox):
    global selected_option
    model = combobox.get_model()
    active_iter = combobox.get_active_iter()

    if active_iter:
        selected_option = model.get_value(active_iter, 0)
        pdb.gimp_message("Selected option:" + str(selected_option))

def on_toggle_button_toggled(button):
    global proceed_with_changes
    proceed_with_changes = not proceed_with_changes
    pdb.gimp_message("Sample toggle result:" + str(proceed_with_changes))
    update_toggle_button_label(button)

def update_toggle_button_label(button):
    label = "Yes" if proceed_with_changes else "No"
    button.set_label(label)


def on_sample_integer_changed(entry):
    global sample_integer
    try:
        sample_integer = float(entry.get_text()) #int(entry.get_text())
        pdb.gimp_message(sample_integer)
        update_live_preview()
    except ValueError:
        # Handle the case where the input is not a valid integer
        pass   

#need these 2 functions for color picker(s)
def on_color_changed(widget, data=None):
    global chosen_color
    pdb.gimp_message("ran color")
    color = widget.get_color()
    chosen_color = (int(color.red/256), int(color.green/256), int(color.blue/256))
    pdb.gimp_message(str(chosen_color[0]))
    update_live_preview()

# def on_color_ok_button_clicked(dialog, data=None):
#     pdb.gimp_message("color clicked")
#     global selected_color
#     color = color_selection.get_current_color()
#     selected_color = (int(color.red * 255), int(color.green * 255), int(color.blue * 255))
#     pdb.gimp_message(selected_color[0])
#     update_live_preview()
#     dialog.destroy()

# Callback function for updating the live preview when param1 changes
def on_param1_changed(scale):
    global global_param1
    global_param1 = scale.get_value()
    update_live_preview()

# Callback function for updating the live preview when param2 changes
def on_param2_changed(scale):
    global global_param2
    global_param2 = scale.get_value()
    update_live_preview()

def on_param3_changed(scale):
    global global_param3
    global_param3 = scale.get_value()
    update_live_preview()

# Callback function for the OK button
def on_ok_button_clicked(button, data=None):
    global drawable
    apply_final(preview_layer) #preview layer because we don't want to apply the invert to final layer it's just for viewing
    button.get_toplevel().destroy() #destroys the gtk dialog window
# Register the Python-Fu plugin
register(
    "python_fu_grow_shrink_live",
    "Grow/Shrink Current Selection with Live Preview",
    "Grow/Shrink Current Selection with Live Preview",
    "TT",
    "TT",
    "NAME",
    "<Image>/Python-Fu/Live Preview/Grow-Shrink Live",  # Menu location
    "*",  # Image type
    [],
    [],
    dialog
)

main()

_________________
TinT


Top
Post new topic Reply to topic  [ 30 posts ]  Go to page 1, 2  Next

All times are UTC - 5 hours [ DST ]


   Similar Topics   Replies 
No new posts Convert GIMP plugin from Python 2 to Python 3

4

No new posts Attachment(s) Macbook User and Python plugin

14

No new posts Unable to get simple python plugin to show up

8

No new posts Please make a python plugin to combine my GEGL filters with GMIC

1

No new posts Get a mouse click or select a pixel in an image in a python plugin?

3



* Login  



Powered by phpBB3 © phpBB Group