You are here

How to get average color within a selection island.

Please see link for more details (http://www.gimptalk.com/forum/how-to-get-average-color-within-a-selectio...), but basically, if I have multiple selection islands, I would like to create fills for those islands based on the average color within each selection island. If just select and blur (large), you just get a single color. That's not what I want. Hopefully someone here (RobA?) can help me. :)

Forums: 

More generally, you want to apply a filter to each island (component that is a closed region) of a selection separately? You want to fill one island with its average color, the next island with its average color, ...?

The Gimp library doesn't appear to have a way to access components of the selection.

I think it could be done by converting the selection to a path, then accessing components of the path (which are strokes?) For each stroke (that is closed), create a new path from the stroke, create a new selection from the path, apply the filter. End by restoring the original selection.

It might be slow, but that doesn't matter.

The details might be devilish. For example, would a selection component at the edge of the image return a closed stroke? Does the Gimp library tell you whether a stroke is closed?

Yes, it is a slow process, but Saul created a cool Script-fu for me at GIMPTalk (for some reason, I get spam filter trigger when I link again; see link in first thread). The no save history version is about 3 times faster (at least on my machine). :)

I began another way to do this. For now, it is a draft that so far, seems to work. It needs more work and testing. For example, it leaves the progress bar weird. When I have more time....

#!/usr/bin/env python

'''
Test apply filter to selection parts

Apply a filter to selection parts.
This might be used when a filter would treat each selection part differently
because of the content or context of the selection part.
If a filter output does not depend on the selection,
e.g. a filter that renders, creates new content, de novo, from scratch ....,
then don't use this plugin, instead filter the entire selection.

In computer programming 'apply a function' means 'iteratively invoke' on say a list.
Which is what this does.
In computer GUI design, an 'Apply' button means 'do it.'
But for the general public, use the long 'Apply filter to selection parts'
to say more clearly what will happen.

Test cases:
doesn't alter existing paths
doesn't alter the selection
doesn't alter the context
doesn't alter layers or channels
'''

'''
Notes about types and terminology.
This can be very confusing.
The below might not be correct: probably a PDB stroke is a SVG polygon.

These contexts of discussion:
Gimp user
PDB type
SVG
Pygimp type
this program

Gimp PDB PDB type SVG Pygimp type this program

Path vectors VECTORS file? vector object a set of strokes
NA strokes int array NA tuple a sequence of stroke items
NA stroke int polyline int (position) a set of lines
NA NA NA polygon NA a closed set of connected lines

'''

from gimpfu import *

def apply_filter_to_stroke(image, drawable, vectors, stroke):
'''
For the given stroke of the given vectors, apply filter to a selection made from stroke.

Doesn't appear to be a gimp_vectors_add_stroke().
There is gimp_vectors_stroke_new_from_points but is only a bezier (for now.)
So we can't use the strategy: add current stroke to a new vectors.
(See a draft of that strategy below.)

Instead, we remove all but the current stroke from a copy of vectors.
'''
workingvectors = gimp.pdb.gimp_vectors_copy(vectors)
gimp.pdb.gimp_image_add_vectors(image, workingvectors, -1)
count, strokes = gimp.pdb.gimp_vectors_get_strokes(workingvectors)
# Remove all but the given stroke.
# !!! Note these are stroke positions, we assume they remain constant among copies.
for strokecopy in strokes:
if not strokecopy == stroke:
print "Removing stroke", strokecopy
gimp.pdb.gimp_vectors_remove_stroke(workingvectors, strokecopy)
# assert workingvectors now has one stroke
gimp.pdb.gimp_selection_none(image)
gimp.pdb.gimp_vectors_to_selection(workingvectors, CHANNEL_OP_ADD, True, True, 2, 2)
# Apply filter to this component of the original selection
gimp.pdb.gimp_edit_fill(drawable, FOREGROUND_FILL)
gimp.pdb.gimp_image_remove_vectors(image, workingvectors)

'''
This is a draft of another strategy, but nonworking.
workingvectors = gimp.pdb.gimp_vectors_new(image, 'working')
gimp.pdb.gimp_image_add_vectors(workingvectors)
gimp.pdb.gimp_vectors_add_stroke(workingvectors, stroke)
gimp.pdb.gimp_selection_none(image)
gimp.pdb.gimp_vectors_to_selection(workingvectors, CHANNEL_OP_ADD, True, True, 2, 2)
# Apply filter to this component of the original selection
gimp.pdb.gimp_edit_fill(drawable, FOREGROUND_FILL)
gimp.pdb.gimp_image_remove_vectors(image, workingvectors)
'''

