You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
9.9 KiB
285 lines
9.9 KiB
#!/usr/bin/env python
|
|
"""
|
|
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 *
|
|
|
|
|
|
def fdist(pos1, pos2):
|
|
return math.sqrt((pos2[0] - pos1[0])**2 + (pos2[1] - pos1[1])**2)
|
|
|
|
|
|
def idist(pos1, pos2):
|
|
fpos1 = [float(i) for i in pos1]
|
|
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, 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:
|
|
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:
|
|
img = gimp.image_list()[0]
|
|
drawable = pdb.gimp_image_active_drawable(img)
|
|
w = pdb.gimp_image_width(img)
|
|
h = pdb.gimp_image_height(img)
|
|
if max_rad is None:
|
|
max_rad = 0
|
|
side_distances = [
|
|
abs(0 - near_pos[0]),
|
|
abs(w - near_pos[0]),
|
|
abs(0 - near_pos[1]),
|
|
abs(h - near_pos[1]),
|
|
]
|
|
for dist in side_distances:
|
|
if dist > max_rad:
|
|
max_rad = dist
|
|
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:
|
|
continue
|
|
# 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:
|
|
continue
|
|
# purposely exclude corners (done in previous loop)
|
|
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(image=None, drawable=None, minimum=1, maximum=254, make_opaque=False, good_minimum=255):
|
|
"""
|
|
Keyword arguments:
|
|
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.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
|
|
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 (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(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://www.youtube.com/watch?v=uSt80abcmJs
|
|
register(
|
|
"python_fu_remove_halo",
|
|
"Remove Halo",
|
|
"Remove alpha",
|
|
"Jake Gustafson", "Jake Gustafson", "2020",
|
|
"Remove Halo", # caption
|
|
"RGBA", # RGB* would mean with or without alpha.
|
|
[
|
|
(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),
|
|
],
|
|
[], # results
|
|
remove_layer_halo,
|
|
menu="<Image>/Layer"
|
|
)
|
|
|
|
main()
|
|
|