It is currently Sat Jun 06, 2026 5:29 am


All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Surround Me Plug-in
PostPosted: Thu Jun 20, 2024 10:57 pm  (#1) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
Something that spawned I think from Custom Shaped Looms and the make copies forgot the name.
This plug-in will draw surrounders shapes/art around another shape using initial surrounder shape relative locations/rotation

1. On a separate layer with transparency, create a shape (of surroundee)
Image

2. On another layer draw a surrounder (name it surrounder or something that you can remember, you'll have to choose this layer in later steps).
Image

3. Run plug-in from menu (make sure you run it on the surroundEE layer and not the surroundER)
Image

4. Select surrounder layer made in step 2. and set number of surrounders you want to populate
Image

5. Plug-in will make copies of your surrounder layer and rotate it appropriately to surround your surroundee
Image



Plug-in code below (save as .py file and put in your GIMP's /plug-ins/ folder found in Edit/Preferences/Folders/plug-ins)
#!/usr/bin/env python
# Author: (Tin Tran)
# Created On: 2024.06.20
# License: Open source whatever the GIMP's license is.
# Revisions:
# 0.1 Initial Version

from gimpfu import *
import math
def slope_to_angle(slope):
    angle_radians = math.atan(slope)
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def angle_from_points(x1, y1, x2, y2):
    # Calculate the difference in coordinates
    dx = x2 - x1
    dy = y2 - y1
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def full_rotation_angle(dx, dy):
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
   
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
   
    # Ensure the angle is positive and in the range [0, 360)
    if angle_degrees < 0:
        angle_degrees += 360
   
    return angle_degrees

def surround_me(image,layer,surrounder,surrounders):
    pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE,layer)

    #grab the vector after creation
    pdb.plug_in_sel2path(image,layer)
    active_vectors = pdb.gimp_image_get_active_vectors(image)

    pdb.gimp_image_set_active_layer(image,surrounder)
    pdb.plug_in_autocrop_layer(image,surrounder)
    sx,sy = pdb.gimp_drawable_offsets(surrounder)
    cx,cy = sx+surrounder.width/2,sy+surrounder.height/2


    num_strokes, stroke_ids = pdb.gimp_vectors_get_strokes(active_vectors)
    length = pdb.gimp_vectors_stroke_get_length(active_vectors,stroke_ids[0],0.1)
   
    minimum_dist = 1000000
    surrounder_atlength = 0
    for d in range(0,int(length-1)):
        pdb.gimp_progress_update(float(d)/length)
        x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],d,0.1)
        away_dist = ((cx-x)**2+(cy-y)**2)**0.5
        if (away_dist<minimum_dist):
            minimum_dist = away_dist
            surrounder_atlength = d #save this length

    #here we should have a closest away dist stored in minimum_dist and surrounder_atlength pointing to that location
    x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],surrounder_atlength-0.1,0.1)
    x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],surrounder_atlength,0.1)
    angle = full_rotation_angle(x-x1,y-y1)

    angle_tosurrounder = angle_from_points(x,y,cx,cy)-angle #this will tell us how to rotate surrounder
    section = float(length)/surrounders
    for d in range(0,int(surrounders)):
        d = section*d+surrounder_atlength
        if (d>=length): #if it's longer then length then we subtract length like modding operation
            d = d-length
        if d > 0.1:   
           x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],d-0.1,0.1)
           x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],d,0.1)
        else:
           x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],d,0.1)
           x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke_ids[0],d+0.1,0.1)
        anglenew = full_rotation_angle(x-x1,y-y1)   
        angle_tosurrounder_inrad = math.radians(anglenew-angle+angle_tosurrounder)
        dx = x+math.cos(angle_tosurrounder_inrad)*minimum_dist
        dy = y+math.sin(angle_tosurrounder_inrad)*minimum_dist
        layer_copy = pdb.gimp_layer_new_from_drawable(surrounder,image)
        pdb.gimp_image_insert_layer(image,layer_copy,None,0)
        offx = dx-surrounder.width/2
        offy = dy-surrounder.height/2
        pdb.gimp_layer_set_offsets(layer_copy,offx,offy)
        pdb.gimp_item_transform_rotate(layer_copy,math.radians(anglenew-angle),TRUE,0,0)
    pdb.gimp_item_set_visible(surrounder,FALSE)