def plugin_main(image, drawable):

savedselection = gimp.pdb.gimp_selection_save(image) # save the original selection

gimp.pdb.plug_in_sel2path(image, drawable, run_mode=RUN_NONINTERACTIVE)
# Note the new path is now active, but there might be other paths

'''
count, vectors = gimp.pdb.gimp_image_get_vectors(image)
Returns a tuple of integer ID's, not vector objects.
There is no PDB proc to get a vector object by ID ???
'''

vectors = gimp.pdb.gimp_image_get_active_vectors(image)
# print vectors, type(vectors)
# vectors is a single object. Not a Python list, not iterable.

count, strokes = gimp.pdb.gimp_vectors_get_strokes(vectors)
# strokes is a tuple of items of type "stroke positions" i.e. integers
# A stroke is a list of lines, connected in the graph theory sense (shared end points) ??
# Here, I assume a stroke from a path from a selection is closed in the graph theory sense.
# TBD test whether any strokes are NOT closed.
print "Strokes", strokes, type(strokes)
for stroke in strokes:
print stroke, type(stroke)
apply_filter_to_stroke(image, drawable, vectors, stroke)

# Cleanup
gimp.pdb.gimp_image_remove_vectors(image, vectors) # Remove path we created from selection
gimp.pdb.gimp_selection_load(savedselection) # restore the original selection

register(
"python_fu_apply",
"Apply",
"This plugin applies a filter to components of a multipart selection",
"Lloyd Konneker (bootch nc.rr.com)",
"Copyright 2010 Lloyd Konneker",
"2010",
"/Filters/_Apply",
"*", # image types, !!! TBD But test the filter has compatible image types
[], # TBD Should be a choice of filters here
[],
plugin_main,
)

if __name__ == "__main__":
# if invoked from Gimp app as a plugin print "Starting apply"
main()

It would probably be helpful to wrap your code within "[pre]" and "[/pre]" tags (substitute "<" and ">" for the brackets); this will preserve Python's invisible syntax.

while not done:
  print "Repeating!"
  do_something()
print "Done!"

Sorry, I was in a hurry, didn't preview, didn't know code tags.

#!/usr/bin/env python

'''
 Test apply filter to selection parts
 
 Apply a filter to selection parts.
 This might be used when a filter would treat each selection part differently
 because of the content or context of the selection part.
 If a filter output does not depend on the selection,
 e.g. a filter that renders, creates new content, de novo, from scratch ....,
 then don't use this plugin, instead filter the entire selection.
 
 In computer programming 'apply a function' means 'iteratively invoke' on say a list.
 Which is what this does.
 In computer GUI design, an 'Apply' button means 'do it.'
 But for the general public, use the long 'Apply filter to selection parts'
 to say more clearly what will happen.
 
 Test cases:
  doesn't alter existing paths
  doesn't alter the selection
  doesn't alter the context
  doesn't alter layers or channels
'''

'''
Notes about types and terminology.
This can be very confusing.
The below might not be correct:  probably a PDB stroke is a SVG polygon.

These contexts of discussion:
Gimp user
PDB type
SVG
Pygimp type
this program


Gimp    PDB       PDB type  SVG       Pygimp type    this program

Path    vectors   VECTORS   file?     vector object  a set of strokes
NA      strokes   int array NA        tuple          a sequence of stroke items
NA      stroke    int       polyline  int (position) a set of lines
NA      NA        NA        polygon   NA             a closed set of connected lines

'''

from gimpfu import * 

def apply_filter_to_stroke(image, drawable, vectors, stroke):
  ''' 
  For the given stroke of the given vectors, apply filter to a selection made from stroke.
  
  Doesn't appear to be a gimp_vectors_add_stroke().
  There is gimp_vectors_stroke_new_from_points but is only a bezier (for now.)
  So we can't use the strategy: add current stroke to a new vectors.
  (See a draft of that strategy below.)
  
  Instead, we remove all but the current stroke from a copy of vectors.
  '''
  workingvectors = gimp.pdb.gimp_vectors_copy(vectors)
  gimp.pdb.gimp_image_add_vectors(image, workingvectors, -1)
  count, strokes = gimp.pdb.gimp_vectors_get_strokes(workingvectors)
  # Remove all but the given stroke.
  # !!! Note these are stroke positions, we assume they remain constant among copies.
  for strokecopy in strokes:
    if not strokecopy == stroke:
      print "Removing stroke", strokecopy
      gimp.pdb.gimp_vectors_remove_stroke(workingvectors, strokecopy)
  # assert workingvectors now has one stroke
  gimp.pdb.gimp_selection_none(image)
  gimp.pdb.gimp_vectors_to_selection(workingvectors, CHANNEL_OP_ADD, True, True, 2, 2)
  # Apply filter to this component of the original selection
  gimp.pdb.gimp_edit_fill(drawable, FOREGROUND_FILL)
  gimp.pdb.gimp_image_remove_vectors(image, workingvectors)
  
  '''
  This is a draft of another strategy, but nonworking.
  workingvectors = gimp.pdb.gimp_vectors_new(image, 'working')
  gimp.pdb.gimp_image_add_vectors(workingvectors)
  gimp.pdb.gimp_vectors_add_stroke(workingvectors, stroke)
  gimp.pdb.gimp_selection_none(image)
  gimp.pdb.gimp_vectors_to_selection(workingvectors, CHANNEL_OP_ADD, True, True, 2, 2)
  # Apply filter to this component of the original selection
  gimp.pdb.gimp_edit_fill(drawable, FOREGROUND_FILL)
  gimp.pdb.gimp_image_remove_vectors(image, workingvectors)
  '''

  

def plugin_main(image, drawable):
  
  savedselection = gimp.pdb.gimp_selection_save(image) # save the original selection
  
  gimp.pdb.plug_in_sel2path(image, drawable, run_mode=RUN_NONINTERACTIVE)
  # Note the new path is now active, but there might be other paths
  
  '''
  count, vectors = gimp.pdb.gimp_image_get_vectors(image)
  Returns a tuple of integer ID's, not vector objects.
  There is no PDB proc to get a vector object by ID ???
  '''
  
  vectors = gimp.pdb.gimp_image_get_active_vectors(image)
  # print vectors, type(vectors)
  # vectors is a single object.  Not a Python list, not iterable.

  count, strokes = gimp.pdb.gimp_vectors_get_strokes(vectors)
  # strokes is a tuple of items of type "stroke positions" i.e. integers 
  # A stroke is a list of lines, connected in the graph theory sense (shared end points) ??
  # Here, I assume a stroke from a path from a selection is closed in the graph theory sense.
  # TBD test whether any strokes  are NOT closed.
  print "Strokes", strokes, type(strokes)
  for stroke in strokes:
    print stroke, type(stroke)
    apply_filter_to_stroke(image, drawable, vectors, stroke)
  
  # Cleanup
  gimp.pdb.gimp_image_remove_vectors(image, vectors) # Remove path we created from selection
  gimp.pdb.gimp_selection_load(savedselection) # restore the original selection


register(
    "python_fu_apply",
    "Apply",
    "This plugin applies a filter to components of a multipart selection",
    "Lloyd Konneker (bootch nc.rr.com)",
    "Copyright 2010 Lloyd Konneker",
    "2010",
    "/Filters/_Apply",
    "*", # image types, !!! TBD But test the filter has compatible image types
    [], # TBD Should be a choice of filters here
    [],
    plugin_main,
    )
    
if __name__ == "__main__":
    # if invoked from Gimp app as a plugin    print "Starting apply"
    main()




If you disable selection view (thanks for the tip from RobA), the Script-fu runs even faster. Maybe Saul will post it here too. It's works fantastic. It's like having the Mosaic filter and you can decide the panel design (not limited to Voronoi outline or the few other shapes that it supports). :)

Saul updated the Script-fu; runs on steroids now. Hope he posts it here at the Registry. Really makes fantastic colored mosaics of any type shape for the tiles you can image. Extremely grateful I am to Saul and many thanks to Rob too. If only I had half your talents. :)

On steroids, is the correct way to put it. Works way faster than before. Good job, Saul & Rob A.! Thanks for pointing it out, Lyle.

Hey Saul,

First, again I do appreciate what you did for me on this cool Script. The big issue is when you have a huge number of islands to fill. I wonder if there could be a way to speed things up by just selecting the color at the center of the selection island as opposed to getting the average color within a selection island. Maybe add this as a user option (either average or center color). Not sure how hard it would be to implement though. Just wondering. :)

Subscribe to Comments for "How to get average color within a selection island."