User:KokoroSenshi/GIMP

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search

Some useful GIMP scripts I wrote! Accepting requests. Messy comments, needs organizing: "educational but unorganized over-commenting".

Does putting CC BY-SA for license section make sense?

Save layers as files

http://registry.gimp.org/node/28268

(Auto insert ZW Blue background, duplicate layer)

KScripts-bg-removal-setup

This is the Python GIMP plugin to duplicate an image, add alpha channel, add ZW blue background (took a lot of trial an error, and is messy since I basically tried to log every issue and resource as I came across it) - For background removal.

#!/usr/bin/env python
#http://registry.gimp.org/node/28124
	#pdb.gimp_invert(layer) refers to gimp-invert in the procedure list
	#so... everything I need is there, except have "-" instead of "_" and no "pdb." is that why (not exactly \/)
#img : image => img is of type 'image'? Or does it mean img=image=current? 
	# type help(gimpfu) after import gimpfu etc. for info (not useful tho?)
	# As a menu item, the img and layer default to current (?)
	#item can refer to not just layer/image/etc. ?
	#gimp.asdf refers to the gimp module that comes with gimpfu; use dir(gimp) to see, e.g. Layer (note capital L)
#pdb doesn't work for new layer stuff..... so use gimp.Layer stuff...(?)
	#http://myvirtualbrain.blogspot.com.au/2012/06/gimp-python-fu-very-basics.html
	#Makes sense now (tho isn't relev?): http://stackoverflow.com/questions/12608210/gimp-2-8-plugin-script-how-do-you-add-a-new-grouplayer
#omfg, from ##put None, not 0! The pdb listing info lied? Refer pdb.gimp_image_insert_layer at http://stackoverflow.com/questions/13882808/gimp-python-fu-nested-group-layers
	#hmm http://developer.gimp.org/api/2.0/libgimp/libgimp-gimplayer.html#gimp-layer-new
#Tried to make a new layer, but is it easier to just copy then clear? (refer bundle of scripts)
# ok, I give up on gimp_layer_new; my attempts makes it break, even.
	#sometimes comments will have stuff that messes things up /somehow/ ........ so keep versions I suggest
# Copy then clear, as in test-discolour-layer-v4.py
	#example code for img. and gimp. ? http://gimpforums.com/thread-gimp-script-help
	#Has gimp module info, e.g. img.add_layer #http://www.gimp.org/docs/python/index.html
	#^ under Plugin Framework, " the first parameter to the plugin function should be the image, and the second should be the current drawable"
	#^ i.e. first two params are automatically the current image then drawable (e.g. image or a pasted-but-not-floated thing?)
#Color are in ( , , ) form, as it now doesn't fail to load+does the color change; deduced from: http://stackoverflow.com/questions/22622370/gimp-script-fu-how-can-i-set-a-value-in-the-colormap-directly
#Btw, you know it failed to load since menu item isn't there.
#btw if explicit need img etc: 
	# >>> image = gimp.image_list()[0]
	# >>> layer = image.layers[0]
	# >>> gimp.pdb.python_fu_bg_removal_setup(image, layer) #uses the name from the "register"
#According to error when tried to do add_layer on bg_layer twice , 'gimp-image-insert-layer' is the same as img.Layer?
#IMPORTANT### Turns out using spaces instead of [tab] caused most of the issues earlier??????? (those that led me to use the non pdb. ones?
from gimpfu import *