register(
    "python_fu_surround_me",
    "description",
    "longer description",
    "author name",
    "copyright name",
    "2024.06.20",
    "Surround Me...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    [
    #INPUT BEGINS
    (PF_IMAGE, "image", "Image", None),
    (PF_DRAWABLE,   "layer", "Drawable", None),
    (PF_DRAWABLE,   "surrounder", "Surrounder:", None),
    (PF_INT, "surrounders", "# of Surrounders:", 18), # PF_INT8, PF_INT16, PF_INT32  similar but no difference in Python.
    #INPUT ENDS
    ],
    [],
    surround_me,
    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 (found online years back that I didn't want to constantly look up)
# Since GIMP is free anyways, I thought it would be handy here for me to CUT and PASTE, CHANGE, USE.
#           (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.


version 0.2 handles shapes with holes
#!/usr/bin/env python
# Author: (Tin Tran)
# Created On: 2024.06.20
# License: Open source whatever the GIMP's license is.
# Revisions:
# 0.1 Initial Version
# 0.2 Handles multi-stroked shape (ie shape with hole(s))

from gimpfu import *
import math
def slope_to_angle(slope):
    angle_radians = math.atan(slope)
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def angle_from_points(x1, y1, x2, y2):
    # Calculate the difference in coordinates
    dx = x2 - x1
    dy = y2 - y1
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def full_rotation_angle(dx, dy):
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
   
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
   
    # Ensure the angle is positive and in the range [0, 360)
    if angle_degrees < 0:
        angle_degrees += 360
   
    return angle_degrees

def surround_me(image,layer,surrounder,surrounders):
    pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE,layer)

    #grab the vector after creation
    pdb.plug_in_sel2path(image,layer)
    active_vectors = pdb.gimp_image_get_active_vectors(image)

    pdb.gimp_image_set_active_layer(image,surrounder)
    pdb.plug_in_autocrop_layer(image,surrounder)
    sx,sy = pdb.gimp_drawable_offsets(surrounder)
    cx,cy = sx+surrounder.width/2,sy+surrounder.height/2


    num_strokes, stroke_ids = pdb.gimp_vectors_get_strokes(active_vectors)
    minimum_dist = 1000000
    surrounder_atlength = 0
    surrounder_stroke = 0
    length = 0.0
    for stroke in stroke_ids:
        length += pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,0.1)
        for d in range(0,int(length-1)):
            pdb.gimp_progress_update(float(d)/length)
            x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,0.1)
            away_dist = ((cx-x)**2+(cy-y)**2)**0.5
            if (away_dist<minimum_dist):
                minimum_dist = away_dist
                surrounder_atlength = d #save this length
                surrounder_stroke = stroke

    #here we should have a closest away dist stored in minimum_dist and surrounder_atlength pointing to that location
    x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,surrounder_stroke,surrounder_atlength-0.1,0.1)
    x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,surrounder_stroke,surrounder_atlength,0.1)
    angle = full_rotation_angle(x-x1,y-y1)

    angle_tosurrounder = angle_from_points(x,y,cx,cy)-angle #this will tell us how to rotate surrounder
    for stroke in stroke_ids:
       thislength = pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,0.1)
        section = float(length)/surrounders
        for d in range(0,int(float(thislength)/section)):
            d = section*d+surrounder_atlength
            while (d>=thislength): #if it's longer then length then we subtract length like modding operation
                d = d-thislength
            if d > 0.1:   
                x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d-0.1,0.1)
                x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,0.1)
            else:
                x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,0.1)
                x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d+0.1,0.1)
            anglenew = full_rotation_angle(x-x1,y-y1)   
            angle_tosurrounder_inrad = math.radians(anglenew-angle+angle_tosurrounder)
            dx = x+math.cos(angle_tosurrounder_inrad)*minimum_dist
            dy = y+math.sin(angle_tosurrounder_inrad)*minimum_dist
            layer_copy = pdb.gimp_layer_new_from_drawable(surrounder,image)
            pdb.gimp_image_insert_layer(image,layer_copy,None,0)
            offx = dx-surrounder.width/2
            offy = dy-surrounder.height/2
            pdb.gimp_layer_set_offsets(layer_copy,offx,offy)
            pdb.gimp_item_transform_rotate(layer_copy,math.radians(anglenew-angle),TRUE,0,0)
    pdb.gimp_item_set_visible(surrounder,FALSE)

