Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update resynthesizer heal selection plugin to work with recent 2.99 release #136

Open
wants to merge 8 commits into
base: deprecations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
366 changes: 219 additions & 147 deletions PluginScripts/plugin-heal-selection.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python3
WarpspeedSCP marked this conversation as resolved.
Show resolved Hide resolved

'''
Gimp plugin "Heal selection"
Expand Down Expand Up @@ -27,153 +27,225 @@

'''

from gimpfu import *
import sys
import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
gi.require_version('GimpUi', '3.0')
from gi.repository import GimpUi
from gi.repository import GObject
from gi.repository import GLib

# Python 2 gettext.install("resynthesizer", Gimp.locale_directory(), unicode=True)
gettext.install("resynthesizer", Gimp.locale_directory())
PLUGIN_NAME = 'resynthesizer-heal-selection'
def N_(message): return message
def _(message): return GLib.dgettext(None, message)

debug = False

def heal_selection(timg, tdrawable, samplingRadiusParam=50, directionParam=0, orderParam=0):
'''
Create stencil selection in a temp image to pass as source (corpus) to plugin resynthesizer,
which does the substantive work.
'''
if pdb.gimp_selection_is_empty(timg):
pdb.gimp_message(_("You must first select a region to heal."))
return

pdb.gimp_image_undo_group_start(timg)

targetBounds = tdrawable.mask_bounds

# In duplicate image, create the sample (corpus).
# (I tried to use a temporary layer but found it easier to use duplicate image.)
tempImage = pdb.gimp_image_duplicate(timg)
if not tempImage:
raise RuntimeError("Failed duplicate image")

# !!! The drawable can be a mask (grayscale channel), don't restrict to layer.
work_drawable = pdb.gimp_image_get_active_drawable(tempImage)
if not work_drawable:
raise RuntimeError("Failed get active drawable")

'''
grow and punch hole, making a frisket iow stencil iow donut

'''
orgSelection = pdb.gimp_selection_save(tempImage) # save for later use
pdb.gimp_selection_grow(tempImage, samplingRadiusParam)
# ??? returns None , docs say it returns SUCCESS

# !!! Note that if selection is a bordering ring already, growing expanded it inwards.
# Which is what we want, to make a corpus inwards.

grownSelection = pdb.gimp_selection_save(tempImage)

# Cut hole where the original selection was, so we don't sample from it.
# !!! Note that gimp enums/constants are not prefixed with GIMP_
pdb.gimp_image_select_item(tempImage, CHANNEL_OP_SUBTRACT, orgSelection)

'''
Selection (to be the corpus) is donut or frisket around the original target T
xxx
xTx
xxx
'''

# crop the temp image to size of selection to save memory and for directional healing!!
frisketBounds = grownSelection.mask_bounds
frisketLowerLeftX = frisketBounds[0]
frisketLowerLeftY = frisketBounds[1]
frisketUpperRightX = frisketBounds[2]
frisketUpperRightY = frisketBounds[3]
targetLowerLeftX = targetBounds[0]
targetLowerLeftY = targetBounds[1]
targetUpperRightX = targetBounds[2]
targetUpperRightY = targetBounds[3]

frisketWidth = frisketUpperRightX - frisketLowerLeftX
frisketHeight = frisketUpperRightY - frisketLowerLeftY

# User's choice of direction affects the corpus shape, and is also passed to resynthesizer plugin
if directionParam == 0: # all around
# Crop to the entire frisket
newWidth, newHeight, newLLX, newLLY = ( frisketWidth, frisketHeight,
frisketLowerLeftX, frisketLowerLeftY )
elif directionParam == 1: # sides
# Crop to target height and frisket width: XTX
newWidth, newHeight, newLLX, newLLY = ( frisketWidth, targetUpperRightY-targetLowerLeftY,
frisketLowerLeftX, targetLowerLeftY )
elif directionParam == 2: # above and below
# X Crop to target width and frisket height
# T
# X
newWidth, newHeight, newLLX, newLLY = ( targetUpperRightX-targetLowerLeftX, frisketHeight,
targetLowerLeftX, frisketLowerLeftY )
# Restrict crop to image size (condition of gimp_image_crop) eg when off edge of image
newWidth = min(pdb.gimp_image_width(tempImage) - newLLX, newWidth)
newHeight = min(pdb.gimp_image_height(tempImage) - newLLY, newHeight)
pdb.gimp_image_crop(tempImage, newWidth, newHeight, newLLX, newLLY)