def bg_removal_setup(img, layer) :
	''' Does the usual stuff when doing Zelda Wiki background removal
	
	Parameters:
	img   : image  The current image.
	layer : layer  The layer of the image that is selected.
	'''
	#Useful? pdb.gimp_image_raise_item_to_top(image, item)
	layer_copy = pdb.gimp_layer_copy(layer, TRUE)       #makes the alpha channeled version
	pdb.gimp_item_set_visible(layer, FALSE)             #hide original ##works!
	pdb.gimp_image_insert_layer(img, layer_copy, None, 1)  #inserts the alpha chanelled ver to position 1 ##put None, not 0! The pdb listing info lied?
	#this causes problems? Was instead due to spaces vs [tab]s?#bg_layer = pdb.gimp_layer_new(img, 5, 5, 0, "bgZW", 100, 0) #makes new layer bg_layer ##did I forget that this only makes, not insert?
	foreground = pdb.gimp_context_get_foreground()
	pdb.gimp_context_set_foreground((23,69,110))
	bg_layer = gimp.Layer(img, "bgZW", layer.width, layer.height, layer.type, layer.opacity, layer.mode) #creates with foreground?
	img.add_layer(bg_layer, 2)	#creates with black (NOT foreground)(?)#inserts the bg layer to position 2
	#no need now since ^ #pdb.gimp_image_insert_layer(img, bg_layer, None, 2)  #inserts the bg layer to position 2
	added_bg_layer = pdb.gimp_image_get_layer_by_name(img, "bgZW")
	#issue was due to [space]s vs [tab]? #img.add_layer(added_bg_layer, 2) #<-to test if the get_layer_.. works
	pdb.gimp_drawable_fill(added_bg_layer, 0)
	alpha_layer = pdb.gimp_image_get_layer_by_name(img, layer.name + " copy")
	pdb.gimp_image_set_active_layer(img, alpha_layer)



register(
	"python_fu_bg_removal_setup",					# name
	"Background removal setup",						# blurb
	"Does the usual stuff for bg removal",			# help
	"KokoroSenshi",									# author
	"KokoroSenshi - CC BY-SA",						# copyright
	"2016",											# date
	"<Image>/Filters/KScripts/BG Removal Setup",	# menupath
	"*",											# imagetypes
	[],												# params
	[],												# results
	bg_removal_setup)								# function

main()												# Don't forget this!

Extract sprites from spritesheets (most?, not fully optimized yet)

KScripts-Spritesheet-extract

Don't forget to select the background/boundary color first!

#!/usr/bin/env python

#gimp_selection_border is useful idea... not always though, e.g. if box inner edge isn't consistent for a selection
#hmm...https://duckduckgo.com/?q=gimp+python+loop+over+all+pixels
##http://shallowsky.com/blog/gimp/pygimp-pixel-ops.html
##idea: exclude rows of identical pixels to save time?
#^pixel level (pixel region) operations = slow!!!?
# e.g. prev link and just below both comment on tiles or storing into arrays as faster
# http://registry.gimp.org/node/28123
#https://wiki.python.org/moin/WhileLoop

#Identify the foreground color and convert to the string needed for comparing with each pixel (instead of converting either the fb color to str or pixel color to RGBAlpha in the loop, and wasteful computation)

#Idea: identify ALL CORNERS!!! or rather, identify top left, then strictly right and down to find next corners
# Note that need 4 pixels, three of which are the bg color; the sprite pixel could be any color/alpha  
#Human: First sample the bg color and make foreground (fg) 
#Next, scan rightward, then downward all pixels until fg then not, then check above pixels for fg-fg (note that scanning for fg-fg then checking below pixels will get too many useless hits I think)
#Then scan down until fg again then right until fg again, then construct selection
#(alternative is select fg then check pixels selected/not)

#Identify the foreground color and convert to the string needed for comparing with each pixel (instead of converting either the fb color to str or pixel color to RGBAlpha in the loop, and wasteful computation)

#the R,G,B,Alpha is the ord(pixel[n]) for n=0,1,2,3 respectively

#####Testing lines:
#layer = gimp.image_list()[0].layers[0]
#tilee = layer.get_tile(False,0,0)
#tilee[0,0] = '3a\xa8\xff'
#layer.flush() #Color picker will work right
#layer.merge_shadow(True) #?
#layer.update(0, 0, layer.width, layer.height) #Looks right when viewed
#ord(tilee[0,0][0])
#str(ord(tilee[0,0][0]))
#####

#http://stackoverflow.com/questions/730764/try-except-in-python-how-do-you-properly-ignore-exceptions#
#https://wiki.python.org/moin/HandlingExceptions