register(
    "python_fu_surround_me",
    "description",
    "longer description",
    "author name",
    "copyright name",
    "2024.06.20",
    "Surround Me...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    [
    #INPUT BEGINS
    (PF_IMAGE, "image", "Image", None),
    (PF_DRAWABLE,   "layer", "Drawable", None),
    (PF_DRAWABLE,   "surrounder", "Surrounder:", None),
    (PF_INT, "surrounders", "# of Surrounders:", 18), # PF_INT8, PF_INT16, PF_INT32  similar but no difference in Python.
    #INPUT ENDS
    ],
    [],
    surround_me,
    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 (found online years back that I didn't want to constantly look up)
# Since GIMP is free anyways, I thought it would be handy here for me to CUT and PASTE, CHANGE, USE.
#           (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.


Don't update progress too often as it slows it down
#!/usr/bin/env python
# Author: (Tin Tran)
# Created On: 2024.06.20
# License: Open source whatever the GIMP's license is.
# Revisions:
# 0.1 Initial Version
# 0.2 Handles multi-stroked shape (ie shape with hole(s))
# 0.25 Don't update progress too often it slows it down
from gimpfu import *
import math
import time
def slope_to_angle(slope):
    angle_radians = math.atan(slope)
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def angle_from_points(x1, y1, x2, y2):
    # Calculate the difference in coordinates
    dx = x2 - x1
    dy = y2 - y1
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def full_rotation_angle(dx, dy):
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
   
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
   
    # Ensure the angle is positive and in the range [0, 360)
    if angle_degrees < 0:
        angle_degrees += 360
   
    return angle_degrees

def surround_me(image,layer,surrounder,surrounders):
    pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE,layer)

    #grab the vector after creation
    pdb.plug_in_sel2path(image,layer)
    active_vectors = pdb.gimp_image_get_active_vectors(image)

    pdb.gimp_image_set_active_layer(image,surrounder)
    pdb.plug_in_autocrop_layer(image,surrounder)
    sx,sy = pdb.gimp_drawable_offsets(surrounder)
    cx,cy = sx+surrounder.width/2,sy+surrounder.height/2


    num_strokes, stroke_ids = pdb.gimp_vectors_get_strokes(active_vectors)
    minimum_dist = 1000000
    surrounder_atlength = 0
    surrounder_stroke = 0
    length = 0.0
    precision = 1.0
    start = time.time()
    for stroke in stroke_ids:
        length += pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,precision)
        for d in range(0,int(length-1)):
            if (d%500==0):
                pdb.gimp_progress_update(float(d)/length)
            x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,precision)
            away_dist = ((cx-x)**2+(cy-y)**2)**0.5
            if (away_dist<minimum_dist):
                minimum_dist = away_dist
                surrounder_atlength = d #save this length
                surrounder_stroke = stroke
    pdb.gimp_message(time.time()-start)
    #here we should have a closest away dist stored in minimum_dist and surrounder_atlength pointing to that location
    x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,surrounder_stroke,surrounder_atlength-0.1,precision)
    x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,surrounder_stroke,surrounder_atlength,precision)
    angle = full_rotation_angle(x-x1,y-y1)

    angle_tosurrounder = angle_from_points(x,y,cx,cy)-angle #this will tell us how to rotate surrounder
    for stroke in stroke_ids:
       thislength = pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,precision)
        section = float(length)/surrounders
        for d in range(0,int(float(thislength)/section)):
            d = section*d+surrounder_atlength
            while (d>=thislength): #if it's longer then length then we subtract length like modding operation
                d = d-thislength
            if d > 0.1:   
                x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d-0.1,precision)
                x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,precision)
            else:
                x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,precision)
                x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d+0.1,precision)
            anglenew = full_rotation_angle(x-x1,y-y1)   
            angle_tosurrounder_inrad = math.radians(anglenew-angle+angle_tosurrounder)
            dx = x+math.cos(angle_tosurrounder_inrad)*minimum_dist
            dy = y+math.sin(angle_tosurrounder_inrad)*minimum_dist
            layer_copy = pdb.gimp_layer_new_from_drawable(surrounder,image)
            pdb.gimp_image_insert_layer(image,layer_copy,None,0)
            offx = dx-surrounder.width/2
            offy = dy-surrounder.height/2
            pdb.gimp_layer_set_offsets(layer_copy,offx,offy)
            pdb.gimp_item_transform_rotate(layer_copy,math.radians(anglenew-angle),TRUE,0,0)
    pdb.gimp_item_set_visible(surrounder,FALSE)

