Gimp-Forum.net
Searching for python script islands to layers - Printable Version

+- Gimp-Forum.net (https://www.gimp-forum.net)
+-- Forum: GIMP (https://www.gimp-forum.net/Forum-GIMP)
+--- Forum: Extending the GIMP (https://www.gimp-forum.net/Forum-Extending-the-GIMP)
+--- Thread: Searching for python script islands to layers (/Thread-Searching-for-python-script-islands-to-layers)

Pages: 1 2 3 4


RE: Searching for python script islands to layers - Ofnuts - 01-24-2018

Suggestions:

Code:
tile_cols = int(layer.height / 64)
   if(layer.width % 64 > 0):
       tile_cols += 1

Better written as:
Code:
tile_cols = int( (layer.height+63) / 64)

And:
Code:
pixel = srcTile[tile_x, tile_y]
# if pixel is not completely transparent
if ord( pixel[3] ) > 0:

can be:

Code:
R,G,B,A = srcTile[tile_x, tile_y]
# if pixel is not completely transparent
if ord( A) > 0:
or even:
Code:
R,G,B,A = [ord(c) for c in srcTile[tile_x, tile_y]]
# if pixel is not completely transparent
if A > 0:

Did you try tile.flush() (see https://www.gimp.org/docs/python/index.html)?

Now, the real problem with your code is that you are using fuzzy select (that selects on color) when you should really be selecting on opacity. There may be corner cases where this won't work (for instance, if your drawable has all pixels with identical RGB, the only difference being the alpha value). pdb.gimp_fuzzy_select() is deprecated and replaced by two more recent calls that seem to allow to include opacity in the selection criteria. You should look into this.


RE: Searching for python script islands to layers - mich_lloid - 01-24-2018

I didn't notice that my script didn't work for layers that aren't divisible by 64 without remainder, I mostly work with images with power of 2 like 1024x1024 or 2048x2048 for game related stuff.
The script did work on layers not divisible by 64 like 500x500, but sprites on the right and bottom edge didn't get picked up as objects.
I removed the "if srcTile != None:" line and I got AttributeError: 'NoneType' object has no attribute 'ewidth', the tiles at the right and lower corners of the layer was something wrong.
I was digging into it but didn't get the cause of the error so I thought, just make the "temp_layer" divisible by 64 with "extra_wdth" and "extra_hght" so new version yet again:


Code:
import time

def loop_tiles(img, sample = 1):
   time_start = time.time()
   #samples = 2
   #img = gimp.image_list()[0]
   src_layer = img.active_layer
   layer = src_layer.copy()
   img.add_layer(layer)
   counter = 1
   temp_layer = layer.copy()
   extra_wdth = 64 - (layer.width % 64)
   extra_hght = 64 - (layer.height % 64)
   layer.resize( layer.width + extra_wdth, layer.height + extra_hght, 0, 0)
   tile_rows = int(layer.width / 64)
   #if(layer.width % 64 > 0):
   #     tile_rows += 1
   tile_cols = int(layer.height / 64)
   #if(layer.width % 64 > 0):
   #    tile_cols += 1
   for tile_row in range(tile_rows):
       for tile_col in range(tile_cols):
           #print(tile_row, tile_col)
           #srcTile = layer.get_tile(False, tile_row, tile_col)
           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
           if srcTile != None:
               for tile_x in range(0, srcTile.ewidth, sample):
                   for tile_y in range(0, srcTile.eheight, sample):
                       pixel = srcTile[tile_x, tile_y]
                       R,G,B,A = srcTile[tile_x, tile_y]
                       # if pixel is not completely transparent
                       if ord( A) > 0:
                           layer_pixel_pos_x = tile_col * 64 + tile_x
                           layer_pixel_pos_y = tile_row * 64 + tile_y
                           #print( layer.get_pixel((tile_col * 64) + tile_x, (tile_row * 64) + tile_y) )
                           #pdb.gimp_fuzzy_select(img.active_layer, layer_pixel_pos_x, layer_pixel_pos_y, 254.9 , 2,0,0,0,0)
                           pdb.gimp_fuzzy_select_full(layer , layer_pixel_pos_x, layer_pixel_pos_y, 255, 2, 0, 0, 0, 0, 0, 0, 0)
                           newlayer = layer.copy()
                           img.add_layer(newlayer)
                           x1, y1, x2, y2 = layer.mask_bounds
                           newlayer.name = "Layer %03d (%d, %d, %d, %d)" % (counter, x1, y1, x2 - x1, y2 - y1)
                           counter += 1
                           newlayer.resize(x2 - x1, y2 - y1, -x1, -y1)
                           img.active_layer = layer
                           pdb.gimp_edit_clear(layer)
                           #update tile by updating temp_layer
                           temp_layer = layer.copy()
                           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
   img.remove_layer(layer)
   time_end = time.time()
   print ('time taken: ' + str(time_end - time_start) + ' seconds.')

img = gimp.image_list()[0]
loop_tiles(img)


I'm off to hunting for more bugs in the script, yay!

Some further questions ofnuts if you don't mind:
1) What is the pdb or gimp-fu equivalent of selecting all non-transparent pixels of a layer, the "Alpha to Selection" option when you right-click on layer in layers tab?
I wish gimp was more like blender, you can hover over almost every button and you can get a python equivalent which you can use in the python interpretor, it makes learning python and programming more fun!

2) Do you know how to make the mask of a layer into a new layer manually and with python-fu?

3) fuzzy select suggests "pdb.gimp_image_select_contiguous_color(image, operation, drawable, x, y)" as a replacement, you have to set "context setters" for this to work? How would I go about doing this and how is this better than "pdb.gimp_fuzzy_select_full()"?

PS: about "tile.flush()"
"tile.flush()" doesn't seem to work for me.
My steps to reproduce:
1) create a new image 64x64
2) create a new transparent layer
3) zoom in and draw one pixel at position (1,1) with pencil tool with size 1
4) in gimp console:


