Browse Source

correct GIMP API usage

master
poikilos 5 years ago
committed by Jacob Gustafson
parent
commit
19dbf1f1d5
  1. 281
      utilities/gimp/plug-ins/remove_halo.py

281
utilities/gimp/plug-ins/remove_halo.py

@ -1,27 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Installation: place this in plug-ins and mark executable, such as: For each pixel where the alpha is below the threshold, get a new color
~/.gimp-2.8/plug-ins/ using the nearest opaque pixel.
(To find your plug-in paths, open GIMP, click Edit, Preferences,
Folders, Plug-ins)
Upgrading: You must restart GIMP if you upgrade the plugin.
Development Notes:
- In GIMP, see Help, Prodedure browser to view GIMP's own documentation.
Python differences from regular API
(See https://www.youtube.com/watch?v=YHXX3KuB23Q):
- Change dashes to underscores
- Change -1 to None
- do not pass run mode
- Making a copy of the pixels would be much faster if repetitive get
pixel calls are necessary.
""" """
import math import math
import sys import sys
from itertools import chain from itertools import chain
import time
from gimpfu import * # by convention, import * from gimpfu import * # by convention, import *
@ -35,16 +21,60 @@ def idist(pos1, pos2):
fpos2 = [float(i) for i in pos2] fpos2 = [float(i) for i in pos2]
return fdist(fpos1, fpos2) return fdist(fpos1, fpos2)
def square_shape(pos, rad):
left = pos[0] - rad
right = pos[0] + rad
top = pos[1] - rad
bottom = pos[1] + rad
x = left
y = top
v_count = left - right + 1
h_count = bottom - (top + 1)
ender = v_count * 2 + h_count * 2
ss_U = 0
ss_D = 1
ss_L = 2
ss_R = 3
d = ss_R
while True:
yield (x,y)
if d == "r":
x += 1
if x > right:
x = right
y += 1
d = ss_D
elif d == ss_D:
y += 1
if y > bottom:
y = bottom
x -= 1
d = ss_L
elif d == ss_L:
x -= 1
if x < left:
x = left
y -= 1
d = ss_U
else:
y -= 1
if y < top:
y = top
break
def find_opaque_pos(near_pos, threshold=255, max_rad=None, drawable=None, w=None, h=None):
def find_opaque_pos(near_pos, good_minimum=255, max_rad=None, drawable=None, w=None, h=None):
""" """
Sequential arguments: Sequential arguments:
near_pos -- This location, or the closest location to it meeting near_pos -- This location, or the closest location to it meeting
criteria, is the search target. criteria, is the search target.
Keyword arguments: Keyword arguments:
threshold -- (0 to 255) If the pixel's alpha is this or higher, get good_minimum -- (0 to 255) If the pixel's alpha is this or higher,
it (the closest in location to near_pos). get it (the closest in location to near_pos).
""" """
if good_minimum < 0:
good_minimum = 0
epsilon = sys.float_info.epsilon epsilon = sys.float_info.epsilon
rad = 0 rad = 0
if drawable is None: if drawable is None:
@ -63,106 +93,193 @@ def find_opaque_pos(near_pos, threshold=255, max_rad=None, drawable=None, w=None
for dist in side_distances: for dist in side_distances:
if dist > max_rad: if dist > max_rad:
max_rad = dist max_rad = dist
for i in range(0, max_rad + 1): print("find_opaque_pos({},...) # max_rad:{}".format(near_pos, max_rad))
left = near_pos[0] - i for rad in range(0, max_rad + 1):
right = near_pos[0] + i left = near_pos[0] - rad
top = near_pos[1] - i right = near_pos[0] + rad
bottom = near_pos[1] + i top = near_pos[1] - rad
bottom = near_pos[1] + rad
# For each side of the square, only use positions within the # For each side of the square, only use positions within the
# circle: # circle:
y = top
count = 0
next_count = (right + 1) - left
for x in chain(range(left, right + 1), range(left, right + 1)): for x in chain(range(left, right + 1), range(left, right + 1)):
# if x == right:
if count >= next_count:
y = bottom
count += 1
if x < 0: if x < 0:
continue continue
if x > w: if x >= w:
continue continue
for y in (top, bottom): # top row of square
# top row of square if y < 0:
if y < 0: continue
continue if y >= h:
if y > h: break
continue pos = (x, y)
pos = (x, y) dist = idist(near_pos, pos)
dist = idist(near_pos, pos) if dist <= float(rad) - epsilon:
if dist <= float(i) - epsilon: print(" navigating verticals {}".format(pos))
all_channels, pixel = pdb.gimp_drawable_get_pixel( all_channels, pixel = pdb.gimp_drawable_get_pixel(
drawable, drawable,
pos[0], pos[0],
pos[1] pos[1]
) )
if pixel[3] >= threshold: if pixel[3] >= good_minimum:
return pos return pos
else:
print(" navigating verticals {} SKIPPED ({} > {})".format(pos, dist, rad))
pass
# For each side of the square (continued) # For each side of the square (continued)
count = 0
next_count = bottom - (top + 1)
for y in chain(range(top+1, bottom), range(top+1, bottom)): for y in chain(range(top+1, bottom), range(top+1, bottom)):
# if y == bottom - 1:
if count >= next_count:
x = right
count += 1
if y < 0: if y < 0:
continue continue
if y > h: if y >= h:
continue continue
# purposely exclude corners (done in previous loop) # purposely exclude corners (done in previous loop)
for x in (left, right): if x < 0:
if x < 0: continue
continue if x >= w:
if x > w: continue
continue # top row of square
# top row of square pos = (x, y)
pos = (x, y) dist = idist(near_pos, pos)
dist = idist(near_pos, pos) if dist <= float(rad) - epsilon:
if dist <= float(i) - epsilon: print(" navigating verticals {} SKIPPED ({} > {})".format(pos, dist, rad))
all_channels, pixel = pdb.gimp_drawable_get_pixel( all_channels, pixel = pdb.gimp_drawable_get_pixel(
drawable, drawable,
pos[0], pos[0],
pos[1] pos[1]
) )
if pixel[3] >= threshold: if pixel[3] >= good_minimum:
return pos return pos
else:
print(" navigating verticals {} SKIPPED".format(pos))
pass
return None return None
def extend(threshold=254, make_opaque=False): def extend(image=None, drawable=None, minimum=1, maximum=254, make_opaque=False, good_minimum=255):
""" """
Keyword arguments: Keyword arguments:
threshold -- (0 to 255) If the pixel's alpha is this or lower, minimum -- (0 to 255) Only edit pixels with at least this for alpha.
change its color maximum -- (0 to 254) Only edit pixels with at most this for alpha.
make_opaque -- Make the pixel within the threshold opaque. This is make_opaque -- Make the pixel within the range opaque. This is
normally for preparing to convert images to indexed color, such as normally for preparing to convert images to indexed color, such as
Minetest wield_image. Minetest wield_image.
""" """
if maximum < 0:
maximum = 0
if minimum < 0:
minimum = 0
if maximum > 254:
maximum = 254
# exists, x1, y1, x2, y2 = \ # exists, x1, y1, x2, y2 = \
# pdb.gimp_selection_bounds(self.img) # pdb.gimp_selection_bounds(self.image)
img = gimp.image_list()[0] if image is None:
drawable = pdb.gimp_image_active_drawable(img) image = gimp.image_list()[0]
w = pdb.gimp_image_width(img) if drawable is None:
h = pdb.gimp_image_height(img) drawable = pdb.gimp_image_active_drawable(image)
new_channels = 3 w = pdb.gimp_image_width(image)
h = pdb.gimp_image_height(image)
new_channels = 4
# ^ new_channels must match the destination channel count,
# or an ExecutionError occurs.
if make_opaque: if make_opaque:
new_channels = 4 new_channels = 4
for x in range(w): print("Size: {}".format((w, h)))
for y in range(h): total_f = float(w * h)
count_f = 0.0
# ok = True
for y in range(h):
# if not ok:
# break
for x in range(w):
# if count_f is None:
count_f = float(y) * float(w) + float(x)
# print("checking {}".format(pdb.gimp_drawable_get_pixel(drawable, x, y)))
all_channels, pixel = pdb.gimp_drawable_get_pixel(drawable, x, y) all_channels, pixel = pdb.gimp_drawable_get_pixel(drawable, x, y)
# if all([p == q for p, q in zip(pixel, color_to_edit)]): if (pixel[3] >= minimum) and (pixel[3] <= maximum):
opaque_pos = find_opaque_pos(x, y, drawable=drawable, w=w, h=h) # if all([p == q for p, q in zip(pixel, color_to_edit)]):
pdb.gimp_drawable_set_pixel(drawable, x, y, new_channels, new_color) pos = (x, y)
opaque_pos = find_opaque_pos((x, y), drawable=drawable, w=w, h=h, good_minimum=good_minimum)
if (opaque_pos is not None):
if opaque_pos == pos:
msg = "Uh oh, got own pos when checking for better color than {}...".format(pixel)
print(msg)
# pdb.gimp_message(msg)
gimp.progress_init(msg)
gimp.progress_update(0.0)
time.sleep(10)
# ok = False
continue
all_channels, new_pixel = pdb.gimp_drawable_get_pixel(drawable, opaque_pos[0], opaque_pos[1])
if new_pixel != pixel:
if make_opaque:
new_pixel = (new_pixel[0], new_pixel[1], new_pixel[2], 255)
# print("Changing pixel at {} from {} to {}".format((x, y), pixel, new_pixel))
print("Changing pixel at {} using color from {}".format((x, y), opaque_pos))
pdb.gimp_drawable_set_pixel(drawable, x, y, new_channels, new_pixel)
else:
# msg = "Uh oh, got own {} color {} at {} when checking for color at better position than {}...".format(pixel, new_pixel, opaque_pos, pos)
# print(msg)
# pdb.gimp_message(msg)
# gimp.progress_init(msg)
# gimp.progress_update(count_f / total_f)
# count_f = None
# time.sleep(10)
# return
# continue
pass
else:
msg = "Uh oh, the image has no pixels at or above the minimum good alpha."
print(msg)
pdb.gimp_message(msg)
pdb.gimp_drawable_update(drawable,0,0,drawable.width,drawable.height)
return
if count_f is not None:
# count_f += 1.0
gimp.progress_update(count_f / total_f)
pdb.gimp_drawable_update(drawable,0,0,drawable.width,drawable.height)
def remove_layer_halo(): def remove_layer_halo(image, drawable, minimum, maximum, good_minimum, make_opaque):
extend(threshold=0, make_opaque=True) gimp.progress_init("This may take a while...")
pdb.gimp_displays_flush() # update the image print("options: {}".format((str(image), str(drawable), str(minimum), str(maximum), str(make_opaque), str(good_minimum))))
extend(image=image, drawable=drawable, minimum=minimum, maximum=maximum, make_opaque=make_opaque, good_minimum=good_minimum)
print("updating image...")
pdb.gimp_displays_flush() # update the image; doesn't work
print("done (remove_layer_halo)")
# See https://jacksonbates.wordpress.com/2015/09/14/python-fu-6-accepting-user-input/ # See https://www.youtube.com/watch?v=uSt80abcmJs
register( register(
"python_fu_remove_halo", "python_fu_remove_halo",
"Remove Halo", "Remove Halo",
"Remove alpha", "Remove alpha",
"Jake Gustafson", "Jake Gustafson", "2020", "Jake Gustafson", "Jake Gustafson", "2020",
"Redo the edge...", "Remove Halo", # caption
"RGBA", # RGB* would mean with or without alpha. "RGBA", # RGB* would mean with or without alpha.
[ [
(PF_SPINNER, "threshold", "Minimum alpha to fix", 254, (0, 255, 1)), (PF_IMAGE, "image", "Current image", None),
(PF_BOOL, "make_opaque", "Make the fixed parts opaque.", True),
(PF_DRAWABLE, "drawable", "Input layer", None), (PF_DRAWABLE, "drawable", "Input layer", None),
(PF_SPINNER, "minimum", "Minimum alpha to fix (normally 1)", 1, (0, 255, 1)),
(PF_SPINNER, "maximum", "Maximum alpha to fix (normally 254 unless less damaged)", 254, (0, 255, 1)),
(PF_SPINNER, "good_minimum", "Find a nearby color >= this (usually maximum+1).", 255, (0, 255, 1)),
(PF_TOGGLE, "make_opaque", "Make the fixed parts opaque.", True),
], ],
[], [], # results
remove_layer_halo, menu="<Image>/Layer,Remove Halo" remove_layer_halo,
menu="<Image>/Layer"
) )
main() main()

Loading…
Cancel
Save