register(
    "python_fu_surround_me",
    "description",
    "longer description",
    "author name",
    "copyright name",
    "2024.06.20",
    "Surround Me...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    [
    #INPUT BEGINS
    (PF_IMAGE, "image", "Image", None),
    (PF_DRAWABLE,   "layer", "Drawable", None),
    (PF_DRAWABLE,   "surrounder", "Surrounder:", None),
    (PF_INT, "surrounders", "# of Surrounders:", 18), # PF_INT8, PF_INT16, PF_INT32  similar but no difference in Python.
    #INPUT ENDS
    ],
    [],
    surround_me,
    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 (found online years back that I didn't want to constantly look up)
# Since GIMP is free anyways, I thought it would be handy here for me to CUT and PASTE, CHANGE, USE.
#           (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.



v0.3 (Fixed bugs, I nearly went nuts)
#!/usr/bin/env python
# Author: (Tin Tran)
# Created On: 2024.06.20
# License: Open source whatever the GIMP's license is.
# Revisions:
# 0.1 Initial Version
# 0.2 Handles multi-stroked shape (ie shape with hole(s))
# 0.25 Had errors
# 0.3 Errors fixed for multiple strokes in a possibly weird shape like with holes.
from gimpfu import *
import math
def slope_to_angle(slope):
    angle_radians = math.atan(slope)
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def angle_from_points(x1, y1, x2, y2):
    # Calculate the difference in coordinates
    dx = x2 - x1
    dy = y2 - y1
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
    return angle_degrees

def full_rotation_angle(dx, dy):
    # Calculate the angle in radians
    angle_radians = math.atan2(dy, dx)
   
    # Convert the angle to degrees
    angle_degrees = math.degrees(angle_radians)
   
    # Ensure the angle is positive and in the range [0, 360)
    if angle_degrees < 0:
        angle_degrees += 360
   
    return angle_degrees

def surround_me(image,layer,surrounder,surrounders):
    pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE,layer)

    #grab the vector after creation
    pdb.plug_in_sel2path(image,layer)
    active_vectors = pdb.gimp_image_get_active_vectors(image)
    pdb.gimp_selection_none(image)

    pdb.gimp_image_set_active_layer(image,surrounder)
    pdb.plug_in_autocrop_layer(image,surrounder)
    sx,sy = pdb.gimp_drawable_offsets(surrounder)
    cx,cy = sx+surrounder.width/2,sy+surrounder.height/2


    num_strokes, stroke_ids = pdb.gimp_vectors_get_strokes(active_vectors)
    minimum_dist = 1000000
    surrounder_atlength = 0
    surrounder_stroke = 0
    surrounder_tlength = 0
    length = 0.0
    precision = 0.1
    tlength = 0.0
    clength = 0.0
    for stroke in stroke_ids:
        #total length of all strokes
        tlength += pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,precision)
    strokeindex = 0   
    for stroke in stroke_ids:
        length = pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,precision)
        for d in range(0,int(length-1)):
            if (d%100==0):
                pdb.gimp_progress_update(float(d)/length)
            x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,stroke,d,precision)
            away_dist = ((cx-x)**2+(cy-y)**2)**0.5
            if (away_dist<minimum_dist):
                minimum_dist = away_dist
                surrounder_atlength = d #save this length of current stroke
                surrounder_tlength = d+clength
                surrounder_stroke = stroke #marks which stroke the surrounder is on
                surrounder_strokeindex = strokeindex
        strokeindex += 1       
        clength += length       
    #here we should have a closest away dist stored in minimum_dist and surrounder_atlength pointing to that location
    x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,surrounder_stroke,surrounder_atlength-0.1,precision)
    x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,surrounder_stroke,surrounder_atlength,precision)
    angle = full_rotation_angle(x-x1,y-y1)
    angle_tosurrounder = angle - angle_from_points(x,y,cx,cy) #this will tell us how to rotate surrounder

    clength = 0.0 #crawl current length
    d = 0.0
    dc = 0.0
    section = float(tlength)/surrounders #total length is used to find section
    cstrokeindex = surrounder_strokeindex
    cstroke = stroke_ids[cstrokeindex]
    cdist = surrounder_atlength
    dc = cdist
    running = True
    created = 0;
    while (running):
        thislength = pdb.gimp_vectors_stroke_get_length(active_vectors,cstroke,precision)
        while (True):

            if (dc >= 0.1):
                x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,cstroke,dc-0.1,precision)
                x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,cstroke,dc,precision)
            else:
                x1,y1,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,cstroke,dc,precision)
                x,y,slope,valid = pdb.gimp_vectors_stroke_get_point_at_dist(active_vectors,cstroke,dc+0.1,precision)
            if valid==TRUE:               
                anglenew = full_rotation_angle(x-x1,y-y1)   
                angle_tosurrounder_inrad = math.radians(anglenew-angle_tosurrounder)
                dx = x+math.cos(angle_tosurrounder_inrad)*minimum_dist
                dy = y+math.sin(angle_tosurrounder_inrad)*minimum_dist
                layer_copy = pdb.gimp_layer_new_from_drawable(surrounder,image)
                pdb.gimp_image_insert_layer(image,layer_copy,None,0)
                offx = dx-surrounder.width/2
                offy = dy-surrounder.height/2
                pdb.gimp_layer_set_offsets(layer_copy,offx,offy)
                floating = pdb.gimp_item_transform_rotate(layer_copy,math.radians(anglenew-angle),TRUE,0,0)
                #exit condition when we reach number of surrounders because
                #I suck at logics and it's driving me nuts =============
                created += 1;
                if (created == surrounders):
                    running = False
                    break;
                #=======================================================
            dc += section
            if dc >= thislength:
                break;

        clength += thislength #updates current length to reflect that this past stroke was added
        dc = dc - thislength
        cstrokeindex = (cstrokeindex+1)%len(stroke_ids) #move to next stroke
        cstroke = stroke_ids[cstrokeindex]

       
       
    pdb.gimp_item_set_visible(surrounder,FALSE)