Code:
➤> img = gimp.image_list()[0]
➤> layer = img.active_layer
➤> srcTile = layer.get_tile(False, 0, 0)
➤> ord(srcTile[1,1][-1])
255


5) rectangle select tool over pixel or select everything and "edit_clear" by delete
6) in gimp console:


Code:
➤> srcTile.flush()
➤> ord(srcTile[1,1][-1])
255


the pixel at srcTile[1,1] is still 255 while it should be 0
if I do this in console:


Code:
➤> srcTile = layer.get_tile(False, 0, 0)
➤> ord(srcTile[1,1][-1])
255


still no 0 but if I now do this:


Code:
➤> img = gimp.image_list()[0]
➤> layer = img.active_layer
➤> srcTile = layer.get_tile(False, 0, 0)
➤> ord(srcTile[1,1][-1])
0


finally srcTile is updated


RE: Searching for python script islands to layers - Ofnuts - 01-24-2018

1) pdb.gimp_image_select_item(image, operation, item). The API and the UI have different goals, they aren't always identical.

2)
Code:
image.add_layer(pdb.gimp_layer_new_from_drawable(layer.mask,image),0)
But why do you need this? You can work on the mask, it is a drawable and can be used in most calls that you use with a layer. Try this:
  • create an empty image (single white layer)
  • add an alpha channel to that
  • add a layer mask
  • put some black on the mask to create transparent holes
  • remember that at this point your editoing goes to the mask (as stated in the status bar at the bottom)
  • start the wand tool and click on your layer. The marching ants circle the holes
  • in the Layers list, click on the Layer preview and check that the status bar tells you that you are now edting the layer and no longer the mask
  • still with the wand, click on you layer (in the holes or not). The marching ants are along the canvas boundaries
This tells you two things:
  • the wand doesn't care about opacity
  • fuzzy select works on a layer mask so if you want to select by opacity you can use the mask directly (note that the mask will be a one bit-per-pixel drawable, so the pixels in the tiles have a single element).
3) Sorry, read some doc to fast, I thought I had seen an Opacity criterion. But then see 2)


RE: Searching for python script islands to layers - mich_lloid - 01-25-2018

(01-24-2018, 11:38 PM)Ofnuts Wrote: 1) pdb.gimp_image_select_item(image, operation, item). The API and the UI have different goals, they aren't always identical.

2)
Code:
image.add_layer(pdb.gimp_layer_new_from_drawable(layer.mask,image),0)
But why do you need this? You can work on the mask, it is a drawable and can be used in most calls that you use with a layer. Try this:
  • create an empty image (single white layer)
  • add an alpha channel to that
  • add a layer mask
  • put some black on the mask to create transparent holes
  • remember that at this point your editoing goes to the mask (as stated in the status bar at the bottom)
  • start the wand tool and click on your layer. The marching ants circle the holes
  • in the Layers list, click on the Layer preview and check that the status bar tells you that you are now edting the layer and no longer the mask
  • still with the wand, click on you layer (in the holes or not). The marching ants are along the canvas boundaries
This tells you two things:
  • the wand doesn't care about opacity
  • fuzzy select works on a layer mask so if you want to select by opacity you can use the mask directly (note that the mask will be a one bit-per-pixel drawable, so the pixels in the tiles have a single element).
3) Sorry, read some doc to fast, I thought I had seen an Opacity criterion. But then see 2)

Thanks I learned a few new things today, I never looked at the status bar at the bottom to see which layer you are working at, very nice tips!