# Encode two script params into one resynthesizer param.
# use border 1 means fill target in random order
# use border 0 is for texture mapping operations, not used by this script
if not orderParam :
useBorder = 1 # User wants NO order, ie random filling
elif orderParam == 1 : # Inward to corpus. 2,3,4
useBorder = directionParam+2 # !!! Offset by 2 to get past the original two boolean values
else:
# Outward from image center.
# 5+0=5 outward concentric
# 5+1=6 outward from sides
# 5+2=7 outward above and below
useBorder = directionParam+5

# Note that the old resynthesizer required an inverted selection !!

if debug:
try:
gimp.Display(tempImage)
gimp.displays_flush()
except RuntimeError: # thrown if non-interactive
pass
from time import sleep
sleep(2)

# Not necessary to restore image to initial condition of selection, activity,
# the original image should not have been changed,
# and the resynthesizer should only heal, not change selection.

# Note that the API hasn't changed but use_border param now has more values.
pdb.plug_in_resynthesizer(timg, tdrawable, 0,0, useBorder, work_drawable, -1, -1, 0.0, 0.117, 16, 500)

# Clean up (comment out to debug)
gimp.delete(tempImage)
pdb.gimp_image_undo_group_end(timg)


register(
"python-fu-heal-selection",
N_("Heal the selection from surroundings as if using the heal tool."),
"Requires separate resynthesizer plugin.",
"Lloyd Konneker",
"2009 Lloyd Konneker", # Copyright
"2009",
N_("_Heal selection..."),
"RGB*, GRAY*",
[
(PF_IMAGE, "image", "Input image", None),
(PF_DRAWABLE, "drawable", "Input drawable", None),
(PF_INT, "samplingRadiusParam", _("Context sampling width (pixels):"), 50),
(PF_OPTION,"directionParam", _("Sample from:"),0,[_("All around"),_("Sides"),_("Above and below")]),
(PF_OPTION, "orderParam", _("Filling order:"), 0, [_("Random"),
_("Inwards towards center"), _("Outwards from center") ])
],
[],
heal_selection,
menu="<Image>/Filters/Enhance",
domain=("resynthesizer", Gimp.locale_directory())
)

main()
class HealSel (Gimp.PlugIn):
## Parameters ##


## GimpPlugIn virtual methods ##
def do_set_i18n(self, procname):
return True, 'gimp30-python', None

def do_query_procedures(self):
return [ PLUGIN_NAME ]

def do_create_procedure(self, name):
if (name == PLUGIN_NAME):
WarpspeedSCP marked this conversation as resolved.
Show resolved Hide resolved
procedure: Gimp.ImageProcedure = Gimp.ImageProcedure.new(self, name,
Gimp.PDBProcType.PLUGIN,
self.run, None)
procedure.set_image_types("RGB*, GRAY*")
procedure.set_sensitivity_mask (Gimp.ProcedureSensitivityMask.DRAWABLE)
procedure.set_documentation (_("resynthesizer heal selection"),
_("heal selection with the resynthesizer algorithm"),
name)
procedure.set_menu_label(_("_heal selection"))
procedure.set_attribution("James Henstridge",
"James Henstridge",
"1999,2007")
procedure.add_menu_path ("<Image>/Filters/Enhance")