#try: #Because it's nice to catch when something unexpected happens and respond
#Determine grid of tiles #if 20, range(...) is 0 to 19? #need int to do range()
#layer.get_tile(False, yt, xt) #False for 'off the shadow buffer', whatever that is
#current_pixel = current_tile[x,y] #Yes, (x,y), I believe (location x units right and y units down of top-left corner of the square/pixel?)#read: Tile Mapping Behaviour at http://www.gimp.org/docs/python/index.html

#(i.e. current tile is layer.get_tile(False, yt, xt)[x,y])
#(location is by tile then pixel - location is tile_num * 64 + pixel_num)

#TODO: clean out educational notes and tidy comments

#Useful instead of restarting GIMP: 
#>>> import pdb
#>>> reload(pdb)
#but... breaks use of pdb.___ !?!?
#OR: if run through console (at least), no need to refresh

#>>> pdb.python_fu_spritesheet_extract(gimp.image_list()[0], gimp.image_list()[0].layers[0])

#First run: caught all 554 sprites, (counted and summed in Excel myself)
#However, some sprites are cut-off on the right side (x-dir)! Larger ones are fine, so not size related? Only the right edge ones are messed up? Even when I swap it to scan tiles row by row. Even when I replace all else by boundary color - it must be something about the position and my code... Not boundary color related.
#If I leave only the info box the Sprite Ripper put, then it encounters an exception in fact... when with the whole lot it's fine...
##oh, I see... it doesn't manage to calculate sprite_width (the error is referencing sprite_width before assignment)
##Still happens when I transpose the spritesheet, i.e. the issue would occurs in both x and y directions
##confirmed that foundx == False on error, using gimp.message on exception to display it
##AH... maybe it isn't checking the last column of pixels! off-by-one?
##(Image/configure grid to set to 64x64, noticed that none of the pixels along edge aren't checked when calculating sprite width/height? - though the code looks right... tried to increase num_w_tiles by one and it seems to work)
##^Found the issue - integer division (in Python 2.x, though not 3.0) gives integer instead of float 
#AT FIRST decided to float the TILE_..... constants but that causes issues... so I'll only float only for that calculation

from gimpfu import *
from math import *