RE: Searching for python script islands to layers - mich_lloid - 01-25-2018

Ok, I've refined the script some more.
Sometimes spritesheets contain very small objects that are stray pixels and optionally can be discarded.
So I've opted for two layer groups, one for "bad objects" and one for "good objects" where the objects will be placed on the criteria min_size (I set as default objects smaller than 5x5 pixels are considered "bad", but this can be changed of course).

the code sofar:


Code:
import time

def objects2layers(img, sample = 1, min_size = 10):
   time_start = time.time()
   #samples = 2
   #img = gimp.image_list()[0]
   src_layer = img.active_layer
   layer = src_layer.copy()
   img.add_layer(layer)
   counter_good = 1
   counter_bad = 1
   temp_layer = layer.copy()
   extra_wdth = 64 - (layer.width % 64)
   extra_hght = 64 - (layer.height % 64)
   layer.resize( layer.width + extra_wdth, layer.height + extra_hght, 0, 0)
   tile_rows = int(layer.width / 64)
   #if(layer.width % 64 > 0):
   #     tile_rows += 1
   tile_cols = int(layer.height / 64)
   #if(layer.width % 64 > 0):
   #    tile_cols += 1
   for tile_row in range(tile_rows):
       for tile_col in range(tile_cols):
           #print(tile_row, tile_col)
           #srcTile = layer.get_tile(False, tile_row, tile_col)
           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
           if srcTile != None:
               for tile_x in range(0, srcTile.ewidth, sample):
                   for tile_y in range(0, srcTile.eheight, sample):
                       pixel = srcTile[tile_x, tile_y]
                       R,G,B,A = srcTile[tile_x, tile_y]
                       # if pixel is not completely transparent
                       if ord( A) > 0:
                           layer_pixel_pos_x = tile_col * 64 + tile_x
                           layer_pixel_pos_y = tile_row * 64 + tile_y
                           #print( layer.get_pixel((tile_col * 64) + tile_x, (tile_row * 64) + tile_y) )
                           #pdb.gimp_fuzzy_select(img.active_layer, layer_pixel_pos_x, layer_pixel_pos_y, 254.9 , 2,0,0,0,0)
                           pdb.gimp_fuzzy_select_full(layer , layer_pixel_pos_x, layer_pixel_pos_y, 255, 2, 0, 0, 0, 0, 0, 0, 0)
                           newlayer = layer.copy()
                           x1, y1, x2, y2 = layer.mask_bounds
                           if x2 - x1 < min_size and y2 - y1 < min_size:
                               # "bad objects", objects that er too small
                               if counter_bad == 1:
                                   layer_group_bad = pdb.gimp_layer_group_new(img)
                                   layer_group_bad.name = 'small objects'
                                   img.add_layer(layer_group_bad)
                               newlayer.name = "trash %03d (%d, %d, %d, %d)" % (counter_bad, x1, y1, x2 - x1, y2 - y1)
                               counter_bad += 1
                               img.active_layer = layer_group_bad
                               img.add_layer(newlayer)
                           else:
                               # "good objects"
                               if counter_good == 1:
                                   layer_group_good = pdb.gimp_layer_group_new(img)
                                   layer_group_good.name = 'objects'
                                   img.add_layer(layer_group_good)
                               newlayer.name = "object %03d (%d, %d, %d, %d)" % (counter_good, x1, y1, x2 - x1, y2 - y1)
                               counter_good += 1
                               img.active_layer = layer_group_good
                               img.add_layer(newlayer)
                           newlayer.resize(x2 - x1, y2 - y1, -x1, -y1)
                           img.active_layer = layer
                           pdb.gimp_edit_clear(layer)
                           #update tile by updating temp_layer
                           temp_layer = layer.copy()
                           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
   img.remove_layer(layer)
   time_end = time.time()
   print ('time taken: ' + str(time_end - time_start) + ' seconds.')

img = gimp.image_list()[0]
objects2layers(img)


Question for you ofnuts if you don't mind:
1) Are there gimp-fu commands for scrolling/panning and zooming on the canvas? On big images it can be hard to find very small objects on the canvas.
In Blender for example you can zoom and focus on an object or its vertices with shortcut numpad ..

Also on the matter of "fuzzy select tool" pdb.gimp_fuzzy_select_full(drawable, x, y, threshold, operation, antialias, feather, feather_radius_x, feather_radius_y, sample_merged, select_transparent, select_criterion).
I think this is the best approach to select objects/islands because fully transparent pixels will never be selected, you can see that if you select the wand and then check-off "Select transparent areas" in Tool Options.
If you click on fully transparent areas on the canvas, the selection turns to none, and you can safely set threshold to 255, only islands/will be selected when you click on them, nothing else.


