Improving Plugns scripts & Add Editor Config
This commit is contained in:
parent
2634d16361
commit
c96db15b00
13 changed files with 606 additions and 528 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -3,4 +3,7 @@
|
||||||
|
|
||||||
# Ignore Personal GIMP Files
|
# Ignore Personal GIMP Files
|
||||||
.var/app/org.gimp.GIMP/data
|
.var/app/org.gimp.GIMP/data
|
||||||
.var/app/org.gimp.GIMP/current
|
.var/app/org.gimp.GIMP/current
|
||||||
|
|
||||||
|
# Ignore VSCode Files
|
||||||
|
.vscode/*
|
||||||
|
|
|
||||||
|
|
@ -41,46 +41,47 @@ def heal_selection(timg, tdrawable, samplingRadiusParam=50, directionParam=0, or
|
||||||
if pdb.gimp_selection_is_empty(timg):
|
if pdb.gimp_selection_is_empty(timg):
|
||||||
pdb.gimp_message(_("You must first select a region to heal."))
|
pdb.gimp_message(_("You must first select a region to heal."))
|
||||||
return
|
return
|
||||||
|
|
||||||
pdb.gimp_image_undo_group_start(timg)
|
pdb.gimp_image_undo_group_start(timg)
|
||||||
|
|
||||||
targetBounds = tdrawable.mask_bounds
|
targetBounds = tdrawable.mask_bounds
|
||||||
|
|
||||||
# In duplicate image, create the sample (corpus).
|
# In duplicate image, create the sample (corpus).
|
||||||
# (I tried to use a temporary layer but found it easier to use duplicate image.)
|
# (I tried to use a temporary layer but found it easier to use duplicate image.)
|
||||||
tempImage = pdb.gimp_image_duplicate(timg)
|
tempImage = pdb.gimp_image_duplicate(timg)
|
||||||
|
|
||||||
if not tempImage:
|
if not tempImage:
|
||||||
raise RuntimeError, "Failed duplicate image"
|
raise RuntimeError, "Failed duplicate image"
|
||||||
|
|
||||||
# !!! The drawable can be a mask (grayscale channel), don't restrict to layer.
|
# !!! The drawable can be a mask (grayscale channel), don't restrict to layer.
|
||||||
work_drawable = pdb.gimp_image_get_active_drawable(tempImage)
|
work_drawable = pdb.gimp_image_get_active_drawable(tempImage)
|
||||||
if not work_drawable:
|
if not work_drawable:
|
||||||
raise RuntimeError, "Failed get active drawable"
|
raise RuntimeError, "Failed get active drawable"
|
||||||
|
|
||||||
'''
|
'''
|
||||||
grow and punch hole, making a frisket iow stencil iow donut
|
grow and punch hole, making a frisket iow stencil iow donut
|
||||||
|
|
||||||
'''
|
'''
|
||||||
orgSelection = pdb.gimp_selection_save(tempImage) # save for later use
|
orgSelection = pdb.gimp_selection_save(tempImage) # save for later use
|
||||||
pdb.gimp_selection_grow(tempImage, samplingRadiusParam)
|
pdb.gimp_selection_grow(tempImage, samplingRadiusParam)
|
||||||
# ??? returns None , docs say it returns SUCCESS
|
# ??? returns None , docs say it returns SUCCESS
|
||||||
|
|
||||||
# !!! Note that if selection is a bordering ring already, growing expanded it inwards.
|
# !!! Note that if selection is a bordering ring already, growing expanded it inwards.
|
||||||
# Which is what we want, to make a corpus inwards.
|
# Which is what we want, to make a corpus inwards.
|
||||||
|
|
||||||
grownSelection = pdb.gimp_selection_save(tempImage)
|
grownSelection = pdb.gimp_selection_save(tempImage)
|
||||||
|
|
||||||
# Cut hole where the original selection was, so we don't sample from it.
|
# Cut hole where the original selection was, so we don't sample from it.
|
||||||
# !!! Note that gimp enums/constants are not prefixed with GIMP_
|
# !!! Note that gimp enums/constants are not prefixed with GIMP_
|
||||||
pdb.gimp_image_select_item(tempImage, CHANNEL_OP_SUBTRACT, orgSelection)
|
pdb.gimp_selection_combine(orgSelection, CHANNEL_OP_SUBTRACT)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Selection (to be the corpus) is donut or frisket around the original target T
|
Selection (to be the corpus) is donut or frisket around the original target T
|
||||||
xxx
|
xxx
|
||||||
xTx
|
xTx
|
||||||
xxx
|
xxx
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# crop the temp image to size of selection to save memory and for directional healing!!
|
# crop the temp image to size of selection to save memory and for directional healing!!
|
||||||
frisketBounds = grownSelection.mask_bounds
|
frisketBounds = grownSelection.mask_bounds
|
||||||
frisketLowerLeftX = frisketBounds[0]
|
frisketLowerLeftX = frisketBounds[0]
|
||||||
|
|
@ -91,30 +92,30 @@ def heal_selection(timg, tdrawable, samplingRadiusParam=50, directionParam=0, or
|
||||||
targetLowerLeftY = targetBounds[1]
|
targetLowerLeftY = targetBounds[1]
|
||||||
targetUpperRightX = targetBounds[2]
|
targetUpperRightX = targetBounds[2]
|
||||||
targetUpperRightY = targetBounds[3]
|
targetUpperRightY = targetBounds[3]
|
||||||
|
|
||||||
frisketWidth = frisketUpperRightX - frisketLowerLeftX
|
frisketWidth = frisketUpperRightX - frisketLowerLeftX
|
||||||
frisketHeight = frisketUpperRightY - frisketLowerLeftY
|
frisketHeight = frisketUpperRightY - frisketLowerLeftY
|
||||||
|
|
||||||
# User's choice of direction affects the corpus shape, and is also passed to resynthesizer plugin
|
# User's choice of direction affects the corpus shape, and is also passed to resynthesizer plugin
|
||||||
if directionParam == 0: # all around
|
if directionParam == 0: # all around
|
||||||
# Crop to the entire frisket
|
# Crop to the entire frisket
|
||||||
newWidth, newHeight, newLLX, newLLY = ( frisketWidth, frisketHeight,
|
newWidth, newHeight, newLLX, newLLY = ( frisketWidth, frisketHeight,
|
||||||
frisketLowerLeftX, frisketLowerLeftY )
|
frisketLowerLeftX, frisketLowerLeftY )
|
||||||
elif directionParam == 1: # sides
|
elif directionParam == 1: # sides
|
||||||
# Crop to target height and frisket width: XTX
|
# Crop to target height and frisket width: XTX
|
||||||
newWidth, newHeight, newLLX, newLLY = ( frisketWidth, targetUpperRightY-targetLowerLeftY,
|
newWidth, newHeight, newLLX, newLLY = ( frisketWidth, targetUpperRightY-targetLowerLeftY,
|
||||||
frisketLowerLeftX, targetLowerLeftY )
|
frisketLowerLeftX, targetLowerLeftY )
|
||||||
elif directionParam == 2: # above and below
|
elif directionParam == 2: # above and below
|
||||||
# X Crop to target width and frisket height
|
# X Crop to target width and frisket height
|
||||||
# T
|
# T
|
||||||
# X
|
# X
|
||||||
newWidth, newHeight, newLLX, newLLY = ( targetUpperRightX-targetLowerLeftX, frisketHeight,
|
newWidth, newHeight, newLLX, newLLY = ( targetUpperRightX-targetLowerLeftX, frisketHeight,
|
||||||
targetLowerLeftX, frisketLowerLeftY )
|
targetLowerLeftX, frisketLowerLeftY )
|
||||||
# Restrict crop to image size (condition of gimp_image_crop) eg when off edge of image
|
# 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)
|
newWidth = min(pdb.gimp_image_width(tempImage) - newLLX, newWidth)
|
||||||
newHeight = min(pdb.gimp_image_height(tempImage) - newLLY, newHeight)
|
newHeight = min(pdb.gimp_image_height(tempImage) - newLLY, newHeight)
|
||||||
pdb.gimp_image_crop(tempImage, newWidth, newHeight, newLLX, newLLY)
|
pdb.gimp_image_crop(tempImage, newWidth, newHeight, newLLX, newLLY)
|
||||||
|
|
||||||
# Encode two script params into one resynthesizer param.
|
# Encode two script params into one resynthesizer param.
|
||||||
# use border 1 means fill target in random order
|
# use border 1 means fill target in random order
|
||||||
# use border 0 is for texture mapping operations, not used by this script
|
# use border 0 is for texture mapping operations, not used by this script
|
||||||
|
|
@ -123,30 +124,30 @@ def heal_selection(timg, tdrawable, samplingRadiusParam=50, directionParam=0, or
|
||||||
elif orderParam == 1 : # Inward to corpus. 2,3,4
|
elif orderParam == 1 : # Inward to corpus. 2,3,4
|
||||||
useBorder = directionParam+2 # !!! Offset by 2 to get past the original two boolean values
|
useBorder = directionParam+2 # !!! Offset by 2 to get past the original two boolean values
|
||||||
else:
|
else:
|
||||||
# Outward from image center.
|
# Outward from image center.
|
||||||
# 5+0=5 outward concentric
|
# 5+0=5 outward concentric
|
||||||
# 5+1=6 outward from sides
|
# 5+1=6 outward from sides
|
||||||
# 5+2=7 outward above and below
|
# 5+2=7 outward above and below
|
||||||
useBorder = directionParam+5
|
useBorder = directionParam+5
|
||||||
|
|
||||||
# Note that the old resynthesizer required an inverted selection !!
|
# Note that the old resynthesizer required an inverted selection !!
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
try:
|
try:
|
||||||
gimp.Display(tempImage)
|
gimp.Display(tempImage)
|
||||||
gimp.displays_flush()
|
gimp.displays_flush()
|
||||||
except RuntimeError: # thrown if non-interactive
|
except RuntimeError: # thrown if non-interactive
|
||||||
pass
|
pass
|
||||||
from time import sleep
|
from time import sleep
|
||||||
sleep(2)
|
sleep(2)
|
||||||
|
|
||||||
# Not necessary to restore image to initial condition of selection, activity,
|
# Not necessary to restore image to initial condition of selection, activity,
|
||||||
# the original image should not have been changed,
|
# the original image should not have been changed,
|
||||||
# and the resynthesizer should only heal, not change selection.
|
# and the resynthesizer should only heal, not change selection.
|
||||||
|
|
||||||
# Note that the API hasn't changed but use_border param now has more values.
|
# 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)
|
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)
|
# Clean up (comment out to debug)
|
||||||
gimp.delete(tempImage)
|
gimp.delete(tempImage)
|
||||||
pdb.gimp_image_undo_group_end(timg)
|
pdb.gimp_image_undo_group_end(timg)
|
||||||
|
|
@ -157,7 +158,7 @@ register(
|
||||||
N_("Heal the selection from surroundings as if using the heal tool."),
|
N_("Heal the selection from surroundings as if using the heal tool."),
|
||||||
"Requires separate resynthesizer plugin.",
|
"Requires separate resynthesizer plugin.",
|
||||||
"Lloyd Konneker",
|
"Lloyd Konneker",
|
||||||
"2009 Lloyd Konneker", # Copyright
|
"2009 Lloyd Konneker", # Copyright
|
||||||
"2009",
|
"2009",
|
||||||
N_("_Heal selection..."),
|
N_("_Heal selection..."),
|
||||||
"RGB*, GRAY*",
|
"RGB*, GRAY*",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Gimp plugin "Heal transparency"
|
Gimp plugin "Heal transparency"
|
||||||
|
|
||||||
Copyright 2010 lloyd konneker (bootch at nc.rr.com)
|
Copyright 2010 lloyd konneker (bootch at nc.rr.com)
|
||||||
|
|
@ -22,71 +22,81 @@ License:
|
||||||
|
|
||||||
The GNU Public License is available at
|
The GNU Public License is available at
|
||||||
http://www.gnu.org/copyleft/gpl.html
|
http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from gimpfu import *
|
from gimpfu import *
|
||||||
|
|
||||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
|
|
||||||
|
|
||||||
def heal_transparency(timg, tdrawable, samplingRadiusParam=50, orderParam=2):
|
def heal_transparency(timg, tdrawable, samplingRadiusParam=50, orderParam=2):
|
||||||
|
|
||||||
# precondition should be enforced by Gimp according to image modes allowed.
|
# precondition should be enforced by Gimp according to image modes allowed.
|
||||||
if not pdb.gimp_drawable_has_alpha(tdrawable):
|
if not pdb.gimp_drawable_has_alpha(tdrawable):
|
||||||
pdb.gimp_message("The active layer has no alpha channel to heal.")
|
pdb.gimp_message("The active layer has no alpha channel to heal.")
|
||||||
return
|
return
|
||||||
|
|
||||||
pdb.gimp_image_undo_group_start(timg)
|
pdb.gimp_image_undo_group_start(timg)
|
||||||
|
|
||||||
# save selection for later restoration.
|
# save selection for later restoration.
|
||||||
# Saving selection channel makes it active, so we must save and restore the active layer
|
# Saving selection channel makes it active, so we must save and restore the active layer
|
||||||
org_selection = pdb.gimp_selection_save(timg)
|
org_selection = pdb.gimp_selection_save(timg)
|
||||||
pdb.gimp_image_set_active_layer(timg, tdrawable)
|
pdb.gimp_image_set_active_layer(timg, tdrawable)
|
||||||
|
|
||||||
# alpha to selection
|
# alpha to selection
|
||||||
pdb.gimp_image_select_item(timg, CHANNEL_OP_REPLACE, tdrawable)
|
pdb.gimp_selection_layer_alpha(tdrawable)
|
||||||
# Want the transparent, not the opaque.
|
# Want the transparent, not the opaque.
|
||||||
pdb.gimp_selection_invert(timg)
|
pdb.gimp_selection_invert(timg)
|
||||||
# Since transparency was probably anti-aliased (dithered with partial transpancy),
|
# Since transparency was probably anti-aliased (dithered with partial transpancy),
|
||||||
# grow the selection to get past the dithering.
|
# grow the selection to get past the dithering.
|
||||||
pdb.gimp_selection_grow(timg, 1)
|
pdb.gimp_selection_grow(timg, 1)
|
||||||
# Remove the alpha from this layer. IE compose with current background color (often white.)
|
# Remove the alpha from this layer. IE compose with current background color (often white.)
|
||||||
# Resynthesizer won't heal transparent.
|
# Resynthesizer won't heal transparent.
|
||||||
pdb.gimp_layer_flatten(tdrawable)
|
pdb.gimp_layer_flatten(tdrawable)
|
||||||
|
|
||||||
# Call heal selection (not the resynthesizer), which will create a proper corpus.
|
# Call heal selection (not the resynthesizer), which will create a proper corpus.
|
||||||
# 0 = sample from all around
|
# 0 = sample from all around
|
||||||
pdb.python_fu_heal_selection(timg, tdrawable, samplingRadiusParam, 0, orderParam, run_mode=RUN_NONINTERACTIVE)
|
pdb.python_fu_heal_selection(
|
||||||
|
timg, tdrawable, samplingRadiusParam, 0, orderParam, run_mode=RUN_NONINTERACTIVE
|
||||||
# Restore image to initial conditions of user, except for later cleanup.
|
)
|
||||||
|
|
||||||
# restore selection
|
# Restore image to initial conditions of user, except for later cleanup.
|
||||||
pdb.gimp_image_select_item(timg, CHANNEL_OP_REPLACE, org_selection)
|
|
||||||
|
# restore selection
|
||||||
# Clean up (comment out to debug)
|
pdb.gimp_selection_load(org_selection)
|
||||||
pdb.gimp_image_undo_group_end(timg)
|
|
||||||
|
# Clean up (comment out to debug)
|
||||||
|
pdb.gimp_image_undo_group_end(timg)
|
||||||
|
|
||||||
|
|
||||||
register(
|
register(
|
||||||
"python_fu_heal_transparency",
|
"python_fu_heal_transparency",
|
||||||
N_("Removes alpha channel by synthesis. Fill outward for edges, inward for holes."),
|
N_(
|
||||||
"Requires separate resynthesizer plugin.",
|
"Removes alpha channel by synthesis. Fill outward for edges, inward for holes."
|
||||||
"Lloyd Konneker",
|
),
|
||||||
"Copyright 2010 Lloyd Konneker",
|
"Requires separate resynthesizer plugin.",
|
||||||
"2010",
|
"Lloyd Konneker",
|
||||||
N_("Heal transparency..."),
|
"Copyright 2010 Lloyd Konneker",
|
||||||
"RGBA, GRAYA", # !!! Requires an alpha channel to heal
|
"2010",
|
||||||
[
|
N_("Heal transparency..."),
|
||||||
(PF_IMAGE, "image", "Input image", None),
|
"RGBA, GRAYA", # !!! Requires an alpha channel to heal
|
||||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
[
|
||||||
(PF_INT, "samplingRadiusParam", _("Context sampling width (pixels):"), 50),
|
(PF_IMAGE, "image", "Input image", None),
|
||||||
(PF_OPTION, "orderParam", _("Filling order:"), 2, [_("Random"),
|
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||||
_("Inwards towards center"), _("Outwards from center") ])
|
(PF_INT, "samplingRadiusParam", _("Context sampling width (pixels):"), 50),
|
||||||
],
|
(
|
||||||
[],
|
PF_OPTION,
|
||||||
heal_transparency,
|
"orderParam",
|
||||||
menu="<Image>/Filters/Enhance",
|
_("Filling order:"),
|
||||||
domain=("resynthesizer", gimp.locale_directory)
|
2,
|
||||||
)
|
[_("Random"), _("Inwards towards center"), _("Outwards from center")],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
heal_transparency,
|
||||||
|
menu="<Image>/Filters/Enhance",
|
||||||
|
domain=("resynthesizer", gimp.locale_directory),
|
||||||
|
)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,31 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Gimp plugin.
|
Gimp plugin.
|
||||||
Transfer style (color and surface texture) from a source image to the active, target image.
|
Transfer style (color and surface texture) from a source image to the active, target image.
|
||||||
|
|
||||||
Requires resynthesizer plug-in.
|
Requires resynthesizer plug-in.
|
||||||
|
|
||||||
Author:
|
Authors:
|
||||||
lloyd konneker, lkk
|
lloyd konneker, lkk
|
||||||
|
Gabriel Almir, avlye
|
||||||
|
|
||||||
Version:
|
Version:
|
||||||
1.0 lkk 7/15/2010 Initial version. Released to Gimp Registry.
|
1.0 lkk 7/15/2010 Initial version. Released to Gimp Registry.
|
||||||
1.1 lkk 8/1/2010 Unreleased
|
1.1 lkk 8/1/2010 Unreleased
|
||||||
1.2 lkk 8/10/2010
|
1.2 lkk 8/10/2010
|
||||||
|
1.3 avlye 01/10/2021
|
||||||
|
|
||||||
Change log:
|
Change log:
|
||||||
_________________
|
_________________
|
||||||
1.1
|
1.1
|
||||||
Bug: Fixed test of mode variable, since it is a string, needs explicit test for == 1
|
Bug: Fixed test of mode variable, since it is a string, needs explicit test for == 1
|
||||||
Bug: Added remove Selection Mask copy channel in make_grayscale_map
|
Bug: Added remove Selection Mask copy channel in make_grayscale_map
|
||||||
1.2
|
1.2
|
||||||
Changes for new resynthesizer: no need to synchronize, remove alphas
|
Changes for new resynthesizer: no need to synchronize, remove alphas
|
||||||
Fixed improper adjustment of contrast of source: only adjust source map.
|
Fixed improper adjustment of contrast of source: only adjust source map.
|
||||||
|
1.3
|
||||||
|
Minor changes to improve developer experience
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
a quality setting that changes the parameters to resynth
|
a quality setting that changes the parameters to resynth
|
||||||
|
|
@ -93,7 +97,7 @@ _________________
|
||||||
IN: The active image and layer.
|
IN: The active image and layer.
|
||||||
The selection in the active image.
|
The selection in the active image.
|
||||||
The selection in any layers chosen for source.
|
The selection in any layers chosen for source.
|
||||||
OUT: The active image, altered. The source is unaltered.
|
OUT: The active image, altered. The source is unaltered.
|
||||||
Target mode can be altered, but with the implied consent of the user.
|
Target mode can be altered, but with the implied consent of the user.
|
||||||
|
|
||||||
The print stmts go to the console, info to advanced users and debuggers.
|
The print stmts go to the console, info to advanced users and debuggers.
|
||||||
|
|
@ -105,293 +109,314 @@ synchronization of modes
|
||||||
abstracting the settings
|
abstracting the settings
|
||||||
contrast adjustment
|
contrast adjustment
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from gimpfu import *
|
from gimpfu import *
|
||||||
from math import acos
|
from math import acos, pi
|
||||||
|
|
||||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
|
|
||||||
# True if you want to display and retain working, temporary images
|
# True if you want to display and retain working, temporary images
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
def display_debug_image(image) :
|
|
||||||
if debug :
|
def display_debug_image(image):
|
||||||
try:
|
if debug:
|
||||||
pdb.gimp_display_new(image)
|
try:
|
||||||
pdb.gimp_displays_flush()
|
pdb.gimp_display_new(image)
|
||||||
except RuntimeError:
|
pdb.gimp_displays_flush()
|
||||||
pass # if run-mode not interactive, Gimp throws
|
except RuntimeError:
|
||||||
|
pass # if run-mode not interactive, Gimp throws
|
||||||
|
|
||||||
|
|
||||||
def make_grayscale_map(image, drawable):
|
def make_grayscale_map(image, drawable):
|
||||||
'''
|
"""
|
||||||
Make a grayscale copy for a map.
|
Make a grayscale copy for a map.
|
||||||
|
|
||||||
Maps must be same size as their parent image.
|
Maps must be same size as their parent image.
|
||||||
|
|
||||||
If image is already grayscale, return it without copying.
|
If image is already grayscale, return it without copying.
|
||||||
|
|
||||||
Maps don't need a selection, since the resynthesizer looks at parent drawables for the selection.
|
Maps don't need a selection, since the resynthesizer looks at parent drawables for the selection.
|
||||||
'''
|
"""
|
||||||
if pdb.gimp_image_base_type(image) == GRAY :
|
if pdb.gimp_image_base_type(image) == GRAY:
|
||||||
return image, drawable
|
return image, drawable
|
||||||
|
|
||||||
# Save selection, copy entire image, and restore
|
# Save selection, copy entire image, and restore
|
||||||
original_selection = pdb.gimp_selection_save(image)
|
original_selection = pdb.gimp_selection_save(image)
|
||||||
pdb.gimp_selection_all(image) # copy requires selection
|
pdb.gimp_selection_all(image) # copy requires selection
|
||||||
pdb.gimp_edit_copy(drawable)
|
pdb.gimp_edit_copy(drawable)
|
||||||
if original_selection:
|
if original_selection:
|
||||||
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, original_selection) # restore selection in image
|
pdb.gimp_selection_load(original_selection) # restore selection in image
|
||||||
pdb.gimp_image_remove_channel(image, original_selection) # cleanup the copied selection mask
|
pdb.gimp_image_remove_channel(
|
||||||
# !!! Note remove_channel not drawable_delete
|
image, original_selection
|
||||||
|
) # cleanup the copied selection mask
|
||||||
# Make a copy, greyscale
|
# !!! Note remove_channel not drawable_delete
|
||||||
temp_image = pdb.gimp_edit_paste_as_new()
|
|
||||||
pdb.gimp_image_convert_grayscale(temp_image)
|
# Make a copy, greyscale
|
||||||
display_debug_image(temp_image)
|
temp_image = pdb.gimp_edit_paste_as_new()
|
||||||
temp_drawable = pdb.gimp_image_get_active_drawable(temp_image)
|
pdb.gimp_image_convert_grayscale(temp_image)
|
||||||
return temp_image, temp_drawable
|
display_debug_image(temp_image)
|
||||||
|
temp_drawable = pdb.gimp_image_get_active_drawable(temp_image)
|
||||||
|
return temp_image, temp_drawable
|
||||||
|
|
||||||
|
|
||||||
def synchronize_modes(target_image, source_image) :
|
def synchronize_modes(target_image, source_image):
|
||||||
'''
|
"""
|
||||||
User-friendliness:
|
User-friendliness:
|
||||||
If mode of target is not equal to mode of source source, change modes.
|
If mode of target is not equal to mode of source source, change modes.
|
||||||
Resynthesizer requires target and source to be same mode.
|
Resynthesizer requires target and source to be same mode.
|
||||||
Assert target is RGB or GRAY (since is precondition of plugin.)
|
Assert target is RGB or GRAY (since is precondition of plugin.)
|
||||||
UI decision: make this quiet, presume user intends mode change.
|
UI decision: make this quiet, presume user intends mode change.
|
||||||
But don't permanently change mode of source.
|
But don't permanently change mode of source.
|
||||||
Always upgrade GRAY to RGB, not downgrade RGB to GRAY.
|
Always upgrade GRAY to RGB, not downgrade RGB to GRAY.
|
||||||
'''
|
"""
|
||||||
target_mode = pdb.gimp_image_base_type(target_image)
|
target_mode = pdb.gimp_image_base_type(target_image)
|
||||||
source_mode = pdb.gimp_image_base_type(source_image)
|
source_mode = pdb.gimp_image_base_type(source_image)
|
||||||
if target_mode != source_mode :
|
if target_mode != source_mode:
|
||||||
# print("Map style: converted mode\n.")
|
# print("Map style: converted mode\n.")
|
||||||
if target_mode == GRAY:
|
if target_mode == GRAY:
|
||||||
pdb.gimp_image_convert_rgb(target_image)
|
pdb.gimp_image_convert_rgb(target_image)
|
||||||
else : # target is RGB and source is GRAY
|
else: # target is RGB and source is GRAY
|
||||||
# Assert only convert a copy of source,
|
# Assert only convert a copy of source,
|
||||||
# user NEVER intends original source be altered.
|
# user NEVER intends original source be altered.
|
||||||
pdb.gimp_image_convert_rgb(source_image)
|
pdb.gimp_image_convert_rgb(source_image)
|
||||||
|
|
||||||
'''
|
|
||||||
Not used
|
|
||||||
'''
|
|
||||||
"""
|
|
||||||
def synchronize_alphas(target_drawable, source_drawable) :
|
|
||||||
'''
|
|
||||||
User-friendliness:
|
|
||||||
If source has alpha and target doesn't, remove or add alpha to source.
|
|
||||||
Do this without user dialog since it is done on copies, and really, the alpha doesn't matter.
|
|
||||||
'''
|
|
||||||
if pdb.gimp_drawable_has_alpha(source_drawable) :
|
|
||||||
if not pdb.gimp_drawable_has_alpha(target_drawable) :
|
|
||||||
# Should never get here, since removed alpha from source_drawable copy earlier
|
|
||||||
print "Adding alpha channel to target image since style source image has alpha."
|
|
||||||
pdb.gimp_layer_add_alpha (target_drawable)
|
|
||||||
else: # source has no alpha
|
|
||||||
if pdb.gimp_drawable_has_alpha(target_drawable) :
|
|
||||||
print "Map style: Adding alpha channel to style source image copy since target image has alpha."
|
|
||||||
pdb.gimp_layer_add_alpha (source_drawable)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def copy_selection_to_image(drawable) :
|
|
||||||
'''
|
|
||||||
If image has a selection, copy selection to new image, and prepare it for resynthesizer,
|
|
||||||
else return a copy of the entire source image.
|
|
||||||
This is called for the source image, where it helps performance to reduce size and flatten.
|
|
||||||
'''
|
|
||||||
image = pdb.gimp_drawable_get_image(drawable)
|
|
||||||
|
|
||||||
# copy selection or whole image
|
|
||||||
pdb.gimp_edit_copy(drawable)
|
|
||||||
image_copy = pdb.gimp_edit_paste_as_new()
|
|
||||||
# Activate layer, and remove alpha channel
|
|
||||||
pdb.gimp_image_flatten(image_copy)
|
|
||||||
layer_copy = pdb.gimp_image_get_active_layer(image_copy)
|
|
||||||
# In earlier version, futzed with selection to deal with transparencey
|
|
||||||
display_debug_image(image_copy)
|
|
||||||
return image_copy, layer_copy
|
|
||||||
|
|
||||||
|
|
||||||
def synchronize_contrast( drawable, source_drawable, percent_transfer) :
|
|
||||||
'''
|
|
||||||
Adjust contrast of source, to match target.
|
|
||||||
Adjustment depends inversely on percent_transfer.
|
|
||||||
Very crude histogram matching.
|
|
||||||
'''
|
|
||||||
# histogram upper half: typical mean is 191 (3/4*255). Skew of mean towards 255 means high contrast.
|
|
||||||
mean, deviation, median, pixels, count, percentile = pdb.gimp_histogram(drawable, HISTOGRAM_VALUE, 128, 255)
|
|
||||||
source_mean, source_deviation, source_median, pixels, count, percentile = pdb.gimp_histogram(
|
|
||||||
source_drawable, HISTOGRAM_VALUE, 128, 255)
|
|
||||||
# if mean > source_mean: # target has more contrast than source
|
|
||||||
# Adjust contrast of source.
|
|
||||||
# Inversely proportional to percent transfer.
|
|
||||||
# 2.5 is from experimentation with gimp_brightness_contrast which seems linear in its effect.
|
|
||||||
contrast_control = (mean - source_mean) * 2.5 * (1 - (percent_transfer / 100))
|
|
||||||
# clamp to valid range (above formula is lazy, ad hoc)
|
|
||||||
if contrast_control < -127: contrast_control = -127
|
|
||||||
if contrast_control > 127: contrast_control = 127
|
|
||||||
pdb.gimp_brightness_contrast(source_drawable, 0, contrast_control)
|
|
||||||
# For experimentation, print new values
|
|
||||||
source_mean, source_deviation, source_median, pixels, count, percentile = pdb.gimp_histogram(
|
|
||||||
source_drawable, HISTOGRAM_VALUE, 128, 255)
|
|
||||||
# print "Map style: Source contrast changed by ", contrast_control
|
|
||||||
# print "Map style: Target and source upper half histogram means", mean, source_mean
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_map_weight(percent_transfer) :
|
def copy_selection_to_image(drawable):
|
||||||
'''
|
"""
|
||||||
This is a GUI design discussion.
|
If image has a selection, copy selection to new image, and prepare it for resynthesizer,
|
||||||
Transform percent_transfer to map_weight parameter to resynthesizer.
|
else return a copy of the entire source image.
|
||||||
For resynthesizer:
|
This is called for the source image, where it helps performance to reduce size and flatten.
|
||||||
map weight 0 means copy source to target, meaning ALL style.
|
"""
|
||||||
map weight 0.5 means just a grainy transfer of style (as little as is possible.)
|
image = pdb.gimp_drawable_get_image(drawable)
|
||||||
Transform from a linear percent GUI, because user more comfortable than with a ratio [.5, 0]
|
|
||||||
which is backwards to the usual *less on the left*.
|
|
||||||
By experiment, a sinusoid gives good results for linearizing the non-linear map_weight control.
|
|
||||||
'''
|
|
||||||
return acos((percent_transfer/100)*2 -1)/(2*3.14)
|
|
||||||
|
|
||||||
|
|
||||||
def transfer_style(image, drawable, source_drawable, percent_transfer, map_mode ):
|
# copy selection or whole image
|
||||||
'''
|
pdb.gimp_edit_copy(drawable)
|
||||||
Main body of plugin to transfer style from one image to another.
|
image_copy = pdb.gimp_edit_paste_as_new()
|
||||||
|
# Activate layer, and remove alpha channel
|
||||||
!!! Note map_mode is type string, "if map_mode:" will not work.
|
pdb.gimp_image_flatten(image_copy)
|
||||||
'''
|
layer_copy = pdb.gimp_image_get_active_layer(image_copy)
|
||||||
|
# In earlier version, futzed with selection to deal with transparencey
|
||||||
pdb.gimp_image_undo_group_start(image)
|
display_debug_image(image_copy)
|
||||||
|
return image_copy, layer_copy
|
||||||
# Get image of source drawable
|
|
||||||
source_image = pdb.gimp_drawable_get_image(source_drawable)
|
|
||||||
|
def synchronize_contrast(drawable, source_drawable, percent_transfer):
|
||||||
'''
|
"""
|
||||||
|
Adjust contrast of source, to match target.
|
||||||
|
Adjustment depends inversely on percent_transfer.
|
||||||
|
Very crude histogram matching.
|
||||||
|
"""
|
||||||
|
# histogram upper half: typical mean is 191 (3/4*255). Skew of mean towards 255 means high contrast.
|
||||||
|
mean, deviation, median, pixels, count, percentile = pdb.gimp_histogram(
|
||||||
|
drawable, HISTOGRAM_VALUE, 128, 255
|
||||||
|
)
|
||||||
|
(
|
||||||
|
source_mean,
|
||||||
|
source_deviation,
|
||||||
|
source_median,
|
||||||
|
pixels,
|
||||||
|
count,
|
||||||
|
percentile,
|
||||||
|
) = pdb.gimp_histogram(source_drawable, HISTOGRAM_VALUE, 128, 255)
|
||||||
|
|
||||||
|
# if mean > source_mean: # target has more contrast than source
|
||||||
|
# Adjust contrast of source.
|
||||||
|
# Inversely proportional to percent transfer.
|
||||||
|
# 2.5 is from experimentation with gimp_brightness_contrast which seems linear in its effect.
|
||||||
|
contrast_control = (mean - source_mean) * 2.5 * (1 - (percent_transfer / 100))
|
||||||
|
|
||||||
|
# clamp to valid range (above formula is lazy, ad hoc)
|
||||||
|
contrast_control = max(min(contrast_control, 127), -127)
|
||||||
|
|
||||||
|
pdb.gimp_brightness_contrast(source_drawable, 0, contrast_control)
|
||||||
|
|
||||||
|
# For experimentation, print new values
|
||||||
|
(
|
||||||
|
source_mean,
|
||||||
|
source_deviation,
|
||||||
|
source_median,
|
||||||
|
pixels,
|
||||||
|
count,
|
||||||
|
percentile,
|
||||||
|
) = pdb.gimp_histogram(source_drawable, HISTOGRAM_VALUE, 128, 255)
|
||||||
|
|
||||||
|
# print "Map style: Source contrast changed by ", contrast_control
|
||||||
|
# print "Map style: Target and source upper half histogram means", mean, source_mean
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_map_weight(percent_transfer):
|
||||||
|
"""
|
||||||
|
This is a GUI design discussion.
|
||||||
|
Transform percent_transfer to map_weight parameter to resynthesizer.
|
||||||
|
For resynthesizer:
|
||||||
|
map weight 0 means copy source to target, meaning ALL style.
|
||||||
|
map weight 0.5 means just a grainy transfer of style (as little as is possible.)
|
||||||
|
Transform from a linear percent GUI, because user more comfortable than with a ratio [.5, 0]
|
||||||
|
which is backwards to the usual *less on the left*.
|
||||||
|
By experiment, a sinusoid gives good results for linearizing the non-linear map_weight control.
|
||||||
|
"""
|
||||||
|
return acos((percent_transfer / 100) * 2 - 1) / (2 * pi)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_style(image, drawable, source_drawable, percent_transfer, map_mode):
|
||||||
|
"""
|
||||||
|
Main body of plugin to transfer style from one image to another.
|
||||||
|
|
||||||
|
!!! Note map_mode is type string, "if map_mode:" will not work.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pdb.gimp_image_undo_group_start(image)
|
||||||
|
|
||||||
|
# Get image of source drawable
|
||||||
|
source_image = pdb.gimp_drawable_get_image(source_drawable)
|
||||||
|
|
||||||
|
"""
|
||||||
User-friendliness.
|
User-friendliness.
|
||||||
Note the drawable chooser widget in Pygimp does not allow us to prefilter INDEXED mode.
|
Note the drawable chooser widget in Pygimp does not allow us to prefilter INDEXED mode.
|
||||||
So check here and give a warning.
|
So check here and give a warning.
|
||||||
'''
|
"""
|
||||||
# These are the originals base types, and this plugin might change the base types
|
# These are the originals base types, and this plugin might change the base types
|
||||||
original_source_base_type = pdb.gimp_image_base_type(source_image)
|
original_source_base_type = pdb.gimp_image_base_type(source_image)
|
||||||
original_target_base_type = pdb.gimp_image_base_type(image)
|
original_target_base_type = pdb.gimp_image_base_type(image)
|
||||||
|
|
||||||
if original_source_base_type == INDEXED :
|
|
||||||
pdb.gimp_message(_("The style source cannot be of mode INDEXED"));
|
|
||||||
return
|
|
||||||
|
|
||||||
if image == source_image and drawable == source_drawable:
|
if original_source_base_type == INDEXED:
|
||||||
is_source_copy = False
|
pdb.gimp_message(_("The style source cannot be of mode INDEXED"))
|
||||||
'''
|
return
|
||||||
If source is same as target,
|
|
||||||
|
if image == source_image and drawable == source_drawable:
|
||||||
|
is_source_copy = False
|
||||||
|
"""
|
||||||
|
If source is same as target,
|
||||||
then the old resynthesizer required a selection (engine used inverse selection for corpus).
|
then the old resynthesizer required a selection (engine used inverse selection for corpus).
|
||||||
New resynthesizer doesn't need a selection.
|
New resynthesizer doesn't need a selection.
|
||||||
If source same as target, effect is similar to a blur.
|
If source same as target, effect is similar to a blur.
|
||||||
'''
|
"""
|
||||||
# assert modes and alphas are same (since they are same layer!)
|
# assert modes and alphas are same (since they are same layer!)
|
||||||
else: # target layer is not the source layer (source could be a copy of target, but effect is none)
|
else: # target layer is not the source layer (source could be a copy of target, but effect is none)
|
||||||
# Copy source always, for performance, and for possible mode change.
|
# Copy source always, for performance, and for possible mode change.
|
||||||
is_source_copy = True
|
is_source_copy = True
|
||||||
source_image, source_drawable = copy_selection_to_image(source_drawable)
|
source_image, source_drawable = copy_selection_to_image(source_drawable)
|
||||||
|
|
||||||
# Futz with modes if necessary.
|
|
||||||
synchronize_modes(image, source_image)
|
|
||||||
|
|
||||||
'''
|
|
||||||
Old resythesizer required both images to have alpha, or neither.
|
|
||||||
synchronize_alphas( drawable, source_drawable)
|
|
||||||
'''
|
|
||||||
|
|
||||||
'''
|
# Futz with modes if necessary.
|
||||||
|
synchronize_modes(image, source_image)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Old resythesizer required both images to have alpha, or neither.
|
||||||
|
synchronize_alphas( drawable, source_drawable)
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
TODO For performance, if there is a selection in target, it would be better to copy
|
TODO For performance, if there is a selection in target, it would be better to copy
|
||||||
selection to a new layer, and later merge it back (since resynthesizer engine reads
|
selection to a new layer, and later merge it back (since resynthesizer engine reads
|
||||||
entire target into memory. Low priority since rarely does user make a selection in target.
|
entire target into memory. Low priority since rarely does user make a selection in target.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
'''
|
"""
|
||||||
!!! Note this plugin always sends maps to the resynthesizer,
|
!!! Note this plugin always sends maps to the resynthesizer,
|
||||||
and the "percent transfer" setting is always effective.
|
and the "percent transfer" setting is always effective.
|
||||||
However, maps may not be separate,copied images unless converted to grayscale.
|
However, maps may not be separate,copied images unless converted to grayscale.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# Copy and reduce maps to grayscale: at the option of the user
|
|
||||||
# !!! Or if the target was GRAY and source is RGB, in which case maps give a better result.
|
|
||||||
# Note that if the target was GRAY, we already upgraded it to RGB.
|
|
||||||
if map_mode == 1 or (original_source_base_type == RGB and original_target_base_type == GRAY) :
|
|
||||||
# print "Map style: source mode: ", original_source_base_type, " target mode: ", original_target_base_type
|
|
||||||
# print "Map style: Converting maps to grayscale"
|
|
||||||
# Convert mode, but in new temp image and drawable
|
|
||||||
target_map_image, target_map_drawable = make_grayscale_map(image, drawable)
|
|
||||||
source_map_image, source_map_drawable = make_grayscale_map(source_image, source_drawable)
|
|
||||||
|
|
||||||
target_map = target_map_drawable
|
|
||||||
source_map = source_map_drawable
|
|
||||||
# later, delete temp images
|
|
||||||
|
|
||||||
# User control: adjust contrast of source_map as a function of percent transfer
|
|
||||||
# Hard to explain why, but experimentation shows result more like user expectation.
|
|
||||||
# TODO This could be improved.
|
|
||||||
# !!! Don't change the original source, only a temporary map we created
|
|
||||||
synchronize_contrast( drawable, source_map, percent_transfer)
|
|
||||||
else :
|
|
||||||
# !!! Maps ARE the target and source, not copies
|
|
||||||
source_map = source_drawable
|
|
||||||
target_map = drawable
|
|
||||||
|
|
||||||
|
# Copy and reduce maps to grayscale: at the option of the user
|
||||||
'''
|
# !!! Or if the target was GRAY and source is RGB, in which case maps give a better result.
|
||||||
|
# Note that if the target was GRAY, we already upgraded it to RGB.
|
||||||
|
if map_mode == 1 or (
|
||||||
|
original_source_base_type == RGB and original_target_base_type == GRAY
|
||||||
|
):
|
||||||
|
# print "Map style: source mode: ", original_source_base_type, " target mode: ", original_target_base_type
|
||||||
|
# print "Map style: Converting maps to grayscale"
|
||||||
|
# Convert mode, but in new temp image and drawable
|
||||||
|
target_map_image, target_map_drawable = make_grayscale_map(image, drawable)
|
||||||
|
source_map_image, source_map_drawable = make_grayscale_map(
|
||||||
|
source_image, source_drawable
|
||||||
|
)
|
||||||
|
|
||||||
|
target_map = target_map_drawable
|
||||||
|
source_map = source_map_drawable
|
||||||
|
# later, delete temp images
|
||||||
|
|
||||||
|
# User control: adjust contrast of source_map as a function of percent transfer
|
||||||
|
# Hard to explain why, but experimentation shows result more like user expectation.
|
||||||
|
# TODO This could be improved.
|
||||||
|
# !!! Don't change the original source, only a temporary map we created
|
||||||
|
synchronize_contrast(drawable, source_map, percent_transfer)
|
||||||
|
else:
|
||||||
|
# !!! Maps ARE the target and source, not copies
|
||||||
|
source_map = source_drawable
|
||||||
|
target_map = drawable
|
||||||
|
|
||||||
|
"""
|
||||||
Parameters to resynthesizer:
|
Parameters to resynthesizer:
|
||||||
|
|
||||||
htile and vtile = 1 since it reduces artifacts around edge
|
htile and vtile = 1 since it reduces artifacts around edge
|
||||||
|
|
||||||
map_weight I linearize since easier on users than an exponential
|
map_weight I linearize since easier on users than an exponential
|
||||||
|
|
||||||
use_border = 1 since there might be a selection and context (outside target).
|
use_border = 1 since there might be a selection and context (outside target).
|
||||||
|
|
||||||
9 neighbors (a 3x3 patch) and 200 tries for speed
|
9 neighbors (a 3x3 patch) and 200 tries for speed
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
map_weight = calculate_map_weight(percent_transfer)
|
map_weight = calculate_map_weight(percent_transfer)
|
||||||
|
|
||||||
# !!! This is for version of resynthesizer, with an uninverted selection
|
# !!! This is for version of resynthesizer, with an uninverted selection
|
||||||
pdb.plug_in_resynthesizer(image, drawable, 1, 1, 1, source_drawable.ID, source_map.ID, target_map.ID, map_weight, 0.117, 9, 200)
|
pdb.plug_in_resynthesizer(
|
||||||
|
image,
|
||||||
# Clean up.
|
drawable,
|
||||||
# Delete working images: separate map images and copy of source image
|
1,
|
||||||
if not debug:
|
1,
|
||||||
if map_mode == 1: # if made working map images
|
1,
|
||||||
pdb.gimp_image_delete(target_map_image)
|
source_drawable.ID,
|
||||||
pdb.gimp_image_delete(source_map_image)
|
source_map.ID,
|
||||||
if is_source_copy: # if created a copy earlier
|
target_map.ID,
|
||||||
pdb.gimp_image_delete(source_image)
|
map_weight,
|
||||||
|
0.117,
|
||||||
pdb.gimp_image_undo_group_end(image)
|
9,
|
||||||
pdb.gimp_displays_flush()
|
200,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clean up.
|
||||||
|
# Delete working images: separate map images and copy of source image
|
||||||
|
if not debug:
|
||||||
|
if map_mode == 1: # if made working map images
|
||||||
|
pdb.gimp_image_delete(target_map_image)
|
||||||
|
pdb.gimp_image_delete(source_map_image)
|
||||||
|
if is_source_copy: # if created a copy earlier
|
||||||
|
pdb.gimp_image_delete(source_image)
|
||||||
|
|
||||||
|
pdb.gimp_image_undo_group_end(image)
|
||||||
|
pdb.gimp_displays_flush()
|
||||||
|
|
||||||
|
|
||||||
register(
|
register(
|
||||||
"python_fu_map_style",
|
"python_fu_map_style",
|
||||||
N_("Transfer style (color and surface) from a chosen source to the active layer. "),
|
N_("Transfer style (color and surface) from a chosen source to the active layer. "),
|
||||||
"Transforms image using art media and style from another image. Maps or synthesizes texture or theme from one image onto another. Requires separate resynthesizer plugin.",
|
"Transforms image using art media and style from another image. Maps or synthesizes texture or theme from one image onto another. Requires separate resynthesizer plugin.",
|
||||||
"Lloyd Konneker (bootch nc.rr.com)",
|
"Lloyd Konneker (bootch nc.rr.com)",
|
||||||
"Copyright 2010 Lloyd Konneker",
|
"Copyright 2010 Lloyd Konneker",
|
||||||
"2010",
|
"2010",
|
||||||
N_("Style..."),
|
N_("Style..."),
|
||||||
"RGB*, GRAY*",
|
"RGB*, GRAY*",
|
||||||
[
|
[
|
||||||
(PF_IMAGE, "image", "Input image", None),
|
(PF_IMAGE, "image", "Input image", None),
|
||||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||||
(PF_DRAWABLE, "source_drawable", _("Source of style:"), None),
|
(PF_DRAWABLE, "source_drawable", _("Source of style:"), None),
|
||||||
(PF_SLIDER, "percent_transfer", _("Percent transfer:"), 0, (10, 90, 10.0)),
|
(PF_SLIDER, "percent_transfer", _("Percent transfer:"), 0, (10, 90, 10.0)),
|
||||||
(PF_RADIO, "map_mode", _("Map by:"), 0, ((_("Color and brightness"), 0),(_("Brightness only"),1)))
|
(
|
||||||
],
|
PF_RADIO,
|
||||||
[],
|
"map_mode",
|
||||||
transfer_style,
|
_("Map by:"),
|
||||||
menu="<Image>/Filters/Map",
|
0,
|
||||||
domain=("resynthesizer", gimp.locale_directory)
|
((_("Color and brightness"), 0), (_("Brightness only"), 1)),
|
||||||
)
|
),
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
transfer_style,
|
||||||
|
menu="<Image>/Filters/Map",
|
||||||
|
domain=("resynthesizer", gimp.locale_directory),
|
||||||
|
)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,14 @@ Sometimes called rendering a texture.
|
||||||
|
|
||||||
Requires resynthesizer plug-in.
|
Requires resynthesizer plug-in.
|
||||||
|
|
||||||
Author:
|
Authors:
|
||||||
lloyd konneker, lkk, bootch at nc.rr.com
|
lloyd konneker, lkk, bootch at nc.rr.com
|
||||||
|
Gabriel Almir, avlye, avlye.me
|
||||||
|
|
||||||
Version:
|
Version:
|
||||||
1.0 lkk 7/15/2010 Initial version
|
1.0 lkk 7/15/2010 Initial version
|
||||||
1.1 lkk 4/10/2011 Fixed a bug with layer types impacting greyscale images.
|
1.1 lkk 4/10/2011 Fixed a bug with layer types impacting greyscale images.
|
||||||
|
1.3 avlye 01/10/2020 Minor changes for improving developer experience
|
||||||
|
|
||||||
License:
|
License:
|
||||||
|
|
||||||
|
|
@ -48,9 +50,9 @@ The continuum of randomness versus speed:
|
||||||
- Filte.Render.Texture an entire image is slower but seamless and moderately irregular.
|
- Filte.Render.Texture an entire image is slower but seamless and moderately irregular.
|
||||||
- Edit.Fill with resynthesized pattern is slowest but seamless and highly irregular, unpatterned.
|
- Edit.Fill with resynthesized pattern is slowest but seamless and highly irregular, unpatterned.
|
||||||
|
|
||||||
This filter is not tiling (instead resynthesizing) but makes
|
This filter is not tiling (instead resynthesizing) but makes
|
||||||
an image that you can then use to tile with especially if
|
an image that you can then use to tile with especially if
|
||||||
you choose the option to make the edges suitable for tiling.
|
you choose the option to make the edges suitable for tiling.
|
||||||
|
|
||||||
IN: The selection (or the entire active drawable) is the source of texture and is not changed.
|
IN: The selection (or the entire active drawable) is the source of texture and is not changed.
|
||||||
OUT New image, possibly resized canvas, same scale and resolution.
|
OUT New image, possibly resized canvas, same scale and resolution.
|
||||||
|
|
@ -64,11 +66,12 @@ gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
|
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
|
|
||||||
def new_resized_image(image, resize_ratio):
|
def new_resized_image(image, resize_ratio):
|
||||||
# Create new image resized by a ratio from *selection* in the old image
|
# Create new image resized by a ratio from *selection* in the old image
|
||||||
|
|
||||||
(is_selection, ulx, uly, lrx, lry) = pdb.gimp_selection_bounds(image)
|
(is_selection, ulx, uly, lrx, lry) = pdb.gimp_selection_bounds(image)
|
||||||
|
|
||||||
if not is_selection :
|
if not is_selection :
|
||||||
# Resynthesizer will use the entire source image as corpus.
|
# Resynthesizer will use the entire source image as corpus.
|
||||||
# Resize new image in proportion to entire source image.
|
# Resize new image in proportion to entire source image.
|
||||||
|
|
@ -79,99 +82,98 @@ def new_resized_image(image, resize_ratio):
|
||||||
# Resize new image in proportion to selection in source
|
# Resize new image in proportion to selection in source
|
||||||
new_width = int((lrx - ulx) * resize_ratio)
|
new_width = int((lrx - ulx) * resize_ratio)
|
||||||
new_height = int((lry - uly) * resize_ratio)
|
new_height = int((lry - uly) * resize_ratio)
|
||||||
|
|
||||||
new_basetype = pdb.gimp_image_base_type(image) # same as source
|
new_basetype = pdb.gimp_image_base_type(image) # same as source
|
||||||
new_layertype = pdb.gimp_drawable_type(pdb.gimp_image_get_active_layer(image))
|
new_layertype = pdb.gimp_drawable_type(pdb.gimp_image_get_active_layer(image))
|
||||||
new_image = pdb.gimp_image_new(new_width, new_height, new_basetype)
|
new_image = pdb.gimp_image_new(new_width, new_height, new_basetype)
|
||||||
|
|
||||||
# !!! Note that gimp_layer_new wants a layer type, not an image basetype
|
# !!! Note that gimp_layer_new wants a layer type, not an image basetype
|
||||||
new_drawable = pdb.gimp_layer_new(new_image, new_width, new_height,
|
new_drawable = pdb.gimp_layer_new(new_image, new_width, new_height,
|
||||||
new_layertype, "Texture", 100, NORMAL_MODE)
|
new_layertype, "Texture", 100, NORMAL_MODE)
|
||||||
pdb.gimp_image_add_layer(new_image, new_drawable, 0)
|
pdb.gimp_image_add_layer(new_image, new_drawable, 0)
|
||||||
|
|
||||||
return new_image, new_drawable
|
return new_image, new_drawable
|
||||||
|
|
||||||
|
|
||||||
def display_image(image):
|
def display_image(image):
|
||||||
try:
|
try:
|
||||||
gimp.Display(image)
|
gimp.Display(image)
|
||||||
except gimp.error:
|
except gimp.error:
|
||||||
pass # If runmode is NONINTERACTIVE, expect gimp_display_new() to fail
|
pass # If runmode is NONINTERACTIVE, expect gimp_display_new() to fail
|
||||||
|
|
||||||
gimp.displays_flush()
|
gimp.displays_flush()
|
||||||
|
|
||||||
|
|
||||||
def render_texture(image, drawable, resize_ratio=2, make_tile=0):
|
def render_texture(image, drawable, resize_ratio=2, make_tile=0):
|
||||||
'''
|
'''
|
||||||
Create a randomized texture image from the selection.
|
Create a randomized texture image from the selection.
|
||||||
The image can be suited for further, seamless tiling.
|
The image can be suited for further, seamless tiling.
|
||||||
The image is same scale and resolution but resized from the selection.
|
The image is same scale and resolution but resized from the selection.
|
||||||
Not undoable, no changes to the source (you can just delete the new image.)
|
Not undoable, no changes to the source (you can just delete the new image.)
|
||||||
|
|
||||||
A selection in the source image is optional.
|
A selection in the source image is optional.
|
||||||
If there is no selection, the resynthesizer will use the entire source image.
|
If there is no selection, the resynthesizer will use the entire source image.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Its all or nothing, user must delete new image if not happy.
|
# Its all or nothing, user must delete new image if not happy.
|
||||||
pdb.gimp_image_undo_disable(image)
|
pdb.gimp_image_undo_disable(image)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Create new image, optionally resized, and display for it.
|
Create new image, optionally resized, and display for it.
|
||||||
'''
|
'''
|
||||||
new_image, new_drawable = new_resized_image(image, resize_ratio)
|
new_image, new_drawable = new_resized_image(image, resize_ratio)
|
||||||
pdb.gimp_image_undo_disable(new_image)
|
pdb.gimp_image_undo_disable(new_image)
|
||||||
|
|
||||||
if not new_drawable:
|
if not new_drawable:
|
||||||
raise RuntimeError, "Failed create layer."
|
raise RuntimeError, "Failed create layer."
|
||||||
|
|
||||||
# If using new resynthesizer with animation for debugging
|
# If using new resynthesizer with animation for debugging
|
||||||
if debug:
|
if debug:
|
||||||
display_image(new_image)
|
display_image(new_image)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
copy original into temp and crop it to the selection to save memory in resynthesizer
|
copy original into temp and crop it to the selection to save memory in resynthesizer
|
||||||
'''
|
'''
|
||||||
temp_image = pdb.gimp_image_duplicate(image)
|
temp_image = pdb.gimp_image_duplicate(image)
|
||||||
|
|
||||||
if not temp_image:
|
if not temp_image:
|
||||||
raise RuntimeError, "Failed duplicate image"
|
raise RuntimeError, "Failed duplicate image"
|
||||||
|
|
||||||
# Get bounds, offset of selection
|
# Get bounds, offset of selection
|
||||||
(is_selection, ulx, uly, lrx, lry) = pdb.gimp_selection_bounds(image)
|
(is_selection, ulx, uly, lrx, lry) = pdb.gimp_selection_bounds(image)
|
||||||
if not is_selection :
|
|
||||||
# No need to crop. Resynthesizer will use all if no selection.
|
if is_selection:
|
||||||
pass
|
|
||||||
else :
|
|
||||||
pdb.gimp_image_crop(temp_image, lrx - ulx, lry - uly, ulx, uly)
|
pdb.gimp_image_crop(temp_image, lrx - ulx, lry - uly, ulx, uly)
|
||||||
|
|
||||||
# Don't flatten because it turns transparency to background (white usually)
|
# Don't flatten because it turns transparency to background (white usually)
|
||||||
work_layer = pdb.gimp_image_get_active_layer(temp_image)
|
work_layer = pdb.gimp_image_get_active_layer(temp_image)
|
||||||
|
|
||||||
if not work_layer:
|
if not work_layer:
|
||||||
raise RuntimeError, "Failed get active layer"
|
raise RuntimeError, "Failed get active layer"
|
||||||
|
|
||||||
# Insure the selection is all (not necessary, resynthesizer will use all if no selection.)
|
# Insure the selection is all (not necessary, resynthesizer will use all if no selection.)
|
||||||
pdb.gimp_selection_all(temp_image)
|
pdb.gimp_selection_all(temp_image)
|
||||||
|
|
||||||
# Settings for making edges suitable for seamless tiling afterwards.
|
# Settings for making edges suitable for seamless tiling afterwards.
|
||||||
# That is what these settings mean in the resynthesizer:
|
# That is what these settings mean in the resynthesizer:
|
||||||
# wrap context probes in the target so that edges of target will be suitable for seamless tiling.
|
# wrap context probes in the target so that edges of target will be suitable for seamless tiling.
|
||||||
# I.E. treat the target as a sphere when matching.
|
# I.E. treat the target as a sphere when matching.
|
||||||
if make_tile :
|
tile = (1, 1) if make_tile else (0, 0)
|
||||||
htile = 1
|
|
||||||
vtile = 1
|
|
||||||
else :
|
|
||||||
htile = 0
|
|
||||||
vtile = 0
|
|
||||||
|
|
||||||
# Call resynthesizer
|
# Call resynthesizer
|
||||||
# use_border is moot since there is no context (outside the target) in the newImage.
|
# use_border is moot since there is no context (outside the target) in the newImage.
|
||||||
# The target is the entire new image, the source is the cropped copy of the selection.
|
# The target is the entire new image, the source is the cropped copy of the selection.
|
||||||
#
|
#
|
||||||
# 9 neighbors (a 3x3 patch) and 200 tries for speed, since new image is probably large
|
# 9 neighbors (a 3x3 patch) and 200 tries for speed, since new image is probably large
|
||||||
# and source is probably natural (fractal), where quality is not important.
|
# and source is probably natural (fractal), where quality is not important.
|
||||||
|
|
||||||
# For version of resynthesizer with uninverted selection
|
# For version of resynthesizer with uninverted selection
|
||||||
# !!! Pass -1 for ID of no layer, not None
|
# !!! Pass -1 for ID of no layer, not None
|
||||||
pdb.plug_in_resynthesizer(new_image, new_drawable, htile, vtile, 0, work_layer.ID, -1, -1, 0.0, 0.117, 9, 200)
|
pdb.plug_in_resynthesizer(new_image, new_drawable, tile[0], tile[1], 0, work_layer.ID, -1, -1, 0.0, 0.117, 9, 200)
|
||||||
|
|
||||||
display_image(new_image)
|
display_image(new_image)
|
||||||
|
|
||||||
# Clean up.
|
# Clean up.
|
||||||
pdb.gimp_image_delete(temp_image)
|
pdb.gimp_image_delete(temp_image)
|
||||||
pdb.gimp_image_undo_enable(image)
|
pdb.gimp_image_undo_enable(image)
|
||||||
pdb.gimp_image_undo_enable(new_image)
|
pdb.gimp_image_undo_enable(new_image)
|
||||||
|
|
@ -201,4 +203,3 @@ register(
|
||||||
)
|
)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,22 +33,22 @@ gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
def plugin_main(image, drawable, scale_factor):
|
def plugin_main(image, drawable, scale_factor):
|
||||||
'''
|
'''
|
||||||
Algorithm:
|
Algorithm:
|
||||||
|
|
||||||
Scale image up.
|
Scale image up.
|
||||||
Resynthesize with:
|
Resynthesize with:
|
||||||
corpus = original size image
|
corpus = original size image
|
||||||
in map = original size image but scaled up and down to blur
|
in map = original size image but scaled up and down to blur
|
||||||
out map = scaled up image
|
out map = scaled up image
|
||||||
|
|
||||||
This restores the detail that scaling up looses.
|
This restores the detail that scaling up looses.
|
||||||
It maintains the aspect ratio of all image features.
|
It maintains the aspect ratio of all image features.
|
||||||
|
|
||||||
Unlike the original smart-enlarge.scm, this alters the original image.
|
Unlike the original smart-enlarge.scm, this alters the original image.
|
||||||
|
|
||||||
original did not accept an alpha channel
|
original did not accept an alpha channel
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
temp_image1 = pdb.gimp_image_duplicate(image) # duplicate for in map
|
temp_image1 = pdb.gimp_image_duplicate(image) # duplicate for in map
|
||||||
if not temp_image1:
|
if not temp_image1:
|
||||||
raise RuntimeError, "Failed duplicate image"
|
raise RuntimeError, "Failed duplicate image"
|
||||||
|
|
@ -67,7 +67,7 @@ def plugin_main(image, drawable, scale_factor):
|
||||||
height = pdb.gimp_drawable_height(drawable)
|
height = pdb.gimp_drawable_height(drawable)
|
||||||
pdb.gimp_image_scale(temp_image1, width/scale_factor, height/scale_factor)
|
pdb.gimp_image_scale(temp_image1, width/scale_factor, height/scale_factor)
|
||||||
pdb.gimp_image_scale(temp_image1, width, height)
|
pdb.gimp_image_scale(temp_image1, width, height)
|
||||||
|
|
||||||
# scale up the image
|
# scale up the image
|
||||||
pdb.gimp_image_scale(image, width * scale_factor, height*scale_factor)
|
pdb.gimp_image_scale(image, width * scale_factor, height*scale_factor)
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ def plugin_main(image, drawable, scale_factor):
|
||||||
temp_layer2.ID, # corpus
|
temp_layer2.ID, # corpus
|
||||||
temp_layer1.ID, # input map
|
temp_layer1.ID, # input map
|
||||||
drawable.ID, # output map is scaled up original itself
|
drawable.ID, # output map is scaled up original itself
|
||||||
1.0, 0.117, 8, 500)
|
1.0, 0.117, 8, 500)
|
||||||
|
|
||||||
pdb.gimp_image_delete(temp_image1)
|
pdb.gimp_image_delete(temp_image1)
|
||||||
pdb.gimp_image_delete(temp_image2)
|
pdb.gimp_image_delete(temp_image2)
|
||||||
|
|
@ -108,5 +108,5 @@ if __name__ == "__main__" :
|
||||||
)
|
)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Gimp plugin "Fill with pattern seamless..."
|
Gimp plugin "Fill with pattern seamless..."
|
||||||
Front end to the resynthesizer plugin to make a seamless fill.
|
Front end to the resynthesizer plugin to make a seamless fill.
|
||||||
|
|
||||||
|
|
@ -28,91 +28,103 @@ GNU General Public License for more details.
|
||||||
The GNU Public License is available at
|
The GNU Public License is available at
|
||||||
http://www.gnu.org/copyleft/gpl.html
|
http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from gimpfu import *
|
from gimpfu import *
|
||||||
|
|
||||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True);
|
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
|
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
|
|
||||||
def layer_from_pattern(image, pattern):
|
def layer_from_pattern(image, pattern):
|
||||||
'''
|
"""
|
||||||
Create a new image and layer having the same size as a pattern.
|
Create a new image and layer having the same size as a pattern.
|
||||||
'''
|
"""
|
||||||
new_basetype = pdb.gimp_image_base_type(image) # same as source
|
new_basetype = pdb.gimp_image_base_type(image) # same as source
|
||||||
new_layertype = pdb.gimp_drawable_type(pdb.gimp_image_get_active_layer(image))
|
new_layertype = pdb.gimp_drawable_type(pdb.gimp_image_get_active_layer(image))
|
||||||
pattern_width, pattern_height, bpp = pdb.gimp_pattern_get_info(pattern)
|
pattern_width, pattern_height, bpp = pdb.gimp_pattern_get_info(pattern)
|
||||||
new_image = pdb.gimp_image_new(pattern_width, pattern_height, new_basetype)
|
new_image = pdb.gimp_image_new(pattern_width, pattern_height, new_basetype)
|
||||||
# !!! Note that gimp_layer_new wants a layer type, not an image basetype
|
|
||||||
new_drawable = pdb.gimp_layer_new(new_image, pattern_width, pattern_height,
|
# !!! Note that gimp_layer_new wants a layer type, not an image basetype
|
||||||
new_layertype, "Texture", 100, NORMAL_MODE)
|
new_drawable = pdb.gimp_layer_new(
|
||||||
pdb.gimp_image_add_layer(new_image, new_drawable, 0)
|
new_image,
|
||||||
return new_image, new_drawable
|
pattern_width,
|
||||||
|
pattern_height,
|
||||||
|
new_layertype,
|
||||||
|
"Texture",
|
||||||
|
100,
|
||||||
|
NORMAL_MODE,
|
||||||
|
)
|
||||||
|
pdb.gimp_image_add_layer(new_image, new_drawable, 0)
|
||||||
|
|
||||||
|
return new_image, new_drawable
|
||||||
|
|
||||||
|
|
||||||
def guts(image, drawable, pattern):
|
def guts(image, drawable, pattern):
|
||||||
''' Crux of algorithm '''
|
""" Crux of algorithm """
|
||||||
|
|
||||||
# Make drawble from pattern
|
# Make drawble from pattern
|
||||||
pattern_image, pattern_layer = layer_from_pattern(image, pattern)
|
pattern_image, pattern_layer = layer_from_pattern(image, pattern)
|
||||||
|
|
||||||
# Fill it with pattern
|
# Fill it with pattern
|
||||||
# NOT pass pattern_layer.ID !!!
|
# NOT pass pattern_layer.ID !!!
|
||||||
pdb.gimp_drawable_fill(pattern_layer, PATTERN_FILL)
|
pdb.gimp_drawable_fill(pattern_layer, PATTERN_FILL)
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
gimp.Display(pattern_image)
|
gimp.Display(pattern_image)
|
||||||
gimp.displays_flush()
|
gimp.displays_flush()
|
||||||
|
|
||||||
# Resynthesize the selection from the pattern without using context
|
# Resynthesize the selection from the pattern without using context
|
||||||
# 0,0,0: Not use_border (context), not tile horiz, not tile vert
|
# 0,0,0: Not use_border (context), not tile horiz, not tile vert
|
||||||
# -1, -1, 0: No maps and no map weight
|
# -1, -1, 0: No maps and no map weight
|
||||||
# DO pass pattern_layer.ID !!!
|
# DO pass pattern_layer.ID !!!
|
||||||
# Resynthesizer is an engine, never interactive
|
# Resynthesizer is an engine, never interactive
|
||||||
pdb.plug_in_resynthesizer(image, drawable, 0, 0, 0, pattern_layer.ID, -1, -1, 0, 0.05, 8, 300)
|
pdb.plug_in_resynthesizer(
|
||||||
|
image, drawable, 0, 0, 0, pattern_layer.ID, -1, -1, 0, 0.05, 8, 300
|
||||||
# Clean up
|
)
|
||||||
if not debug:
|
|
||||||
# Delete image that is displayed throws RuntimeError
|
# Clean up
|
||||||
pdb.gimp_image_delete(pattern_image)
|
if not debug:
|
||||||
|
# Delete image that is displayed throws RuntimeError
|
||||||
|
pdb.gimp_image_delete(pattern_image)
|
||||||
|
|
||||||
|
|
||||||
def plugin_main(image, drawable, pattern):
|
def plugin_main(image, drawable, pattern):
|
||||||
'''
|
"""
|
||||||
Main: the usual user-friendly precondition checking, postcondition cleanup.
|
Main: the usual user-friendly precondition checking, postcondition cleanup.
|
||||||
pattern is a string
|
pattern is a string
|
||||||
'''
|
"""
|
||||||
# User_friendly: if no selection, use entire image.
|
# User_friendly: if no selection, use entire image.
|
||||||
# But the resynthesizer does that for us.
|
# But the resynthesizer does that for us.
|
||||||
|
|
||||||
# Save/restore the context since we change the pattern
|
# Save/restore the context since we change the pattern
|
||||||
pdb.gimp_context_push()
|
pdb.gimp_context_push()
|
||||||
pdb.gimp_context_set_pattern(pattern)
|
pdb.gimp_context_set_pattern(pattern)
|
||||||
guts(image, drawable, pattern)
|
guts(image, drawable, pattern)
|
||||||
pdb.gimp_context_pop()
|
pdb.gimp_context_pop()
|
||||||
|
|
||||||
gimp.displays_flush()
|
gimp.displays_flush()
|
||||||
|
|
||||||
|
|
||||||
register(
|
register(
|
||||||
"python_fu_fill_pattern_resynth",
|
"python_fu_fill_pattern_resynth",
|
||||||
N_("Seamlessly fill with a pattern using synthesis."),
|
N_("Seamlessly fill with a pattern using synthesis."),
|
||||||
"Requires separate resynthesizer plugin.",
|
"Requires separate resynthesizer plugin.",
|
||||||
"Lloyd Konneker",
|
"Lloyd Konneker",
|
||||||
"Copyright 2011 Lloyd Konneker",
|
"Copyright 2011 Lloyd Konneker",
|
||||||
"2011",
|
"2011",
|
||||||
N_("_Fill with pattern seamless..."),
|
N_("_Fill with pattern seamless..."),
|
||||||
"RGB*, GRAY*",
|
"RGB*, GRAY*",
|
||||||
[
|
[
|
||||||
(PF_IMAGE, "image", "Input image", None),
|
(PF_IMAGE, "image", "Input image", None),
|
||||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||||
(PF_PATTERN, "pattern", _("Pattern:"), 'Maple Leaves')
|
(PF_PATTERN, "pattern", _("Pattern:"), "Maple Leaves"),
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
plugin_main,
|
plugin_main,
|
||||||
menu="<Image>/Edit",
|
menu="<Image>/Edit",
|
||||||
domain=("resynthesizer", gimp.locale_directory)
|
domain=("resynthesizer", gimp.locale_directory),
|
||||||
)
|
)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ Requires resynthesizer plug_in (v2).
|
||||||
Author:
|
Author:
|
||||||
lloyd konneker (bootch at nc.rr.com)
|
lloyd konneker (bootch at nc.rr.com)
|
||||||
Based on smart_enlarge.scm 2000 by Paul Harrison.
|
Based on smart_enlarge.scm 2000 by Paul Harrison.
|
||||||
|
Gabriel Almir, avlye
|
||||||
|
|
||||||
Version:
|
Version:
|
||||||
1.0 lloyd konneker lkk 2010 Initial version in python.
|
1.0 lloyd konneker lkk 2010 Initial version in python.
|
||||||
|
|
@ -36,18 +37,18 @@ gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
def plugin_main(image, drawable, scale_factor):
|
def plugin_main(image, drawable, scale_factor):
|
||||||
'''
|
'''
|
||||||
Algorithm:
|
Algorithm:
|
||||||
|
|
||||||
Resynthesize with:
|
Resynthesize with:
|
||||||
corpus = smaller image
|
corpus = smaller image
|
||||||
in map = smaller image but scaled up and down to blur
|
in map = smaller image but scaled up and down to blur
|
||||||
out map = original image
|
out map = original image
|
||||||
|
|
||||||
TODO undo
|
TODO undo
|
||||||
|
|
||||||
original did not accept an alpha channel
|
original did not accept an alpha channel
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
temp_image1 = pdb.gimp_image_duplicate(image) # duplicate for in map
|
temp_image1 = pdb.gimp_image_duplicate(image) # duplicate for in map
|
||||||
if not temp_image1:
|
if not temp_image1:
|
||||||
raise RuntimeError, "Failed duplicate image"
|
raise RuntimeError, "Failed duplicate image"
|
||||||
|
|
@ -61,14 +62,14 @@ def plugin_main(image, drawable, scale_factor):
|
||||||
if not temp_layer2:
|
if not temp_layer2:
|
||||||
raise RuntimeError, "Failed get active layer"
|
raise RuntimeError, "Failed get active layer"
|
||||||
|
|
||||||
|
|
||||||
width = pdb.gimp_drawable_width(drawable)
|
width = pdb.gimp_drawable_width(drawable)
|
||||||
height = pdb.gimp_drawable_height(drawable)
|
height = pdb.gimp_drawable_height(drawable)
|
||||||
|
|
||||||
|
|
||||||
# scale input image down, for corpus map
|
# scale input image down, for corpus map
|
||||||
pdb.gimp_image_scale(temp_image2, width/scale_factor, height/scale_factor)
|
pdb.gimp_image_scale(temp_image2, width/scale_factor, height/scale_factor)
|
||||||
|
|
||||||
# scale input image way down, then back up to, to blur, for corpus
|
# scale input image way down, then back up to, to blur, for corpus
|
||||||
# Final size is same size as corpus map.
|
# Final size is same size as corpus map.
|
||||||
pdb.gimp_image_scale(temp_image1, width / (2 * scale_factor), height / (2 * scale_factor) )
|
pdb.gimp_image_scale(temp_image1, width / (2 * scale_factor), height / (2 * scale_factor) )
|
||||||
|
|
@ -83,7 +84,7 @@ def plugin_main(image, drawable, scale_factor):
|
||||||
temp_layer2.ID, # corpus is smaller original
|
temp_layer2.ID, # corpus is smaller original
|
||||||
temp_layer1.ID, # input map is blurred smaller original
|
temp_layer1.ID, # input map is blurred smaller original
|
||||||
drawable.ID, # output map is original itself
|
drawable.ID, # output map is original itself
|
||||||
1.0, 0.117, 8, 500)
|
1.0, 0.117, 8, 500)
|
||||||
|
|
||||||
pdb.gimp_image_delete(temp_image1)
|
pdb.gimp_image_delete(temp_image1)
|
||||||
pdb.gimp_image_delete(temp_image2)
|
pdb.gimp_image_delete(temp_image2)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Gimp plugin "Uncrop"
|
Gimp plugin "Uncrop"
|
||||||
|
|
||||||
Increase image/canvas size and synthesize outer band from edge of original.
|
Increase image/canvas size and synthesize outer band from edge of original.
|
||||||
|
|
||||||
Author:
|
Author:
|
||||||
lloyd konneker, lkk
|
lloyd konneker, lkk
|
||||||
|
Gabriel Almir, avlye
|
||||||
|
|
||||||
Version:
|
Version:
|
||||||
1.0 lkk 5/15/2009 Initial version in scheme, released to Gimp Registry.
|
1.0 lkk 5/15/2009 Initial version in scheme, released to Gimp Registry.
|
||||||
1.1 lkk 9/21/2009 Translate to python.
|
1.1 lkk 9/21/2009 Translate to python.
|
||||||
|
1.3 avlye 01/11/2020
|
||||||
|
|
||||||
License:
|
License:
|
||||||
|
|
||||||
|
|
@ -28,134 +30,148 @@ The GNU Public License is available at
|
||||||
http://www.gnu.org/copyleft/gpl.html
|
http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
|
||||||
The effect for users:
|
The effect for users:
|
||||||
widens the field of view, maintaining perspective of original
|
widens the field of view, maintaining perspective of original
|
||||||
Should be undoable, except for loss of selection.
|
Should be undoable, except for loss of selection.
|
||||||
Should work on any image type, any count of layers and channels (although only active layer is affected.)
|
Should work on any image type, any count of layers and channels (although only active layer is affected.)
|
||||||
|
|
||||||
Programming notes:
|
Programming notes:
|
||||||
Scheme uses - in names, python uses _
|
Scheme uses - in names, python uses _
|
||||||
Programming devt. cycle:
|
Programming devt. cycle:
|
||||||
Initial creation: cp foo.py ~/.gimp-2.6/scripts, chmod +x, start gimp
|
Initial creation: cp foo.py ~/.gimp-2.6/scripts, chmod +x, start gimp
|
||||||
Refresh: just copy, no need to restart gimp if the pdb registration is unchanged
|
Refresh: just copy, no need to restart gimp if the pdb registration is unchanged
|
||||||
|
|
||||||
IN: Nothing special. The selection is immaterial but is not preserved.
|
IN: Nothing special. The selection is immaterial but is not preserved.
|
||||||
OUT larger layer and image. All other layers not enlarged.
|
OUT larger layer and image. All other layers not enlarged.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
from gimpfu import *
|
from gimpfu import *
|
||||||
|
|
||||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||||
|
|
||||||
|
|
||||||
def resizeImageCentered(image, percentEnlarge):
|
def resizeImageCentered(image, percentEnlarge):
|
||||||
# resize and center image by percent (converted to pixel units)
|
# resize and center image by percent (converted to pixel units)
|
||||||
deltaFraction = (percentEnlarge / 100) + 1.0
|
|
||||||
priorWidth = pdb.gimp_image_width(image)
|
priorWidth = pdb.gimp_image_width(image)
|
||||||
priorHeight = pdb.gimp_image_height(image)
|
priorHeight = pdb.gimp_image_height(image)
|
||||||
|
|
||||||
|
deltaFraction = (percentEnlarge / 100) + 1.0
|
||||||
deltaWidth = priorWidth * deltaFraction
|
deltaWidth = priorWidth * deltaFraction
|
||||||
deltaHeight = priorHeight * deltaFraction
|
deltaHeight = priorHeight * deltaFraction
|
||||||
centeredOffX = (deltaWidth - priorWidth)/ 2
|
|
||||||
centeredOffY = (deltaHeight - priorHeight) / 2
|
centeredOffX = (deltaWidth - priorWidth) / 2
|
||||||
|
centeredOffY = (deltaHeight - priorHeight) / 2
|
||||||
|
|
||||||
pdb.gimp_image_resize(image, deltaWidth, deltaHeight, centeredOffX, centeredOffY)
|
pdb.gimp_image_resize(image, deltaWidth, deltaHeight, centeredOffX, centeredOffY)
|
||||||
#if not pdb.gimp_image_resize(image, deltaWidth, deltaHeight, centeredOffX, centeredOffY):
|
|
||||||
# raise RuntimeError, "Failed resize"
|
|
||||||
|
|
||||||
def shrinkSelectionByPercent(image, percent):
|
def shrinkSelectionByPercent(image, percent):
|
||||||
# shrink selection by percent (converted to pixel units)
|
|
||||||
deltaFraction = percent / 100
|
|
||||||
# convert to pixel dimensions
|
# convert to pixel dimensions
|
||||||
priorWidth = pdb.gimp_image_width(image)
|
priorWidth = pdb.gimp_image_width(image)
|
||||||
priorHeight = pdb.gimp_image_height(image)
|
priorHeight = pdb.gimp_image_height(image)
|
||||||
|
|
||||||
deltaWidth = priorWidth * deltaFraction
|
deltaWidth = priorWidth * deltaFraction
|
||||||
deltaHeight = priorHeight * deltaFraction
|
deltaHeight = priorHeight * deltaFraction
|
||||||
|
# shrink selection by percent (converted to pixel units)
|
||||||
|
deltaFraction = percent / 100
|
||||||
|
|
||||||
# !!! Note total shrink percentage is halved (width of band is percentage/2)
|
# !!! Note total shrink percentage is halved (width of band is percentage/2)
|
||||||
maxDelta = max(deltaWidth, deltaHeight) / 2
|
maxDelta = max(deltaWidth, deltaHeight) / 2
|
||||||
|
|
||||||
pdb.gimp_selection_shrink(image, maxDelta)
|
pdb.gimp_selection_shrink(image, maxDelta)
|
||||||
#if not pdb.gimp_selection_shrink(image, maxDelta):
|
|
||||||
# raise RuntimeError, "Failed shrink selection"
|
|
||||||
|
|
||||||
|
|
||||||
def uncrop(orgImage, drawable, percentEnlargeParam=10):
|
def uncrop(orgImage, drawable, percentEnlargeParam=10):
|
||||||
'''
|
"""
|
||||||
Create frisket stencil selection in a temp image to pass as source (corpus) to plugin resynthesizer,
|
Create frisket stencil selection in a temp image to pass as source (corpus) to plugin resynthesizer,
|
||||||
which does the substantive work.
|
which does the substantive work.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if not pdb.gimp_item_is_layer(drawable):
|
if not pdb.gimp_drawable_is_layer(drawable):
|
||||||
pdb.gimp_message(_("A layer must be active, not a channel."))
|
pdb.gimp_message(_("A layer must be active, not a channel."))
|
||||||
return
|
return
|
||||||
|
|
||||||
pdb.gimp_image_undo_group_start(orgImage)
|
pdb.gimp_image_undo_group_start(orgImage)
|
||||||
|
|
||||||
# copy original into temp for later use
|
# copy original into temp for later use
|
||||||
tempImage = pdb.gimp_image_duplicate(orgImage)
|
tempImage = pdb.gimp_image_duplicate(orgImage)
|
||||||
|
|
||||||
if not tempImage:
|
if not tempImage:
|
||||||
raise RuntimeError, "Failed duplicate image"
|
raise RuntimeError, "Failed duplicate image"
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Prepare target: enlarge canvas and select the new, blank outer ring
|
Prepare target: enlarge canvas and select the new, blank outer ring
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# Save original bounds to later select outer band
|
# Save original bounds to later select outer band
|
||||||
pdb.gimp_selection_all(orgImage)
|
pdb.gimp_selection_all(orgImage)
|
||||||
selectAllPrior = pdb.gimp_selection_save(orgImage)
|
selectAllPrior = pdb.gimp_selection_save(orgImage)
|
||||||
|
|
||||||
# Resize image alone doesn't resize layer, so resize layer also
|
# Resize image alone doesn't resize layer, so resize layer also
|
||||||
resizeImageCentered(orgImage, percentEnlargeParam)
|
resizeImageCentered(orgImage, percentEnlargeParam)
|
||||||
pdb.gimp_layer_resize_to_image_size(drawable)
|
pdb.gimp_layer_resize_to_image_size(drawable)
|
||||||
pdb.gimp_image_select_item(orgImage, CHANNEL_OP_REPLACE, selectAllPrior)
|
pdb.gimp_selection_load(selectAllPrior)
|
||||||
|
|
||||||
# select outer band, the new blank canvas.
|
# select outer band, the new blank canvas.
|
||||||
pdb.gimp_selection_invert(orgImage)
|
pdb.gimp_selection_invert(orgImage)
|
||||||
|
|
||||||
# Assert target image is ready.
|
# Assert target image is ready.
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Prepare source (corpus) layer, a band at edge of original, in a dupe.
|
Prepare source (corpus) layer, a band at edge of original, in a dupe.
|
||||||
Note the width of corpus band is same as width of enlargement band.
|
Note the width of corpus band is same as width of enlargement band.
|
||||||
'''
|
"""
|
||||||
# Working with the original size.
|
# Working with the original size.
|
||||||
# Could be alpha channel transparency
|
# Could be alpha channel transparency
|
||||||
workLayer = pdb.gimp_image_get_active_layer(tempImage)
|
workLayer = pdb.gimp_image_get_active_layer(tempImage)
|
||||||
|
|
||||||
if not workLayer:
|
if not workLayer:
|
||||||
raise RuntimeError, "Failed get active layer"
|
raise RuntimeError, "Failed get active layer"
|
||||||
|
|
||||||
# Select outer band: select all, shrink
|
# Select outer band: select all, shrink
|
||||||
pdb.gimp_selection_all(tempImage)
|
pdb.gimp_selection_all(tempImage)
|
||||||
shrinkSelectionByPercent(tempImage, percentEnlargeParam)
|
shrinkSelectionByPercent(tempImage, percentEnlargeParam)
|
||||||
pdb.gimp_selection_invert(tempImage) # invert interior selection into a frisket
|
pdb.gimp_selection_invert(tempImage) # invert interior selection into a frisket
|
||||||
|
|
||||||
# Note that v1 resynthesizer required an inverted selection !!
|
# Note that v1 resynthesizer required an inverted selection !!
|
||||||
# No need to crop corpus to save memory.
|
# No need to crop corpus to save memory.
|
||||||
|
|
||||||
# Note that the API hasn't changed but use_border param now has more values.
|
# Note that the API hasn't changed but use_border param now has more values.
|
||||||
# !!! The crux: use_border param=5 means inside out direction
|
# !!! The crux: use_border param=5 means inside out direction
|
||||||
pdb.plug_in_resynthesizer(orgImage, drawable, 0,0,5, workLayer.ID, -1, -1, 0.0, 0.117, 16, 500)
|
pdb.plug_in_resynthesizer(
|
||||||
|
orgImage, drawable, 0, 0, 5, workLayer.ID, -1, -1, 0.0, 0.117, 16, 500
|
||||||
# Clean up.
|
)
|
||||||
|
|
||||||
|
# Clean up.
|
||||||
# Any errors now are moot.
|
# Any errors now are moot.
|
||||||
pdb.gimp_selection_none(orgImage)
|
pdb.gimp_selection_none(orgImage)
|
||||||
pdb.gimp_image_remove_channel(orgImage, selectAllPrior)
|
pdb.gimp_image_remove_channel(orgImage, selectAllPrior)
|
||||||
pdb.gimp_image_undo_group_end(orgImage)
|
pdb.gimp_image_undo_group_end(orgImage)
|
||||||
pdb.gimp_displays_flush()
|
pdb.gimp_displays_flush()
|
||||||
|
|
||||||
gimp.delete(tempImage) # Comment out to debug corpus creation.
|
gimp.delete(tempImage) # Comment out to debug corpus creation.
|
||||||
|
|
||||||
|
|
||||||
register(
|
register(
|
||||||
"python_fu_uncrop",
|
"python_fu_uncrop",
|
||||||
N_("Enlarge image by synthesizing a border that matches the edge, maintaining perspective. Works best for small enlargement of natural edges. Undo a Crop instead, if possible! "),
|
N_(
|
||||||
"Requires separate resynthesizer plugin.",
|
"Enlarge image by synthesizing a border that matches the edge, maintaining perspective. Works best for small enlargement of natural edges. Undo a Crop instead, if possible! "
|
||||||
"Lloyd Konneker",
|
),
|
||||||
"Copyright 2009 Lloyd Konneker",
|
"Requires separate resynthesizer plugin.",
|
||||||
"2009",
|
"Lloyd Konneker",
|
||||||
N_("Uncrop..."),
|
"Copyright 2009 Lloyd Konneker",
|
||||||
"RGB*, GRAY*",
|
"2009",
|
||||||
[
|
N_("Uncrop..."),
|
||||||
(PF_IMAGE, "image", "Input image", None),
|
"RGB*, GRAY*",
|
||||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
[
|
||||||
(PF_SLIDER, "percentEnlargeParam", _("Percent enlargement"), 10, (0, 100, 1))
|
(PF_IMAGE, "image", "Input image", None),
|
||||||
],
|
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||||
[],
|
(PF_SLIDER, "percentEnlargeParam", _("Percent enlargement"), 10, (0, 100, 1)),
|
||||||
uncrop,
|
],
|
||||||
menu="<Image>/Filters/Enhance",
|
[],
|
||||||
domain=("resynthesizer", gimp.locale_directory)
|
uncrop,
|
||||||
)
|
menu="<Image>/Filters/Enhance",
|
||||||
|
domain=("resynthesizer", gimp.locale_directory),
|
||||||
|
)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue