Posts: 6,338
Threads: 272
Joined: Oct 2016
Reputation:
562
Operating system(s):
Gimp version: 2.10
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.
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-24-2018, 10:54 PM
(This post was last modified: 01-24-2018, 10:58 PM by mich_lloid.)
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
Posts: 6,338
Threads: 272
Joined: Oct 2016
Reputation:
562
Operating system(s):
Gimp version: 2.10
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)
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-25-2018, 12:17 AM
(This post was last modified: 01-25-2018, 12:30 AM by mich_lloid.)
(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!
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-25-2018, 10:41 PM
(This post was last modified: 01-25-2018, 10:45 PM by mich_lloid.)
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.
Posts: 6,338
Threads: 272
Joined: Oct 2016
Reputation:
562
Operating system(s):
Gimp version: 2.10
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
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.
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-26-2018, 04:10 PM
(This post was last modified: 01-26-2018, 04:52 PM by mich_lloid.)
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...
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-26-2018, 09:14 PM
(This post was last modified: 01-26-2018, 11:23 PM by mich_lloid.)
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)
Posts: 6,338
Threads: 272
Joined: Oct 2016
Reputation:
562
Operating system(s):
Gimp version: 2.10
(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.
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
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" :
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?
|