RE: Searching for python script islands to layers - Ofnuts - 01-25-2018

Personally I wouldn't worry about groups of good and bad. Have a threshold parameter in the start dialog that gives you decent defaults, and keep only the good.

1) No, and as afar as I know its a matter of design, the scripts cannot interfere with the Ui (they can only create new Image windows, an ddelete those they created themselves).

2) Possibly. I don't know all the corners of the Gimp API Smile

Still the hunt-and-peck approach to selection isn't very efficient. If the objects are in a regular grid, you can aim for the center of each tile, and if you didn't find anything there start spiraling away until you hit something.


RE: Searching for python script islands to layers - mich_lloid - 01-26-2018

Ok, I updated the script by replacing '64' with 'tile_width' and 'tile_height' so it works if gimp ever lets tilesize be changed from 64 to something else.
I also replaced 'deprecated' gimp_fuzzy_select_full with gimp_image_select_contiguous_color if gimp ever decides to remove deprecated functions which I doubt.
Also sanity check if layer has alpha and works on rgb, greyscale and indexed (gif) images, if no alpha found print in console that there was no alpha found and stop script.
So only requirements for this script to work with an image:
-layer of image must have an alpha
-must apply layer mask if layer has one
script:

Code:
def objects2layers(img, sample = 1, min_size = 10):
   time_start = time.time()
   src_layer = img.active_layer
   # if image is rgb mode and has alpha channel
   if img.base_type == 0:
       img_mode = 1
       if img.active_layer.bpp != 4:
           print('this layer has no alpha')
           return
   # if image is greyscale or indexed mode (gif) and has alpha channel
   if img.base_type == 1 or img.base_type == 2:
       img_mode = 2
       if img.active_layer.bpp != 2:
           print('this layer has no alpha')
           return
   layer = src_layer.copy()
   tile_width = gimp.tile_width()
   tile_height = gimp.tile_height()
   pdb.gimp_context_set_antialias(0)
   pdb.gimp_context_set_feather(0)
   pdb.gimp_context_set_sample_merged(0)
   pdb.gimp_context_set_sample_criterion(0)
   pdb.gimp_context_set_sample_threshold(1)
   pdb.gimp_context_set_sample_transparent(0)
   img.add_layer(layer)
   counter_good = 1
   counter_bad = 1
   temp_layer = layer.copy()
   extra_wdth = tile_width - (layer.width % tile_width)
   extra_hght = tile_height - (layer.height % tile_height)
   layer.resize( layer.width + extra_wdth, layer.height + extra_hght, 0, 0)
   tile_rows = int(layer.width / tile_width)
   tile_cols = int(layer.height / tile_height)
   for tile_row in range(tile_rows):
       for tile_col in range(tile_cols):
           #print(tile_row, tile_col)
           #srcTile = layer.get_tile(False, tile_row, tile_col)
           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
           if srcTile != None:
               for tile_x in range(0, srcTile.ewidth, sample):
                   for tile_y in range(0, srcTile.eheight, sample):
                       pixel = srcTile[tile_x, tile_y]
                       #RGBA image
                       if img_mode == 1:
                           R,G,B,A = srcTile[tile_x, tile_y]
                       #indexed image like gif
                       if img_mode == 2:
                           I,A = srcTile[tile_x, tile_y]
                       # if pixel is not completely transparent
                       if ord( A) > 0:
                           layer_pixel_pos_x = tile_col * tile_width + tile_x
                           layer_pixel_pos_y = tile_row * tile_height + tile_y
                           #print( layer.get_pixel((tile_col * tile_size) + tile_x, (tile_row * tile_size) + tile_y) )
                           #pdb.gimp_fuzzy_select(img.active_layer, layer_pixel_pos_x, layer_pixel_pos_y, 254.9 , 2,0,0,0,0)
                           #pdb.gimp_fuzzy_select_full(layer , layer_pixel_pos_x, layer_pixel_pos_y, 255, 2, 0, 0, 0, 0, 0, 0, 0)
                           pdb.gimp_image_select_contiguous_color(img, 2, layer, layer_pixel_pos_x, layer_pixel_pos_y)
                           newlayer = layer.copy()
                           x1, y1, x2, y2 = layer.mask_bounds
                           if x2 - x1 < min_size and y2 - y1 < min_size:
                               # "bad objects", objects that er too small
                               if counter_bad == 1:
                                   layer_group_bad = pdb.gimp_layer_group_new(img)
                                   layer_group_bad.name = 'small objects'
                                   img.add_layer(layer_group_bad)
                               newlayer.name = "trash %03d (%d, %d, %d, %d)" % (counter_bad, x1, y1, x2 - x1, y2 - y1)
                               counter_bad += 1
                               img.active_layer = layer_group_bad
                           else:
                               # "good objects"
                               if counter_good == 1:
                                   layer_group_good = pdb.gimp_layer_group_new(img)
                                   layer_group_good.name = 'objects'
                                   img.add_layer(layer_group_good)
                               newlayer.name = "object %03d (%d, %d, %d, %d)" % (counter_good, x1, y1, x2 - x1, y2 - y1)
                               counter_good += 1
                               img.active_layer = layer_group_good
                           img.add_layer(newlayer)
                           newlayer.resize(x2 - x1, y2 - y1, -x1, -y1)
                           img.active_layer = layer
                           pdb.gimp_edit_clear(layer)
                           #update tile by updating temp_layer
                           temp_layer = layer.copy()
                           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
   img.remove_layer(layer)
   time_end = time.time()
   print ('time taken: ' + str(time_end - time_start) + ' seconds.')

