#!/usr/bin/env python2

'''
Gimp plugin "Heal selection"

Copyright 2009 lloyd konneker (bootch at nc.rr.com)
Based on smart_remove.scm Copyright 2000 by Paul Harrison.

Version:
  1.0 lloyd konneker lkk 9/21/2009 Initial version in python.
  (See release notes for differences over P. Harrison's prior version in scheme language.)

License:

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  The GNU Public License is available at
  http://www.gnu.org/copyleft/gpl.html

'''

from gimpfu import *

gettext.install("resynthesizer", gimp.locale_directory, unicode=True)

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_selection_combine(orgSelection, CHANNEL_OP_SUBTRACT)
  
  '''
  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.ID, -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()
