It is currently Sat Aug 08, 2026 8:13 am


All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 27 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: String Art Plug-in
PostPosted: Sat Dec 09, 2023 4:41 pm  (#1) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4527
Location: Canada
Image
#!/usr/bin/env python

# thread-weave.py
# Author: Tin Tran
# String art, anyone? I thought it would be cool.
# Comment or questions gimpchat.com or gimp-forum.net
# Open source whatever the GIMP's license is.
from gimpfu import *
def string_art(img,layer,points,pattern):
    points = int(points) #make sure it's int
    pattern = [int(x) for x in pattern.split(',')] #make it array of ints
    vectors = pdb.gimp_image_get_active_vectors(img)
    num_strokes,stroke_ids = pdb.gimp_vectors_get_strokes(vectors)
    length = pdb.gimp_vectors_stroke_get_length(vectors,stroke_ids[0],0.1)
    sectionlength = length*1.0/points
    p = []
    #get all the points coordinates into p array
    for i in range(0,points):
        dist = sectionlength*i
        x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(vectors,stroke_ids[0],dist,0.1)
        p.append([x,y])
   
    cp = [] #control points
    checkpattern = [];
    currentpoint = 0;
    cp.append(currentpoint)
    firsttime=1;
    while 1==1: #loop until we break from it
        for i in range(0,len(pattern)):
            currentpoint = ((currentpoint + points) + pattern[i] ) % points;
            cp.append(currentpoint)
        if (firsttime==1):
            checkpattern = cp[:] #copy this initial pattern to know when to stop
            firsttime = 0
        else:
            good = 1
            l = len(checkpattern)
            for j in range(0,l):
                if (cp[len(cp)-l+j]!=checkpattern[j]):
                    good = 0
                    break;
            if good==1: #it's all the same as checkpattern so we've done looping
                cp = cp[0:-(l-1)]
                break
    #here we have cp containing all our points index now create the path
    new_vectors = pdb.gimp_vectors_new(img,"String Art")
    pdb.gimp_image_insert_vectors(img,new_vectors,None,0)
    cpoints = []
    for i in range(0,len(cp)):
        cpoints = cpoints + (p[cp[i]]*3) #add 3 times the current point
    npoints = len(cpoints)
    stroke_id = pdb.gimp_vectors_stroke_new_from_points(new_vectors,0,npoints,cpoints,FALSE)

register(
    "python_fu_string_art",
    "Create String Art from Active Path",
    "Create String Art from Active Path",
    "TT",
    "TT",
    "2023.12.09",
    "A String Art...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    [
    #INPUT BEGINS
    (PF_IMAGE, "img", "Image", None),
    (PF_DRAWABLE,   "layer", "Drawable", None),
    (PF_INT, "points", "Points on Path:", 18),
    (PF_STRING, "stringpattern", "String Pattern (ie. 3,-2 means forward 3, backward 2...):", "3,-2"),  # alias PF_VALUE
    #INPUT ENDS
    ],
    [],
    string_art,
    menu="<Image>/Python-Fu")

main()