img = gimp.image_list()[0]
objects2layers(img)

Edit: I thought this script also worked for indexed images, the sprites/objects were showing in the layer preview but not on the canvas. I thought "what the ?!", turns out indexed images don't allow layer groups, I'll have to patch that later...


RE: Searching for python script islands to layers - mich_lloid - 01-26-2018

Ok, now script works for all image modes rgb, grayscale and index, as long as the active layer has transparency

Code:
import time

def objects2layers(img, sample = 1, min_size = 10):
   time_start = time.time()
   src_layer = img.active_layer
   # if image is rgb mode and has alpha channel
   if img.base_type == 0:
       img_mode = 'RGB'
       if img.active_layer.bpp != 4:
           print('this layer has no alpha')
           return
   # if image is greyscale and has alpha channel
   if img.base_type == 1:
       img_mode = 'Greyscale'
       if img.active_layer.bpp != 2:
           print('this layer has no alpha')
           return
   # if image is indexed mode (gif) and has alpha channel
   if img.base_type == 2:
       img_mode = 'Indexed'
       if img.active_layer.bpp != 2:
           print('this layer has no alpha')
           return
   layer = src_layer.copy()
   tile_width = gimp.tile_width()
   tile_height = gimp.tile_height()
   pdb.gimp_context_set_antialias(0)
   pdb.gimp_context_set_feather(0)
   pdb.gimp_context_set_sample_merged(0)
   pdb.gimp_context_set_sample_criterion(0)
   pdb.gimp_context_set_sample_threshold(1)
   pdb.gimp_context_set_sample_transparent(0)
   img.add_layer(layer)
   counter_good = 1
   counter_bad = 1
   extra_wdth = tile_width - (layer.width % tile_width)
   extra_hght = tile_height - (layer.height % tile_height)
   layer.resize( layer.width + extra_wdth, layer.height + extra_hght, 0, 0)
   tile_rows = int(layer.width / tile_width)
   tile_cols = int(layer.height / tile_height)
   temp_layer = layer.copy()
   for tile_row in range(tile_rows):
       for tile_col in range(tile_cols):
           #print(tile_row, tile_col)
           #srcTile = layer.get_tile(False, tile_row, tile_col)
           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
           if srcTile != None:
               for tile_y in range(0, srcTile.eheight, sample):
                   for tile_x in range(0, srcTile.ewidth, sample):
                       pixel = srcTile[tile_x, tile_y]
                       #RGBA image
                       if img_mode == 'RGB':
                           R,G,B,A = srcTile[tile_x, tile_y]
                       #indexed image like gif
                       if img_mode == 'Greyscale' or img_mode == 'Indexed':
                           I,A = srcTile[tile_x, tile_y]
                       # if pixel is not completely transparent
                       if ord( A) > 0:
                           layer_pixel_pos_x = tile_col * tile_height + tile_x
                           layer_pixel_pos_y = tile_row * tile_width + tile_y
                           pdb.gimp_image_select_contiguous_color(img, 2, layer, layer_pixel_pos_x, layer_pixel_pos_y)
                           newlayer = layer.copy()
                           x1, y1, x2, y2 = layer.mask_bounds
                           if x2 - x1 < min_size and y2 - y1 < min_size:
                               # "bad objects", objects that er too small
                               if counter_bad == 1 and (img_mode == 'RGB' or img_mode == 'Greyscale'):
                                   layer_group_bad = pdb.gimp_layer_group_new(img)
                                   layer_group_bad.name = 'small objects'
                                   img.add_layer(layer_group_bad)
                               newlayer.name = "trash %03d (%d, %d, %d, %d)" % (counter_bad, x1, y1, x2 - x1, y2 - y1)
                               counter_bad += 1
                               if img_mode == 'RGB' or img_mode == 'Greyscale':
                                   img.active_layer = layer_group_bad
                           else:
                               # "good objects"
                               if counter_good == 1 and (img_mode == 'RGB' or img_mode == 'Greyscale'):
                                   layer_group_good = pdb.gimp_layer_group_new(img)
                                   layer_group_good.name = 'objects'
                                   img.add_layer(layer_group_good)
                               newlayer.name = "object %03d (%d, %d, %d, %d)" % (counter_good, x1, y1, x2 - x1, y2 - y1)
                               counter_good += 1
                               if img_mode == 'RGB' or img_mode == 'Greyscale':
                                   img.active_layer = layer_group_good
                           img.add_layer(newlayer)
                           newlayer.resize(x2 - x1, y2 - y1, -x1, -y1)
                           img.active_layer = layer
                           pdb.gimp_edit_clear(layer)
                           #update tile by updating temp_layer
                           temp_layer = layer.copy()
                           srcTile = temp_layer.get_tile(False, tile_row, tile_col)
   #img.remove_layer(layer)
   time_end = time.time()
   print ('time taken: ' + str(time_end - time_start) + ' seconds.')

