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
"""
Installation: place this in plug-ins and mark executable, such as:
~/.gimp-2.8/plug-ins/
(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.
For each pixel where the alpha is below the threshold, get a new color
using the nearest opaque pixel.
"""
import math
import sys
from itertools import chain
import time
from gimpfu import * # by convention, import *
@ -35,16 +21,60 @@ def idist(pos1, pos2):
fpos2 = [float(i) for i in pos2]
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:
near_pos -- This location, or the closest location to it meeting
criteria, is the search target.
Keyword arguments:
threshold -- (0 to 255) If the pixel's alpha is this or higher, get
it (the closest in location to near_pos).
good_minimum -- (0 to 255) If the pixel's alpha is this or higher,
get it (the closest in location to near_pos).
"""
if good_minimum < 0:
good_minimum = 0
epsilon = sys.float_info.epsilon
rad = 0
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:
if dist > max_rad:
max_rad = dist
for i in range(0, max_rad + 1):
left = near_pos[0] - i
right = near_pos[0] + i
top = near_pos[1] - i
bottom = near_pos[1] + i
print("find_opaque_pos({},...) # max_rad:{}".format(near_pos, max_rad))
for rad in range(0, max_rad + 1):
left = near_pos[0] - rad
right = near_pos[0] + rad
top = near_pos[1] - rad
bottom = near_pos[1] + rad
# For each side of the square, only use positions within the
# circle:
y = top
count = 0
next_count = (right + 1) - left
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:
continue
if x > w:
if x >= w:
continue
for y in (top, bottom):
# top row of square
if y < 0:
continue
if y > h:
continue
pos = (x, y)
dist = idist(near_pos, pos)
if dist <= float(i) - epsilon:
all_channels, pixel = pdb.gimp_drawable_get_pixel(
drawable,
pos[0],
pos[1]
)
if pixel[3] >= threshold:
# top row of square
if y < 0:
continue
if y >= h:
break
pos = (x, y)
dist = idist(near_pos, pos)
if dist <= float(rad) - epsilon:
print(" navigating verticals {}".format(pos))
all_channels, pixel = pdb.gimp_drawable_get_pixel(
drawable,
pos[0],
pos[1]
)
if pixel[3] >= good_minimum:
return pos
else:
print(" navigating verticals {} SKIPPED ({} > {})".format(pos, dist, rad))
pass
# 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)):
# if y == bottom - 1:
if count >= next_count:
x = right
count += 1
if y < 0:
continue
if y > h:
if y >= h:
continue
# purposely exclude corners (done in previous loop)
for x in (left, right):
if x < 0:
continue
if x > w:
continue
# top row of square
pos = (x, y)
dist = idist(near_pos, pos)
if dist <= float(i) - epsilon:
all_channels, pixel = pdb.gimp_drawable_get_pixel(
drawable,
pos[0],
pos[1]
)
if pixel[3] >= threshold:
return pos
if x < 0:
continue
if x >= w:
continue
# top row of square
pos = (x, y)
dist = idist(near_pos, pos)
if dist <= float(rad) - epsilon:
print(" navigating verticals {} SKIPPED ({} > {})".format(pos, dist, rad))
all_channels, pixel = pdb.gimp_drawable_get_pixel(
drawable,
pos[0],
pos[1]
)
if pixel[3] >= good_minimum:
return pos
else:
print(" navigating verticals {} SKIPPED".format(pos))
pass
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:
threshold -- (0 to 255) If the pixel's alpha is this or lower,
change its color
make_opaque -- Make the pixel within the threshold opaque. This is
minimum -- (0 to 255) Only edit pixels with at least this for alpha.
maximum -- (0 to 254) Only edit pixels with at most this for alpha.
make_opaque -- Make the pixel within the range opaque. This is
normally for preparing to convert images to indexed color, such as
Minetest wield_image.
"""
if maximum < 0:
maximum = 0
if minimum < 0:
minimum = 0
if maximum > 254:
maximum = 254
# exists, x1, y1, x2, y2 = \
# pdb.gimp_selection_bounds(self.img)
img = gimp.image_list()[0]
drawable = pdb.gimp_image_active_drawable(img)
w = pdb.gimp_image_width(img)
h = pdb.gimp_image_height(img)
new_channels = 3
# pdb.gimp_selection_bounds(self.image)
if image is None:
image = gimp.image_list()[0]
if drawable is None:
drawable = pdb.gimp_image_active_drawable(image)
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:
new_channels = 4
for x in range(w):
for y in range(h):
print("Size: {}".format((w, 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)
# if all([p == q for p, q in zip(pixel, color_to_edit)]):
opaque_pos = find_opaque_pos(x, y, drawable=drawable, w=w, h=h)
pdb.gimp_drawable_set_pixel(drawable, x, y, new_channels, new_color)
if (pixel[3] >= minimum) and (pixel[3] <= maximum):
# if all([p == q for p, q in zip(pixel, color_to_edit)]):
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():
extend(threshold=0, make_opaque=True)
pdb.gimp_displays_flush() # update the image
def remove_layer_halo(image, drawable, minimum, maximum, good_minimum, make_opaque):
gimp.progress_init("This may take a while...")
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(
"python_fu_remove_halo",
"Remove Halo",
"Remove alpha",
"Jake Gustafson", "Jake Gustafson", "2020",
"Redo the edge...",
"Remove Halo", # caption
"RGBA", # RGB* would mean with or without alpha.
[
(PF_SPINNER, "threshold", "Minimum alpha to fix", 254, (0, 255, 1)),
(PF_BOOL, "make_opaque", "Make the fixed parts opaque.", True),
(PF_IMAGE, "image", "Current image", 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),
],
[],
remove_layer_halo, menu="<Image>/Layer,Remove Halo"
[], # results
remove_layer_halo,
menu="<Image>/Layer"
)
main()

Loading…
Cancel
Save