def spritesheet_extract(img, layer) :
	''' Based on assumption of rectangle sprite regions + boundary color borders top-left and bottom-right of sprites + boundary color is foreground color http://www.spriters-resource.com/fullview/68256/
	
	Parameters:
	img   : image  The current image.
	layer : layer  The layer of the image that is selected.
	'''
	
	# Initializes the progress bar
	gimp.progress_init("Starting spritesheet extraction on " + layer.name + "...")
	
	# Makes an undo group, so undo will undo the whole operation (saves undo-recording effort?)
	img.undo_group_start()
	
	#TEMP: Give layer alpha #Ignore non-alpha channels
	layer.add_alpha()
	
	#Convert foreground color to string form - to compare with each pixel
	fg_tuple = gimp.get_foreground()
	fg_string = chr(fg_tuple[0]) + chr(fg_tuple[1]) + chr(fg_tuple[2]) + chr(fg_tuple[3]) #e.g. (50,97,168,255) is '2'+'a'+'\xa8'+'\xff'
	
	#store tile dimensions
	TILE_WIDTH = gimp.tile_width()
	TILE_HEIGHT = gimp.tile_height()
	
	#The main program: to identify top-left corners (of sprites), the dimensions of such sprites, and create a selection to float to new layer
	try:
		#Determine dimensions of tile grid
		num_w_tiles = int(ceil(layer.width / float(TILE_WIDTH)))
		num_h_tiles = int(ceil(layer.height / float(TILE_HEIGHT)))
		
		#Iterate over tiles (starts top-left at (0,0), counting right & down?)
		for yt in range(num_h_tiles):
			for xt in range(num_w_tiles):
				#Update progress bar at each tile
				gimp.progress_update(float(yt*num_w_tiles + xt) / float(num_w_tiles*num_h_tiles))
				
				#Store current tile
				current_tile = layer.get_tile(False, yt, xt)
				
				#Iterate over the tile's pixels (height/width starting from 0; matches tile's 2-tuple array of pixels)
				for y in range(current_tile.eheight):
					for x in range(current_tile.ewidth):
						
						#First, locate a top-left corner (4 pixels)
						current_pixel = current_tile[x,y] #From (0,0), counting right/down (x/y)
						
						#If sprite pixel (not fg), see if top left 3 pixels are fg; if x or y is 0, then need to move to preceding tile(s); (don't overthink efficiency yet)
						if (current_pixel != fg_string) : #If not bg color (i.e. is sprite pixel)
							
							is_corner = False
							 #(btw left tile = inner tile => (TILE_######-1) will always be right)
							if x == 0 : # X|E
								if fg_string == layer.get_tile(False, yt, xt-1)[TILE_WIDTH-1,y] : #check l
									if y == 0 : 
										# -+- 
										# D|E
										if fg_string == layer.get_tile(False, yt-1, xt)[x,TILE_HEIGHT-1] == layer.get_tile(False, yt-1, xt-1)[TILE_WIDTH-1,TILE_HEIGHT-1] : #check t then tl
											is_corner = True
									else : #if y != 0:
										# X|X
										# D|E
										if fg_string == current_tile[x,y-1] == layer.get_tile(False, yt, xt-1)[TILE_WIDTH-1,y-1] : #check t then tl
											is_corner = True
							else: #if x != 0: # |XE
								if fg_string == current_tile[x-1,y] : #check l
									if y == 0 :
										# +--
										# |DE
										if fg_string == layer.get_tile(False, yt-1, xt)[x,TILE_HEIGHT-1] == layer.get_tile(False, yt-1, xt)[x-1,TILE_HEIGHT-1] : #check t then tl
											is_corner = True
									else : #if y != 0:
										# XX
										# DE
										if fg_string == current_tile[x,y-1] == current_tile[x-1,y-1] : #check t then tl
											is_corner = True
							
							if (is_corner) : #If the current pixel is a sprite's corner pixel
								
								#Find sprite edges by checking rightward and downward
								#( range(1,3) = [1,2]		range(3) = [0,1,2] )
								#Iterate over tiles and pixels in x-dir
								foundx = False
								for pixx in range(x+1,current_tile.ewidth): #if x is last pixel, then get [] i.e. do nothing!?
									if current_tile[pixx,y] == fg_string : 
										sprite_width = (pixx - x) #e.g. if x==62 (!fg), pixx==63 is fg: sprite_width is 1 pixel
										foundx = True
										break
								if foundx == False: 
									for tilex in range(xt+1,num_w_tiles):
										this_tile = layer.get_tile(False, yt, tilex)
										for pixx in range(this_tile.ewidth):
											if this_tile[pixx,y] == fg_string : 
												sprite_width = (TILE_WIDTH-x) + (TILE_WIDTH)*(tilex - 1 - xt) + (pixx)
												#^e.g. if TILE_WIDTH=3,xt=1,x=1 (!fg), tilex=3,pixx=2 if fg: 
												#sprite_width is 3-1+3*(3-1-1)+2=7: OOO OXX XXX XXO (pixx is the first NON-sprite pixel)
												foundx = True
												break
										if foundx: break
								
								
								#Iterate over tiles and pixels in y-dir
								foundy = False
								for pixy in range(y+1,current_tile.eheight): #if y is last pixel, then get [] i.e. do nothing!?
									if current_tile[x,pixy] == fg_string : 
										sprite_height = (pixy - y) #e.g. if y==62 (!fg), pixy==63 is fg: sprite_width is 1 pixel
										foundy = True
										break
								if foundy == False: 
									for tiley in range(yt+1,num_h_tiles):
										this_tile = layer.get_tile(False, tiley, xt)
										for pixy in range(this_tile.eheight):
											if this_tile[x,pixy] == fg_string : 
												sprite_height = (TILE_HEIGHT-y) + (TILE_HEIGHT)*(tiley - 1 - yt) + (pixy)
												#^e.g. if TILE_HEIGHT=3,yt=0,y=1 (!fg), tiley=2,pixy=2 if fg: 
												#sprite_width is 3-1+3*(2-1-0)+2=7: OXX XXX XXO (pixy is the first NON-sprite pixel)
												foundy = True
												break
										if foundy: break
								
								#(continuing operations on the spite of the pixel that is its corner:)
								#Create selection based on this pixel location and sprite_width and sprite_height found
								pdb.gimp_image_select_rectangle(img, 2, TILE_WIDTH*xt+x, TILE_HEIGHT*yt+y, sprite_width, sprite_height)
								#Float it then to new layer
								pdb.gimp_floating_sel_to_layer(pdb.gimp_selection_float(layer,0,0))
		
	except Exception as error_code: #except = on 'try' failing, if error type is Exception, store the error info(?) in variable err (as a number?)
		gimp.message( "Unexpected error: " + str(error_code) )
		#gimp.message("Corner: x pos = "+str(TILE_WIDTH*xt+x)+", y pos = "+str(TILE_HEIGHT*yt+y))
		#gimp.message("Last checked: x pos = "+str(TILE_WIDTH*xt+x)+", y pos = "+str(TILE_HEIGHT*yt+y))
		#gimp.message( "Did you remember to choose the boundary color?")
	
	
	
	#Then proceed again from the top-right identified corner and loop.
	
	#ENDNOTE: identify all sprites first then split off, or same time? same time I'll try first at least - as for to-image-ing, separate, in case of bug/issues
	#Next is to convert layers to new images
	
	#Close the undo group so undo-recording is back to normal
	img.undo_group_end()
	
	#End/finish/remove the progress bar
	pdb.gimp_progress_end()
	
	
	