img = gimp.image_list()[0]
objects2layers(img)
Now I have a strange bug, the script only works on somewhat square images. For example 640 x 320 image objects on right side won't be picked and on 640 x 320 objects on the bottom won't be picked. If I resize the images to 640 x 640 and "Layer to image size" all objects get picked. What  am I doing wrong?

Edit: finally got everything to work, no need to create extra width, I just made mistake and switched layer.width with layer.height
Code:


Code:
import time

def objects2layers(img, sample = 1, min_size = 10):
    time_start = time.time()
    src_layer = img.active_layer
    # if image is rgb mode and has alpha channel
    if img.base_type == 0:
        img_mode = 'RGB'
        if img.active_layer.bpp != 4:
            print('this layer has no alpha')
            return
    # if image is greyscale or indexed mode (gif) and has alpha channel
    if img.base_type == 1:
        img_mode = 'Greyscale'
        if img.active_layer.bpp != 2:
            print('this layer has no alpha')
            return
    # if image is indexed mode (gif) and has alpha channel
    if img.base_type == 2:
        img_mode = 'Indexed'
        if img.active_layer.bpp != 2:
            print('this layer has no alpha')
            return
    layer = src_layer.copy()
    tile_width = gimp.tile_width()
    tile_height = gimp.tile_height()
    pdb.gimp_context_set_antialias(0)
    pdb.gimp_context_set_feather(0)
    pdb.gimp_context_set_sample_merged(0)
    pdb.gimp_context_set_sample_criterion(0)
    pdb.gimp_context_set_sample_threshold(1)
    pdb.gimp_context_set_sample_transparent(0)
    img.add_layer(layer)
    counter_good = 1
    counter_bad = 1
    tile_rows = int( (layer.height + tile_height - 1) / tile_height)
    tile_cols = int( (layer.width  + tile_width - 1) / tile_width)
    temp_layer = layer.copy()
    for tile_row in range(tile_rows):
        for tile_col in range(tile_cols):
            #print(tile_row, tile_col)
            #srcTile = layer.get_tile(False, tile_row, tile_col)
            srcTile = temp_layer.get_tile(False, tile_row, tile_col)
            #if srcTile != None:
            for tile_y in range(0, srcTile.eheight, sample):
                for tile_x in range(0, srcTile.ewidth, sample):
                    pixel = srcTile[tile_x, tile_y]
                    #RGBA image
                    if img_mode == 'RGB':
                        R,G,B,A = srcTile[tile_x, tile_y]
                    #indexed image like gif
                    if img_mode == 'Greyscale' or img_mode == 'Indexed':
                        I,A = srcTile[tile_x, tile_y]
                    # if pixel is not completely transparent
                    if ord( A) > 0:
                        layer_pixel_pos_x = tile_col * tile_height + tile_x
                        layer_pixel_pos_y = tile_row * tile_width + tile_y
                        pdb.gimp_image_select_contiguous_color(img, 2, layer, layer_pixel_pos_x, layer_pixel_pos_y)
                        newlayer = layer.copy()
                        x1, y1, x2, y2 = layer.mask_bounds
                        if x2 - x1 < min_size and y2 - y1 < min_size:
                            # "bad objects", objects that er too small
                            if counter_bad == 1 and (img_mode == 'RGB' or img_mode == 'Greyscale'):
                                layer_group_bad = pdb.gimp_layer_group_new(img)
                                layer_group_bad.name = 'small objects'
                                img.add_layer(layer_group_bad)
                            newlayer.name = "trash %03d (%d, %d, %d, %d)" % (counter_bad, x1, y1, x2 - x1, y2 - y1)
                            counter_bad += 1
                            if img_mode == 'RGB' or img_mode == 'Greyscale':
                                img.active_layer = layer_group_bad
                        else:
                            # "good objects"
                            if counter_good == 1 and (img_mode == 'RGB' or img_mode == 'Greyscale'):
                                layer_group_good = pdb.gimp_layer_group_new(img)
                                layer_group_good.name = 'objects'
                                img.add_layer(layer_group_good)
                            newlayer.name = "object %03d (%d, %d, %d, %d)" % (counter_good, x1, y1, x2 - x1, y2 - y1)
                            counter_good += 1
                            if img_mode == 'RGB' or img_mode == 'Greyscale':
                                img.active_layer = layer_group_good
                        img.add_layer(newlayer)
                        newlayer.resize(x2 - x1, y2 - y1, -x1, -y1)
                        img.active_layer = layer
                        pdb.gimp_edit_clear(layer)
                        #update tile by updating temp_layer
                        temp_layer = layer.copy()
                        srcTile = temp_layer.get_tile(False, tile_row, tile_col)
    img.remove_layer(layer)
    del(temp_layer)
    time_end = time.time()
    print ('time taken: ' + str(time_end - time_start) + ' seconds.')