procedure.add_int_argument(
name="samplingRadiusParam",
nick=_("Sampling radius"),
blurb=_("The sampling radius (in pixels)"),
min=1,
max=1000,
value=50,
flags=GObject.ParamFlags.READWRITE
)

direction_choice = Gimp.Choice.new()

direction_choice.add("all_around", 0, _("All around"), "")
direction_choice.add("sides_only", 1, _("Sides only"), "")
direction_choice.add("above_and_below", 2, _("Above and below only"), "")

procedure.add_choice_argument ("directionParam", _("Direct_ion"), _("Where to sample pixels from"),
direction_choice, "all_around", GObject.ParamFlags.READWRITE)
order_choice = Gimp.Choice.new()
order_choice.add("random", 0, _("Random"), "")
order_choice.add("inwards", 1, _("Inwards"), "")
order_choice.add("outwards", 2, _("Outwards"), "")

procedure.add_choice_argument ("orderParam", _("Order"), _("The order to fill the selection in"),
order_choice, "random", GObject.ParamFlags.READWRITE)
return procedure
return None

def run(self, procedure: Gimp.Procedure, run_mode: Gimp.RunMode, image: Gimp.Image, layers, config, data):

if Gimp.Selection.is_empty(image):
Gimp.message("You must first select a region to heal.")
return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, GLib.Error(None, None, "Select something first."))

if run_mode == Gimp.RunMode.INTERACTIVE:
GimpUi.init(PLUGIN_NAME)
dialog = GimpUi.ProcedureDialog(procedure=procedure, config=config)
dialog.fill(None)
if not dialog.run():
dialog.destroy()
return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, GLib.Error())
else:
dialog.destroy()

samplingRadius: int = config.get_property('samplingRadiusParam')
direction: str = config.get_property('directionParam')
order: str = config.get_property('orderParam')

image.undo_group_start()

# select the bounds of the bottom-most layer.
target_bounds = layers[0].mask_bounds()

temp: Gimp.Image = image.duplicate()

if debug:
try:
disp: Gimp.Display = Gimp.Display.new(image=temp)
Gimp.displays_flush()
except RuntimeError: # thrown if non-interactive
pass
from time import sleep
sleep(2)

# We merge all visible layers together to make things easier for us.

work_drawable: Gimp.Layer = temp.merge_visible_layers(Gimp.MergeType.CLIP_TO_IMAGE)

selection: Gimp.Selection = image.get_selection()

orig_selection: Gimp.Channel = selection.save(temp)
if not selection.grow(temp, samplingRadius):
Gimp.message("Could not grow selection")
return procedure.new_return_values(Gimp.PDBStatusType.EXECUTION_ERROR, GLib.Error(None, None, "couldn't grow the selection somehow."))

grown_selection: Gimp.Channel = selection.save(temp)
temp.select_item(Gimp.ChannelOps.SUBTRACT, orig_selection)

# crop the temp image to size of selection to save memory and for directional healing!!
frisketBounds = grown_selection.mask_bounds()
frisketLowerLeftX = frisketBounds[0]
frisketLowerLeftY = frisketBounds[1]
frisketUpperRightX = frisketBounds[2]
frisketUpperRightY = frisketBounds[3]

targetLowerLeftX = target_bounds[0]
targetLowerLeftY = target_bounds[1]
targetUpperRightX = target_bounds[2]
targetUpperRightY = target_bounds[3]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
frisketLowerLeftX = frisketBounds[0]
frisketLowerLeftY = frisketBounds[1]
frisketUpperRightX = frisketBounds[2]
frisketUpperRightY = frisketBounds[3]
targetLowerLeftX = target_bounds[0]
targetLowerLeftY = target_bounds[1]
targetUpperRightX = target_bounds[2]
targetUpperRightY = target_bounds[3]
frisketLowerLeftX, frisketLowerLeftY, frisketUpperRightX, frisketUpperRightY = frisketBounds
targetLowerLeftX, targetLowerLeftY, targetUpperRightX, targetUpperRightY = target_bounds

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used a different method to extract the coords now.