register(
	"python_fu_spritesheet_extract",					# name
	"Spritesheet extractor",							# blurb
	"Extracts sprites from certain spritesheets",		# help
	"KokoroSenshi",										# author
	"KokoroSenshi - CC BY-SA",							# copyright
	"2016",												# date
	"<Image>/Scripts/KScripts/Spritesheet extractor",	# menupath
	"*",												# imagetypes
	[],													# params
	[],													# results
	spritesheet_extract)								# function - Don't forget this!!

main()													# Don't forget this!

Remove an image's background using two versions, white and black backgrounds

KScripts-2-layer-BW-bg-removal
#!/usr/bin/env python

#http://www.gimp.org/docs/
#https://duckduckgo.com/?q=gimp+object+documentation&t=ffsb
#https://www.gimp.org/docs/python/index.html
#http://www.jamesh.id.au/software/pygimp/
#http://www.jamesh.id.au/software/pygimp/gimp-objects.html
#http://www.jamesh.id.au/software/pygimp/support-modules.html#GIMPSHELF-MODULE
#https://duckduckgo.com/?q=up+to+date+gimp+python+fu+docuentation&t=ffsb
	#http://registry.gimp.org/node/24275
#http://wiki.gimp.org/wiki/Hacking:Plugins#Debugging_GIMP_Plug-ins
#http://myvirtualbrain.blogspot.com.au/2012/06/gimp-python-fu-very-basics.html?view=classic

# Automates the following based on bottom two layers https://graphicdesign.stackexchange.com/questions/8057/using-gimp-can-you-erase-a-color
# >>> image = gimp.image_list()[0]
# >>> layer = image.layers[0]
# >>> gimp.pdb.python_fu_2_layer_BW_bg_removal(image, layer)

# http://www.jamesh.id.au/software/pygimp/gimp-module-procedures.html
# len() gives the length
# help(gimp) is useful, eg, help(gimp.Image.merge_down)
#convert hyphens (that the pdb info may say) to underscores!
#(still haven't confirmed that the function args are the active image and layer respectively...)
#Current docs are outdated! some Layer functions used on/with an Image have been replaced by functions on/with a Layer itself! help(gimp.Layer) is useful!
#Debugging! If it shows in the menu, the structure is right,
#then if it fails to do what you want, then open gimp's python console to see where it stops (put print(asdf) statements to identify is one thing you could do)

from gimpfu import *