register(
    "python_fu_surround_me",
    "description",
    "longer description",
    "author name",
    "copyright name",
    "2024.06.20",
    "Surround Me...",
    "RGB*",      # Alternately use RGB, RGB*, GRAY*, INDEXED etc.
    [
    #INPUT BEGINS
    (PF_IMAGE, "image", "Image", None),
    (PF_DRAWABLE,   "layer", "Drawable", None),
    (PF_DRAWABLE,   "surrounder", "Surrounder:", None),
    (PF_INT, "surrounders", "# of Surrounders:", 18), # PF_INT8, PF_INT16, PF_INT32  similar but no difference in Python.
    #INPUT ENDS
    ],
    [],
    surround_me,
    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 (found online years back that I didn't want to constantly look up)
# Since GIMP is free anyways, I thought it would be handy here for me to CUT and PASTE, CHANGE, USE.
#           (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.


_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Last edited by trandoductin on Fri Jun 21, 2024 3:25 pm, edited 2 times in total.

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: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 5:03 am  (#2) 
Offline
GimpChat Member
User avatar

Joined: Mar 01, 2014
Posts: 14012
Location: Spain, Aragón
Thank you so much Tin for this plug-in. It seems very nice.
I've tried to set it in the plug-in folder but I can't find it in the list when I open gimp.
I installed 2 version. Have I to set the first version before? Thanks a lot.

_________________
Image

Gimp 2.10.30(samj) portable _ OS Windows 10 Home_ 64bits
Don’t be afraid to start over. It’s a new chance to rebuild what you want.


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 5:59 am  (#3) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
Hey Issabella,
You just need to save version 2 as a .py file and put it in your GIMP's plug-ins folder just like other plug-ins.
You don't have to set anything (I might have over explained things in instructions which might confuse people I guess).

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 7:37 am  (#4) 
Offline
GimpChat Member
User avatar

Joined: Mar 01, 2014
Posts: 14012
Location: Spain, Aragón
Thanks Tin. you explain it perfect, no problem.
My problem is that I have your plug-in installed in place but then it doesn't appear in my list and I don't know how to solve it
Here my shots
Thanks a lot


Attachments:
in my plug-ins folder.jpg
in my plug-ins folder.jpg [ 45.09 KiB | Viewed 2566 times ]
list-Python.jpg
list-Python.jpg [ 24.26 KiB | Viewed 2566 times ]

_________________
Image

Gimp 2.10.30(samj) portable _ OS Windows 10 Home_ 64bits
Don’t be afraid to start over. It’s a new chance to rebuild what you want.
Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 11:01 am  (#5) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
Did you restart GIMP? I know you have more experience than that but I don't know what else it would be.

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 11:19 am  (#6) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
The only other reason I can think of is that I am using the new register call, I don't know when this is taking effect but I am on GIMP 2.10.38
You're on 2.10.30 but I don't think that's why it's not showing though

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 11:49 am  (#7) 
Offline
GimpChat Member
User avatar

Joined: Mar 01, 2014
Posts: 14012
Location: Spain, Aragón
Tin, thanks for you interest on my problem. I'm currently on Gimp 2.10.36
Yes, I closed Gimp and opened it several times to check, but it doesn't appear in the list.
Perhaps somebody helps if they get the copy and can say.
Really I like the effect and I'd love to do it. Thanks Tin.

_________________
Image

Gimp 2.10.30(samj) portable _ OS Windows 10 Home_ 64bits
Don’t be afraid to start over. It’s a new chance to rebuild what you want.


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 12:29 pm  (#8) 
Offline
GimpChat Member
User avatar

Joined: Jul 04, 2019
Posts: 279
Location: Lake Havasu City, Arizona, USA
Tin, There's an indention error (a space character is missing) on the line with this code:
thislength = pdb.gimp_vectors_stroke_get_length(active_vectors,stroke,0.1)

I got the v.25 to load with GIMP startup, but the results of the plugin are not what I was expecting.
Attachment:
File comment: v.25 output
result2.png
result2.png [ 1.12 MiB | Viewed 2532 times ]


This is the XCF file I used:
Attachment:
test.xcf [1.39 MiB]
Downloaded 65 times

_________________
Charles


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 12:33 pm  (#9) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
Oh another reason could be you're using notepad (and it defaults to saving all files as .txt) so even though you're seeing filename.py it's actually filename.py.txt to the filesystem.
so here's a zipped .py file.
But if you want you can open your file in notepad and save as then for file type choose All files instead of just .txt then name it .py and it should save it as .py and not .py .txt which the windows default doesn't show file extensions.
Issabella,Image


Attachments:
File comment: v0.25
surround-me.zip [2.69 KiB]
Downloaded 116 times

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com
Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 12:39 pm  (#10) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
bug encountered as reported by gasmask in 2.5
will look at it now

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 12:59 pm  (#11) 
Offline
GimpChat Member
User avatar

Joined: Mar 01, 2014
Posts: 14012
Location: Spain, Aragón
YESSSSSSS!!!! it works great now. Thank you Tin. :jumpclap :jumpclap :jumpclap
Thank you gasMask for your help, too.
I can play with this plug-in so nice. :coolthup


Attachments:
Tin-Suround-Me-Issa.jpg
Tin-Suround-Me-Issa.jpg [ 149.76 KiB | Viewed 2522 times ]

_________________
Image

Gimp 2.10.30(samj) portable _ OS Windows 10 Home_ 64bits
Don’t be afraid to start over. It’s a new chance to rebuild what you want.
Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 3:29 pm  (#12) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
gasmask:
It looks wrong on your output but it's doing what it's supposed to because your surrounder is closest to the corner of surroundee.
If you put it lower down and closer to the right then it's relative position will find a pixel that is on the rectangle that isn't the exact corner like this
Image

Then you'll something like this (before I fixed the rotation bug)
Image

Anyways I had bugs too so I updated v0.3 in first post (That version nearly made me go nuts but it works now).

Issabella:
I am glad it worked out for you, not sure whichone you used but I think the .zip I uploaded was also buggy.

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Fri Jun 21, 2024 3:35 pm  (#13) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
To explain why gasmask got that
it's relative like this
the top right surrounder is relatively to the top right corner of surroundee.
That's why it is like that minus the rotation bug.
Image

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Sat Jun 22, 2024 11:02 am  (#14) 
Offline
GimpChat Member
User avatar

Joined: May 24, 2021
Posts: 859
Location: SEA - South East Asia
Wow.. that's is a cool plugin :bigthup
Thank you Tin :tyspin

_________________
Patrice


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Sat Jun 22, 2024 11:55 am  (#15) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
Thank you @PixLab, and you're welcome. :D

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Mon Jun 24, 2024 3:07 am  (#16) 
Offline
GimpChat Member
User avatar

Joined: Oct 31, 2020
Posts: 2010
This plugin is doing something similar as stroking a path as a brush with dynamics but I do not understand how one can choose the number of objects.


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Mon Jun 24, 2024 6:56 am  (#17) 
Offline
GimpChat Member
User avatar

Joined: Sep 24, 2010
Posts: 12775
Cool stuff again, Tran. Might make for creating cool Paisleys. :)

_________________
Lyle

Psalm 109:8

Image


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Mon Jun 24, 2024 7:09 am  (#18) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
contrast_ wrote:
This plugin is doing something similar as stroking a path as a brush with dynamics but I do not understand how one can choose the number of objects.

How: just by dividing the length of path number of objects, then brush at those intervals.

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Mon Jun 24, 2024 7:10 am  (#19) 
Offline
Script Coder
User avatar

Joined: May 07, 2014
Posts: 4469
Location: Canada
lylejk wrote:
Cool stuff again, Tran. Might make for creating cool Paisleys. :)

Cool.
Thanks :hi5

_________________
TinT
My GIMP 2.10 plug-in writer Fiverr Gig
Plaid Community Forum
Imageplaid-patterns.com


Top
 Post subject: Re: Surround Me Plug-in
PostPosted: Mon Jun 24, 2024 7:43 am  (#20) 
Offline
GimpChat Member
User avatar

Joined: Mar 23, 2012
Posts: 7388
Location: Göteborg at last!
lylejk wrote:
Cool stuff again, Tran. Might make for creating cool Paisleys. :)

I had the same idea. I mentioned it to AnMal too.


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

All times are UTC - 5 hours [ DST ]



* Login  



Powered by phpBB3 © phpBB Group