img = gimp.image_list()[0]
objects2layers(img)



RE: Searching for python script islands to layers - Ofnuts - 01-26-2018

(01-26-2018, 09:14 PM)mich_lloid Wrote: Now I have a strange bug, the script only works on somewhat square images. For example 640 x 320 image objects on right side won't be picked and on 640 x 320 objects on the bottom won't be picked. If I resize the images to 640 x 640 and "Layer to image size" all objects get picked. What  am I doing wrong?

I don't know if this is the problem, but this specifically makes you script very hard to read (professionally, I would require you to fix this even if there were no bugs):

Code:
   tile_rows = int(layer.width / tile_width)
   tile_cols = int(layer.height / tile_height)

In other words, you are naming "rows" what is really "columns" and vice-versa. I assume this confuses you later in the code. Of course, on a square image you have the same number of either so the problem doesn't affect you.


RE: Searching for python script islands to layers - mich_lloid - 01-29-2018

My first plugin ever, I copy-pasted everything from ofnut's "ofn-extract-objects.py" and made a few changes, like content of the function, parameters and registrations, like they say "Imitation is the sincerest form of flattery".
"mich-extract-objects.py" Big Grin :

Code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
from gimpfu import *

def objects2layers(image, layer, sample, minimal_dimensions) :
   '''
   Separates objects on one layer to their own layer. Handy for Spritesheets
   
   Parameters:
   image              -- The current image.
   layer              -- The layer of the image that is selected.
   sample             -- sample every nth pixel of layer tile
   minimal_dimensions -- minimal dimension of object to be placed in "objects" layer group
   '''
   # Start timer
   time_start = time.time()

   # Check image type (RGB/Greyscale or Indexed) and if layer has alpha, if not stop this script with a warning
   no_alpha_warning='This layer has no alpha, please add an alpha channel for this script to work'
   if image.base_type == 0:
       image_mode = 'RGB'
       if layer.bpp != 4:
           gimp.message(no_alpha_warning)
           return
   if image.base_type == 1:
       image_mode = 'Greyscale'
       if layer.bpp != 2:
           gimp.message(no_alpha_warning)
           return
   if image.base_type == 2:
       image_mode = 'Indexed'
       if layer.bpp != 2:
           gimp.message(no_alpha_warning)
           return

   tile_width = gimp.tile_width()
   tile_height = gimp.tile_height()
   layer_copy = layer.copy()
   image.add_layer(layer_copy)

   # Context setters for pdb.gimp_image_select_contiguous_color()
   pdb.gimp_context_set_antialias(0)
   pdb.gimp_context_set_feather(0)
   pdb.gimp_context_set_sample_merged(0)
   pdb.gimp_context_set_sample_criterion(0)
   pdb.gimp_context_set_sample_threshold(1)
   pdb.gimp_context_set_sample_transparent(0)

   # Counters for objects in layer groups
   counter_good = 1
   counter_bad = 1

   # Tile rows and columns of layer_copy
   tile_rows = int( (layer_copy.height + tile_height - 1) / tile_height)
   tile_cols = int( (layer_copy.width  + tile_width - 1) / tile_width)

   # create an 'off-screen' copy of layer_copy (not added to the canvas)
   layer_offscreen = layer_copy.copy()

   # Loop over tiles of layer_offscreen
   for tile_row in range(tile_rows):
       for tile_col in range(tile_cols):
           Tile = layer_offscreen.get_tile(False, tile_row, tile_col)

           # Loop over pixels of tiles
           for tile_y in range(0, Tile.eheight, sample):
               for tile_x in range(0, Tile.ewidth, sample):
                   pixel = Tile[tile_x, tile_y]

                   # Split components of imag_mode to get the alpha
                   # RGBA image
                   if image_mode == 'RGB':
                       R,G,B,A = Tile[tile_x, tile_y]
                   # Greyscale or Indexed image
                   if image_mode == 'Greyscale' or image_mode == 'Indexed':
                       I,A = Tile[tile_x, tile_y]

                   # If pixel is not completely transparent select it and neighbouring non-transparent pixels
                   if ord( A ) > 0:
                       layer_pixel_pos_x = tile_col * tile_height + tile_x
                       layer_pixel_pos_y = tile_row * tile_width + tile_y
                       pdb.gimp_image_select_contiguous_color(image, 2, layer_copy, layer_pixel_pos_x, layer_pixel_pos_y)

                       # Create a layer for an object and assign it to 'small objects' layer group or
                       # 'objects layer group' based on criteria "min_dimensions"
                       object_layer = layer_copy.copy()
                       x1, y1, x2, y2 = layer_copy.mask_bounds

                       # 'small objects' layer group
                       if x2 - x1 < minimal_dimensions and y2 - y1 < minimal_dimensions:
                           if counter_bad == 1 and (image_mode == 'RGB' or image_mode == 'Greyscale'):
                               layer_group_bad = pdb.gimp_layer_group_new(image)
                               layer_group_bad.name = 'small objects'
                               image.add_layer(layer_group_bad)
                           object_layer.name = "trash {:4d} ({:d}, {:d}, {:d}, {:d})".format(counter_bad, x1, y1, x2 - x1, y2 - y1)
                           counter_bad += 1
                           if image_mode == 'RGB' or image_mode == 'Greyscale':
                               image.active_layer = layer_group_bad

                       # 'objects' layer group
                       else:
                           if counter_good == 1 and (image_mode == 'RGB' or image_mode == 'Greyscale'):
                               layer_group_good = pdb.gimp_layer_group_new(image)
                               layer_group_good.name = 'objects'
                               image.add_layer(layer_group_good)
                           object_layer.name = "object {:4d} ({:d}, {:d}, {:d}, {:d})".format(counter_good, x1, y1, x2 - x1, y2 - y1)
                           counter_good += 1
                           if image_mode == 'RGB' or image_mode == 'Greyscale':
                               image.active_layer = layer_group_good

                       # Add object to active layer which is one of the two layer groups and "Auto crop" object layer
                       image.add_layer(object_layer)
                       object_layer.resize(x2 - x1, y2 - y1, -x1, -y1)

                       # Remove/erase selection from layer_copy
                       image.active_layer = layer_copy
                       pdb.gimp_edit_clear(layer_copy)

                       # Update tile, by updating layer_offscreen, by copying layer_copy
                       layer_offscreen = layer_copy.copy()
                       Tile = layer_offscreen.get_tile(False, tile_row, tile_col)

   # Remove layer_copy and layer_offscreen from canvas and memory
   image.remove_layer(layer_copy)
   del(layer_offscreen)

   # Reset context settings to their default values.
   pdb.gimp_context_set_defaults()

   # End timer and display timer seconds up to milliseconds in error console
   time_end = time.time()
   gimp.message('INFO: time taken: {:.3f} seconds'.format(time_end - time_start))