def remove_bg_BW(img, layer) :
	''' Does the background removal explained at  https://graphicdesign.stackexchange.com/questions/8057/using-gimp-can-you-erase-a-color
	
	Parameters:
	img   : image  The current image.
	layer : layer  The layer of the image that is selected.
	'''
	layers_array = img.layers #note that the var will therefore not change (or need to be recalculated y/n?)
	num_layers = len(img.layers)
	#Gives array of ids, which is useless(?):# num_layers, layers_array = pdb.gimp_image_get_layers(img)
	#(white above black; bottom two)
	###Add alpha to bottom two layers just in case:
	#Save + alpha-ize upper layer
	white_bg = layers_array[num_layers-2]
	pdb.gimp_layer_add_alpha(white_bg)
	#Save + alpha-ize lower layer
	black_bg = layers_array[num_layers-1]
	pdb.gimp_layer_add_alpha(black_bg)
	
	#Copy the 'black layer' for later:
	black_copy = black_bg.copy(True)
	##OR: #black_copy = pdb.gimp_layer_copy(black_bg, True)
	
	#Set 'white layer' to "difference" mode
	white_bg.mode = DIFFERENCE_MODE #<- a constant that is 6
	##OR: #pdb.gimp_layer_set_mode(white_bg, 6)
	#Merge down onto the black layer to get the desired image's alpha channel:
	alpha_channel_result = img.merge_down(white_bg, EXPAND_AS_NECESSARY)
	##OR?: #alpha_channel_result = pdb.gimp_image_merge_down(img, white_bg, EXPAND_AS_NECESSARY)
	##Eventally found image.merge_down(layer,type) using help(gimp.Image) and trying invalid arguments and reading the errors
	#Invert the result:
	pdb.gimp_invert(alpha_channel_result)
	
	#extra copy to paste into layer mask later
	alpha_channel_copy = alpha_channel_result.copy(True)
	##OR: #alpha_channel_copy = pdb.gimp_layer_copy(alpha_channel_result, True)
	
	#(alpha channel layer; bottom layer)
	#Set the alpha channel result to "divide" mode
	alpha_channel_result.mode = DIVIDE_MODE
	##OR: #pdb.gimp_layer_set_mode(alpha_channel_result, 15)
	#Merge it onto the black layer after pasting it:
	#(There should be one less layer than started with, but just in case of code changes/reuse...:)
	img.add_layer(black_copy, len(img.layers))#num_layers)
	##OR?: #pdb.gimp_image_insert_layer(img, black_copy, None, num_layers) #Use None, not 0 !?!?!?
	#(alpha channel layer > black layer; bottom 2 layers)
	alpha_on_black_result = img.merge_down(alpha_channel_result, EXPAND_AS_NECESSARY)
	##OR?: #pdb.gimp_image_merge_down(img, alpha_channel_result)
	#(resultant layer; bottom layer)
	
	#Then add layer mask to result:
	alpha_on_black_result.add_mask(alpha_on_black_result.create_mask(0))
	alpha_on_black_result.edit_mask = True
	#Copy + Paste the alphaChannelLayer, then anchor it into the mask
	img.add_layer(alpha_channel_copy, len(img.layers))
	is_non_empty_copy = pdb.gimp_edit_copy(alpha_channel_copy)
	img.remove_layer(alpha_channel_copy)
	#paste (needs the previous line or gimp_edit_cut)
	#The pasting won't act on the mask - Maybe I need to specifically target the mask-object!?
	floating_sel = pdb.gimp_edit_paste(alpha_on_black_result.mask, False)
	#The anchoring won't go into the mask?? or it's fine?: 
	pdb.gimp_floating_sel_anchor(floating_sel)
	
	##Then OPTIONALLY apply the mask
	
	
register(
	"python_fu_2_layer_BW_bg_removal",					# name
	"Black/White layers Background removal",			# blurb
	"Does the bg removal explained at https://graphicdesign.stackexchange.com/questions/8057/using-gimp-can-you-erase-a-color",			# help
	"KokoroSenshi",										# author
	"KokoroSenshi - CC BY-SA",							# copyright
	"2016",												# date
	"<Image>/Filters/KScripts/2 layer BW BG Removal",	# menupath
	"*",												# imagetypes
	[],													# params
	[],													# results
	remove_bg_BW)										# function - Don't forget this!!

main()													# Don't forget this!