frisketWidth = abs(frisketUpperRightX - frisketLowerLeftX)
frisketHeight = abs(frisketUpperRightY - frisketLowerLeftY)


newWidth, newHeight, newLLX, newLLY = (0, 0, 0, 0)

# User's choice of direction affects the corpus shape, and is also passed to resynthesizer plugin
if direction == 'all_around': # all around
# Crop to the entire frisket
newWidth, newHeight, newLLX, newLLY = (
frisketWidth,
frisketHeight,
frisketLowerLeftX,
frisketLowerLeftY
)
elif direction == 'sides_only': # sides
# Crop to target height and frisket width: XTX
newWidth, newHeight, newLLX, newLLY = (
frisketWidth,
targetUpperRightY-targetLowerLeftY,
frisketLowerLeftX,
targetLowerLeftY
)
elif direction == 'above_and_below': # above and below
# X Crop to target width and frisket height
# T
# X
newWidth, newHeight, newLLX, newLLY = (
targetUpperRightX-targetLowerLeftX,
frisketHeight,
targetLowerLeftX,
frisketLowerLeftY
)

# Restrict crop to image size (condition of gimp_image_crop) eg when off edge of image
newWidth = min(temp.get_width() - newLLX, newWidth)
newHeight = min(temp.get_height() - newLLY, newHeight)
temp.crop(newWidth, newHeight, newLLX, newLLY)

# default, just to declare the value.
useBorder = 1

# Encode two script params into one resynthesizer param.
# use border 1 means fill target in random order
# use border 0 is for texture mapping operations, not used by this script
if order == 'random':
useBorder = 1 # User wants NO order, ie random filling
elif order == 'inwards' : # Inward to corpus. 2,3,4
useBorder = direction + 2 # !!! Offset by 2 to get past the original two boolean values
else:
# Outward from image center.
# 5+0=5 outward concentric
# 5+1=6 outward from sides
# 5+2=7 outward above and below
useBorder = direction + 5

# Note that the old resynthesizer required an inverted selection !!

# Not necessary to restore image to initial condition of selection, activity,
# the original image should not have been changed,
# and the resynthesizer should only heal, not change selection.

# Note that the API hasn't changed but use_border param now has more values.
pdb: Gimp.PDB = Gimp.get_pdb()
pdb_proc: Gimp.Procedure = pdb.lookup_procedure('plug-in-resynthesizer')
pdb_config: Gimp.ProcedureConfig = pdb_proc.create_config()
pdb_config.set_property('run-mode', Gimp.RunMode.NONINTERACTIVE)
pdb_config.set_property('image', image)
# A hacky way to pass in python arrays directly,
# see: https://gitlab.gnome.org/GNOME/gobject-introspection/-/issues/492
pdb_config.set_core_object_array('drawables', layers)
pdb_config.set_property('h-tile', 0)
pdb_config.set_property('v-tile', 0)
pdb_config.set_property('use-border', useBorder)
pdb_config.set_property('corpus-drawable', work_drawable)
pdb_config.set_property('input-map', None)
pdb_config.set_property('output-map', None)
pdb_config.set_property('map-weight', 0.0)
pdb_config.set_property('autism', 0.117)
pdb_config.set_property('neighbours', 16)
pdb_config.set_property('trys', 500)
pdb_proc.run(pdb_config)

# Clean up (comment out to debug)
temp.delete()

image.undo_group_end()
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())

Gimp.main(HealSel.__gtype__, sys.argv)
5 changes: 4 additions & 1 deletion src/resynth-parameters.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ to the adapter from Gimp to the innermost engine.

!!! v2
*/
#if GIMP_MAJOR_VERSION == 2 && GIMP_MINOR_VERSION < 99


typedef struct GIMPAdapterParametersStructOld {

int h_tile;
Expand Down Expand Up @@ -281,6 +284,6 @@ set_parameters_to_list(
}
}


#endif


Loading