### Parameters
#imageParm  = (PF_IMAGE,    "image",              'Input image'           , None)
#layerParm  = (PF_DRAWABLE, 'layer',              'Input layer'           , None)
sampleParm = (PF_INT,      'sample',             'Sample every nth pixel', 1)
mindimParm = (PF_INT,      'minimal_dimensions', 'Minimal dimension'     , 10)

### Registrations
name       = "mich-objects-2-layers"
blurb      = "Objects to layers"
help       = "Separates objects on one layer to their own layer. Handy for Spritesheets"
author     = "Mich"
copyright  = "Open source (BSD 3-clause license)"
date       = "2018-01-28"
imagetypes = "*"
#params     = [imageParm, layerParm, sampleParm, mindimParm]
params     = [sampleParm, mindimParm]
results    = []
menupath   = "<Image>/Filters/Mich's Gimp-Fu's/objects2layers..."
function   = objects2layers

### Registrations
register(
   name       ,
   blurb      ,
   help       ,
   author     ,
   copyright  ,
   date       ,
   menupath   ,
   imagetypes ,
   params     ,
   results    ,
   function
)

main()

To run this plugin Filters > Mich's Gimp-Fu's > objects2layers...

Question:
1) In "Parameters" I had to comment out line 138,139 and 151 because I got error that I had 6 parameters in stead of 4?