# Below is all the example input types for INPUTS for the plug-in which can be cut and pasted into #INPUT BEGINS section and edited to taste
#           (PF_INT, "p0", "_INT:", 0), # PF_INT8, PF_INT16, PF_INT32  similar but no difference in Python.
#           (PF_FLOAT, "p02", "_FLOAT:", 3.141),
#           (PF_STRING, "p03", "_STRING:", "foo"),  # alias PF_VALUE
#           (PF_TEXT, "p04", "TEXT:", "bar"),
#           # PF_VALUE
#           # Pick one from set of choices
#           (PF_OPTION,"p1",   "OPTION:", 0, ["0th","1st","2nd"]), # initially 0th is choice
#           (PF_RADIO, "p16", "RADIO:", 0, (("0th", 1),("1st",0))), # note bool indicates initial setting of buttons
#           # PF_RADIO is usually called a radio button group.
#           # SLIDER, ADJUSTMENT types require the extra parameter of the form (min, max, step).
#           (PF_TOGGLE, "p2",   "TOGGLE:", 1), # initially True, checked.  Alias PF_BOOL
#           # PF_TOGGLE is usually called a checkbox.
#           (PF_SLIDER, "p3", "SLIDER:", 0, (0, 100, 10)),
#           (PF_SPINNER, "p4", "SPINNER:", 21, (1, 1000, 50)),  # alias PF_ADJUSTMENT
#           # Pickers ie combo boxes ie choosers from lists of existing Gimp objects
#           (PF_COLOR, "p14", "_COLOR:", (100, 21, 40) ), # extra param is RGB triple
#           # PF_COLOUR is an alias by aussie PyGimp author lol
#           (PF_IMAGE, "p15", "IMAGE:", None), # should be type gimp.image, but None works
#           (PF_FONT, "p17", "FONT:", 0),
#           (PF_FILE, "p18", "FILE:", 0),
#           (PF_BRUSH, "p19", "BRUSH:", 0),
#           (PF_PATTERN, "p20", "PATTERN:", 0),
#           (PF_GRADIENT, "p21", "GRADIENT:", 0),
#           (PF_PALETTE, "p22", "PALETTE:", 0),
#           (PF_LAYER, "p23", "LAYER:", None),
#           (PF_CHANNEL, "p24", "CHANNEL:", None),  # ??? Usually empty, I don't know why.
#           (PF_DRAWABLE, "p25", "DRAWABLE:", None),
#           # Mostly undocumented, but work
#           (PF_VECTORS, "p26", "VECTORS:", None),
#           (PF_FILENAME, "p27", "FILENAME:", 0),
#           (PF_DIRNAME, "p28", "DIRNAME:", 0)
#           # PF_REGION might work but probably of little use.  See gimpfu.py.


This plug-in expects you to have an active path.
It'll then divide that path into equal-distance sections based on path length to get points.
And then uses your entered string pattern to move forward/backward on the points like string weaving or string art.
The result is a path, so you'll have to stroke it or whatever else you need to do to decorate your string art.

Demo video: https://www.youtube.com/watch?v=USnzjegjK-4

_________________
TinT


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: String Art Plug-in
PostPosted: Sat Dec 09, 2023 6:09 pm  (#2) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4527
Location: Canada
This is really fast so it's a good candidate for Live Preview
here's code for live preview version
#!/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 = 18
pattern_ = "3,-2"       
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 preview_layer,has_preview
    if has_preview == True:
        pdb.gimp_image_remove_vectors(image,preview_layer)
        has_preview = False
        points = int(sample_integer) #make sure it's int
        pattern = [int(x) for x in pattern_.split(',')] #make it array of ints
        vectors = pdb.gimp_image_get_active_vectors(image)
        num_strokes,stroke_ids = pdb.gimp_vectors_get_strokes(vectors)
        length = pdb.gimp_vectors_stroke_get_length(vectors,stroke_ids[0],0.1)
        sectionlength = length*1.0/points
        p = []
        #get all the points coordinates into p array
        for i in range(0,points):
            dist = sectionlength*i
            x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(vectors,stroke_ids[0],dist,0.1)
            p.append([x,y])
       
        cp = [] #control points
        checkpattern = [];
        currentpoint = 0;
        cp.append(currentpoint)
        firsttime=1;
        while 1==1: #loop until we break from it
            for i in range(0,len(pattern)):
                currentpoint = ((currentpoint + points) + pattern[i] ) % points;
                cp.append(currentpoint)
            if (firsttime==1):
                checkpattern = cp[:] #copy this initial pattern to know when to stop
                firsttime = 0
            else:
                good = 1
                l = len(checkpattern)
                for j in range(0,l):
                    if (cp[len(cp)-l+j]!=checkpattern[j]):
                        good = 0
                        break;
                if good==1: #it's all the same as checkpattern so we've done looping
                    cp = cp[0:-(l-1)]
                    break
        #here we have cp containing all our points index now create the path
        new_vectors = pdb.gimp_vectors_new(image,"String Art")
        pdb.gimp_image_insert_vectors(image,new_vectors,None,0)
        cpoints = []
        for i in range(0,len(cp)):
            cpoints = cpoints + (p[cp[i]]*3) #add 3 times the current point
        npoints = len(cpoints)
        stroke_id = pdb.gimp_vectors_stroke_new_from_points(new_vectors,0,npoints,cpoints,FALSE)
        pdb.gimp_item_set_visible(new_vectors,TRUE)
        preview_layer = new_vectors
        has_preview = True


    # 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
    pass
    # 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)
       

        points = int(sample_integer) #make sure it's int
        pattern = [int(x) for x in pattern_.split(',')] #make it array of ints
        vectors = pdb.gimp_image_get_active_vectors(image)
        num_strokes,stroke_ids = pdb.gimp_vectors_get_strokes(vectors)
        length = pdb.gimp_vectors_stroke_get_length(vectors,stroke_ids[0],0.1)
        sectionlength = length*1.0/points
        p = []
        #get all the points coordinates into p array
        for i in range(0,points):
            dist = sectionlength*i
            x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(vectors,stroke_ids[0],dist,0.1)
            p.append([x,y])
       
        cp = [] #control points
        checkpattern = [];
        currentpoint = 0;
        cp.append(currentpoint)
        firsttime=1;
        while 1==1: #loop until we break from it
            for i in range(0,len(pattern)):
                currentpoint = ((currentpoint + points) + pattern[i] ) % points;
                cp.append(currentpoint)
            if (firsttime==1):
                checkpattern = cp[:] #copy this initial pattern to know when to stop
                firsttime = 0
            else:
                good = 1
                l = len(checkpattern)
                for j in range(0,l):
                    if (cp[len(cp)-l+j]!=checkpattern[j]):
                        good = 0
                        break;
                if good==1: #it's all the same as checkpattern so we've done looping
                    cp = cp[0:-(l-1)]
                    break
        #here we have cp containing all our points index now create the path
        new_vectors = pdb.gimp_vectors_new(image,"String Art")
        pdb.gimp_image_insert_vectors(image,new_vectors,None,0)
        cpoints = []
        for i in range(0,len(cp)):
            cpoints = cpoints + (p[cp[i]]*3) #add 3 times the current point
        npoints = len(cpoints)
        stroke_id = pdb.gimp_vectors_stroke_new_from_points(new_vectors,0,npoints,cpoints,FALSE)
        pdb.gimp_item_set_visible(new_vectors,TRUE)
        preview_layer = new_vectors
        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("String-Art 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("Points on Paths:")
    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)



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

    # Create a label for the integer input
    label5 = gtk.Label("String Pattern (ie 3,-2 means forward 3 points and backward 2 points:")
    hbox5.pack_start(label5, expand=False, fill=False, padding=5)

    # Create a text entry for the integer input
    entry5 = gtk.Entry()
    entry5.set_text("3,-2")
    entry5.set_width_chars(20)  # Adjust the width of input box as needed
    entry5.connect("changed", on_pattern_changed)
    hbox5.pack_start(entry5, 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   

def on_pattern_changed(entry):
    global pattern_
    try:
        pattern_ = str(entry.get_text()) #int(entry.get_text())
        #pdb.gimp_message(pattern_)
        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_string_art_live",
    "Grow/Shrink Current Selection with Live Preview",
    "Grow/Shrink Current Selection with Live Preview",
    "TT",
    "TT",
    "2023.12.09",
    "<Image>/Python-Fu/Live Preview/String Art Live",  # Menu location
    "*",  # Image type
    [],
    [],
    dialog
)

main()

Image

_________________
TinT


Top
 Post subject: Re: String Art Plug-in
PostPosted: Sat Dec 09, 2023 7:18 pm  (#3) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4527
Location: Canada
Which reminds me... there was a request I forgot from which user but it was related to created looms of different shapes to be weave and they just needed the ability in GIMP to draw those shapes which I wrote but it's lost on the old forum.
That was a fun thing to write I forgot the requirements for it now.

_________________
TinT


Top
 Post subject: Re: String Art Plug-in
PostPosted: Sun Dec 10, 2023 6:02 am  (#4) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4527
Location: Canada
For circular loom designs: https://tinmantaken.blogspot.com/2023/1 ... signs.html
Image

_________________
TinT


Top
 Post subject: Re: String Art Plug-in
PostPosted: Tue Dec 12, 2023 12:23 am  (#5) 
Offline
GimpChat Member
User avatar

Joined: Dec 09, 2018
Posts: 736
Thank you for the plug-in Tim :tyspin

I think it's one that invites experimentation with multiple runs so maybe it would be nice to put the number of points and string pattern in the new path's name and make it visible.

Here's an outcome I got:

Attachment:
thread1.png
thread1.png [ 631.34 KiB | Viewed 60903 times ]


Attachment:
thread2.png
thread2.png [ 1008.76 KiB | Viewed 60899 times ]


Top
 Post subject: Re: String Art Plug-in
PostPosted: Tue Dec 12, 2023 1:23 am  (#6) 
Offline
GimpChat Member
User avatar

Joined: Sep 24, 2010
Posts: 12825
Really cool, Tran. :)


Attachments:
pipe_cleaner_stringart.png
pipe_cleaner_stringart.png [ 3.71 MiB | Viewed 60888 times ]

_________________
Lyle

Psalm 109:8

Image
Top
 Post subject: Re: String Art Plug-in
PostPosted: Tue Dec 12, 2023 1:30 am  (#7) 
Offline
GimpChat Member
User avatar

Joined: Sep 24, 2010
Posts: 12825
Of course I had to droste the result. lol

:)


Attachments:
pipe_cleaner_stringart_droste.jpg
pipe_cleaner_stringart_droste.jpg [ 1.79 MiB | Viewed 60884 times ]

_________________
Lyle

Psalm 109:8

Image
Top
 Post subject: Re: String Art Plug-in
PostPosted: Tue Dec 12, 2023 3:18 am  (#8) 
Offline
GimpChat Member
User avatar

Joined: Sep 24, 2010
Posts: 12825
OK; one more before I hit the sack. Call this one, Dream Weaver. :)

https://www.flickr.com/photos/163034485 ... 93/sizes/o

Image

_________________
Lyle

Psalm 109:8

Image


Top
 Post subject: Re: String Art Plug-in
PostPosted: Tue Dec 12, 2023 1:32 pm  (#9) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4527
Location: Canada
Cool experiments peeps. Thanks for trying it out. :D

_________________
TinT


Top
 Post subject: Re: String Art Plug-in
PostPosted: Wed Dec 13, 2023 2:02 am  (#10) 
Offline
GimpChat Member
User avatar

Joined: Sep 24, 2010
Posts: 12825
OK, Tran; call this one Quantum Entanglement. Used G'MIC's Conformal Mapping this time. :)


Attachments:
Quantum Entanglement.jpg
Quantum Entanglement.jpg [ 1.38 MiB | Viewed 60659 times ]

_________________
Lyle

Psalm 109:8

Image
Top
 Post subject: Re: String Art Plug-in
PostPosted: Thu Dec 14, 2023 5:33 am  (#11) 
Offline
Global Moderator
User avatar

Joined: Apr 01, 2012
Posts: 8452
Location: On the other side of your screen
You'rs are awesome teapot. Can you give me your settings. I cann't get anywhere near there!

_________________
Image
Free Fun Photo Editing & resources
Poems from the Lord
Gimp Newby
Gimp version 3.2.4 and GMIC-Qt 4.0.2 OS :- Windows 10 Home 64


Top
 Post subject: Re: String Art Plug-in
PostPosted: Thu Dec 14, 2023 5:37 am  (#12) 
Offline
Global Moderator
User avatar

Joined: Apr 01, 2012
Posts: 8452
Location: On the other side of your screen
Cool Lyle

_________________
Image
Free Fun Photo Editing & resources
Poems from the Lord
Gimp Newby
Gimp version 3.2.4 and GMIC-Qt 4.0.2 OS :- Windows 10 Home 64


Top
 Post subject: Re: String Art Plug-in
PostPosted: Thu Dec 14, 2023 10:34 am  (#13) 
Offline
GimpChat Member
User avatar

Joined: Dec 09, 2018
Posts: 736
Lyle, Loving all your pipe cleaner outcomes :yes

In the first one, it's cool you made it look three dimensional with the back section darker. Just trying to figure out how you did it, was it make the front and back separately and then make the back darker?

sallyanne wrote:
You'rs are awesome teapot. Can you give me your settings. I cann't get anywhere near there!

Thank you sallyanne :tyspin

My settings were:

Image size 1000x1000.
Input path a circle of diameter 876

A string art plugin settings:
Points on path: 200
String pattern: 58,99,150

First image, stroke path:
Stroke line:
Solid colour
Antialiasing ticked
Line width 2

Second image, using the same path:
On a layer with a black background:
Path to selection.
Fill with white.


Top
 Post subject: Re: String Art Plug-in
PostPosted: Thu Dec 14, 2023 7:15 pm  (#14) 
Offline
GimpChat Member
User avatar

Joined: Jul 04, 2019
Posts: 282
Location: Lake Havasu City, Arizona, USA
Tim, thanks for the script! :cool
teapot, very nice! :)
Lyle, awesome work! :bigthup

_________________
Charles


Top
 Post subject: Re: String Art Plug-in
PostPosted: Fri Dec 15, 2023 7:13 am  (#15) 
Offline
Global Moderator
User avatar

Joined: Apr 01, 2012
Posts: 8452
Location: On the other side of your screen
Have experimented a little. Thanks for the plugin.
All the same but a little bit different

_________________
Image
Free Fun Photo Editing & resources
Poems from the Lord
Gimp Newby
Gimp version 3.2.4 and GMIC-Qt 4.0.2 OS :- Windows 10 Home 64


Top
 Post subject: Re: String Art Plug-in
PostPosted: Fri Dec 15, 2023 11:02 pm  (#16) 
Offline
GimpChat Member
User avatar

Joined: Sep 24, 2010
Posts: 12825
Appreciate the comments.

To answer TP's query, it's not really that hard. I dup, rotate the top layer a bit so that the points divide the ones on the bottom layer (may 5 degrees tops but likely a lot less than that; eyeballed the operation). I then selected the transparent areas above and the center of the top layer and grow it a tad, click bottom layer and chose delete so the points of it won't show (just realized that I might not have had to do the center layer; lol), and then darken the bottom layer a tad (dupped the bottom layer and set the one above it to difference and adjusted opacity to taste) and voila. :)

edit:

Forgot to mention that Sallyanne made some really cool results. :)

_________________
Lyle

Psalm 109:8

Image


Top
 Post subject: Re: String Art Plug-in
PostPosted: Sat Dec 16, 2023 1:42 pm  (#17) 
Offline
GimpChat Member
User avatar

Joined: Dec 09, 2018
Posts: 736
Lyle, Thank you for your explanation :coolthup

sallyanne, Looking good :kpix


Top
 Post subject: Re: String Art Plug-in
PostPosted: Sat Dec 16, 2023 7:23 pm  (#18) 
Offline
GimpChat Member
User avatar

Joined: Jul 06, 2013
Posts: 2662
Location: California
Is there a downloadable file available yet?


Top
 Post subject: Re: String Art Plug-in
PostPosted: Sat Dec 16, 2023 11:26 pm  (#19) 
Offline
Global Moderator
User avatar

Joined: Apr 01, 2012
Posts: 8452
Location: On the other side of your screen
Attachment:
stringartplugin byTin.7z
Thank you teapot and Lyle

@makenzieh, just copy one or both of the codes (separately) and paste them into a notepad (notepad++ is preferred but not necessary.) Save as a .py in your plugins folder.

I only did the first one. oh the codes are in the first couple of posts

_________________
Image
Free Fun Photo Editing & resources
Poems from the Lord
Gimp Newby
Gimp version 3.2.4 and GMIC-Qt 4.0.2 OS :- Windows 10 Home 64


Top
 Post subject: Re: String Art Plug-in
PostPosted: Sun Dec 17, 2023 4:51 am  (#20) 
Offline
GimpChat Member
User avatar

Joined: Aug 08, 2016
Posts: 2855
Location: East Midlands of England
You are on fire again Tim. Amazing outcomes by all thanks to you.

I wish I had had this one in 2018 when I did it all by hand...

Image

and how it might look with your plug-in...

Image

Perhaps I need to re-visit!

_________________
Image
"Let no one steal your dreams."
Paul Cookson

Custom Font Links
2.10 Tools
Character Paths
White Bases


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

All times are UTC - 5 hours [ DST ]



* Login  



Powered by phpBB3 © phpBB Group