mirror of
https://github.com/Diolinux/PhotoGIMP.git
synced 2026-04-05 19:51:58 +02:00
Improving Plugns scripts & Add Editor Config
This commit is contained in:
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
|
||||
.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):
|
||||
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)
|
||||
|
||||
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]
|
||||
@@ -91,30 +92,30 @@ def heal_selection(timg, tdrawable, samplingRadiusParam=50, directionParam=0, or
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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
|
||||
@@ -123,30 +124,30 @@ def heal_selection(timg, tdrawable, samplingRadiusParam=50, directionParam=0, or
|
||||
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.
|
||||
# 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.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)
|
||||
@@ -157,7 +158,7 @@ register(
|
||||
N_("Heal the selection from surroundings as if using the heal tool."),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"2009 Lloyd Konneker", # Copyright
|
||||
"2009 Lloyd Konneker", # Copyright
|
||||
"2009",
|
||||
N_("_Heal selection..."),
|
||||
"RGB*, GRAY*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
"""
|
||||
Gimp plugin "Heal transparency"
|
||||
|
||||
Copyright 2010 lloyd konneker (bootch at nc.rr.com)
|
||||
@@ -22,71 +22,81 @@ License:
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def heal_transparency(timg, tdrawable, samplingRadiusParam=50, orderParam=2):
|
||||
|
||||
# precondition should be enforced by Gimp according to image modes allowed.
|
||||
if not pdb.gimp_drawable_has_alpha(tdrawable):
|
||||
pdb.gimp_message("The active layer has no alpha channel to heal.")
|
||||
return
|
||||
|
||||
pdb.gimp_image_undo_group_start(timg)
|
||||
|
||||
# save selection for later restoration.
|
||||
# Saving selection channel makes it active, so we must save and restore the active layer
|
||||
org_selection = pdb.gimp_selection_save(timg)
|
||||
pdb.gimp_image_set_active_layer(timg, tdrawable)
|
||||
|
||||
# alpha to selection
|
||||
pdb.gimp_image_select_item(timg, CHANNEL_OP_REPLACE, tdrawable)
|
||||
# Want the transparent, not the opaque.
|
||||
pdb.gimp_selection_invert(timg)
|
||||
# Since transparency was probably anti-aliased (dithered with partial transpancy),
|
||||
# grow the selection to get past the dithering.
|
||||
pdb.gimp_selection_grow(timg, 1)
|
||||
# Remove the alpha from this layer. IE compose with current background color (often white.)
|
||||
# Resynthesizer won't heal transparent.
|
||||
pdb.gimp_layer_flatten(tdrawable)
|
||||
|
||||
# Call heal selection (not the resynthesizer), which will create a proper corpus.
|
||||
# 0 = sample from all around
|
||||
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
|
||||
pdb.gimp_image_select_item(timg, CHANNEL_OP_REPLACE, org_selection)
|
||||
|
||||
# Clean up (comment out to debug)
|
||||
pdb.gimp_image_undo_group_end(timg)
|
||||
# precondition should be enforced by Gimp according to image modes allowed.
|
||||
if not pdb.gimp_drawable_has_alpha(tdrawable):
|
||||
pdb.gimp_message("The active layer has no alpha channel to heal.")
|
||||
return
|
||||
|
||||
pdb.gimp_image_undo_group_start(timg)
|
||||
|
||||
# save selection for later restoration.
|
||||
# Saving selection channel makes it active, so we must save and restore the active layer
|
||||
org_selection = pdb.gimp_selection_save(timg)
|
||||
pdb.gimp_image_set_active_layer(timg, tdrawable)
|
||||
|
||||
# alpha to selection
|
||||
pdb.gimp_selection_layer_alpha(tdrawable)
|
||||
# Want the transparent, not the opaque.
|
||||
pdb.gimp_selection_invert(timg)
|
||||
# Since transparency was probably anti-aliased (dithered with partial transpancy),
|
||||
# grow the selection to get past the dithering.
|
||||
pdb.gimp_selection_grow(timg, 1)
|
||||
# Remove the alpha from this layer. IE compose with current background color (often white.)
|
||||
# Resynthesizer won't heal transparent.
|
||||
pdb.gimp_layer_flatten(tdrawable)
|
||||
|
||||
# Call heal selection (not the resynthesizer), which will create a proper corpus.
|
||||
# 0 = sample from all around
|
||||
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
|
||||
pdb.gimp_selection_load(org_selection)
|
||||
|
||||
# Clean up (comment out to debug)
|
||||
pdb.gimp_image_undo_group_end(timg)
|
||||
|
||||
|
||||
register(
|
||||
"python_fu_heal_transparency",
|
||||
N_("Removes alpha channel by synthesis. Fill outward for edges, inward for holes."),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"Copyright 2010 Lloyd Konneker",
|
||||
"2010",
|
||||
N_("Heal transparency..."),
|
||||
"RGBA, GRAYA", # !!! Requires an alpha channel to heal
|
||||
[
|
||||
(PF_IMAGE, "image", "Input image", None),
|
||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||
(PF_INT, "samplingRadiusParam", _("Context sampling width (pixels):"), 50),
|
||||
(PF_OPTION, "orderParam", _("Filling order:"), 2, [_("Random"),
|
||||
_("Inwards towards center"), _("Outwards from center") ])
|
||||
],
|
||||
[],
|
||||
heal_transparency,
|
||||
menu="<Image>/Filters/Enhance",
|
||||
domain=("resynthesizer", gimp.locale_directory)
|
||||
)
|
||||
"python_fu_heal_transparency",
|
||||
N_(
|
||||
"Removes alpha channel by synthesis. Fill outward for edges, inward for holes."
|
||||
),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"Copyright 2010 Lloyd Konneker",
|
||||
"2010",
|
||||
N_("Heal transparency..."),
|
||||
"RGBA, GRAYA", # !!! Requires an alpha channel to heal
|
||||
[
|
||||
(PF_IMAGE, "image", "Input image", None),
|
||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||
(PF_INT, "samplingRadiusParam", _("Context sampling width (pixels):"), 50),
|
||||
(
|
||||
PF_OPTION,
|
||||
"orderParam",
|
||||
_("Filling order:"),
|
||||
2,
|
||||
[_("Random"), _("Inwards towards center"), _("Outwards from center")],
|
||||
),
|
||||
],
|
||||
[],
|
||||
heal_transparency,
|
||||
menu="<Image>/Filters/Enhance",
|
||||
domain=("resynthesizer", gimp.locale_directory),
|
||||
)
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
"""
|
||||
Gimp plugin.
|
||||
Transfer style (color and surface texture) from a source image to the active, target image.
|
||||
|
||||
Requires resynthesizer plug-in.
|
||||
|
||||
Author:
|
||||
Authors:
|
||||
lloyd konneker, lkk
|
||||
Gabriel Almir, avlye
|
||||
|
||||
Version:
|
||||
1.0 lkk 7/15/2010 Initial version. Released to Gimp Registry.
|
||||
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:
|
||||
_________________
|
||||
1.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
|
||||
1.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
|
||||
1.2
|
||||
Changes for new resynthesizer: no need to synchronize, remove alphas
|
||||
Fixed improper adjustment of contrast of source: only adjust source map.
|
||||
Changes for new resynthesizer: no need to synchronize, remove alphas
|
||||
Fixed improper adjustment of contrast of source: only adjust source map.
|
||||
1.3
|
||||
Minor changes to improve developer experience
|
||||
|
||||
TODO
|
||||
a quality setting that changes the parameters to resynth
|
||||
@@ -93,7 +97,7 @@ _________________
|
||||
IN: The active image and layer.
|
||||
The selection in the active image.
|
||||
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.
|
||||
|
||||
The print stmts go to the console, info to advanced users and debuggers.
|
||||
@@ -105,293 +109,314 @@ synchronization of modes
|
||||
abstracting the settings
|
||||
contrast adjustment
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
from gimpfu import *
|
||||
from math import acos
|
||||
from math import acos, pi
|
||||
|
||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||
|
||||
# True if you want to display and retain working, temporary images
|
||||
debug = False
|
||||
|
||||
def display_debug_image(image) :
|
||||
if debug :
|
||||
try:
|
||||
pdb.gimp_display_new(image)
|
||||
pdb.gimp_displays_flush()
|
||||
except RuntimeError:
|
||||
pass # if run-mode not interactive, Gimp throws
|
||||
|
||||
def display_debug_image(image):
|
||||
if debug:
|
||||
try:
|
||||
pdb.gimp_display_new(image)
|
||||
pdb.gimp_displays_flush()
|
||||
except RuntimeError:
|
||||
pass # if run-mode not interactive, Gimp throws
|
||||
|
||||
|
||||
def make_grayscale_map(image, drawable):
|
||||
'''
|
||||
Make a grayscale copy for a map.
|
||||
|
||||
Maps must be same size as their parent image.
|
||||
|
||||
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.
|
||||
'''
|
||||
if pdb.gimp_image_base_type(image) == GRAY :
|
||||
return image, drawable
|
||||
|
||||
# Save selection, copy entire image, and restore
|
||||
original_selection = pdb.gimp_selection_save(image)
|
||||
pdb.gimp_selection_all(image) # copy requires selection
|
||||
pdb.gimp_edit_copy(drawable)
|
||||
if original_selection:
|
||||
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, original_selection) # restore selection in image
|
||||
pdb.gimp_image_remove_channel(image, original_selection) # cleanup the copied selection mask
|
||||
# !!! Note remove_channel not drawable_delete
|
||||
|
||||
# Make a copy, greyscale
|
||||
temp_image = pdb.gimp_edit_paste_as_new()
|
||||
pdb.gimp_image_convert_grayscale(temp_image)
|
||||
display_debug_image(temp_image)
|
||||
temp_drawable = pdb.gimp_image_get_active_drawable(temp_image)
|
||||
return temp_image, temp_drawable
|
||||
"""
|
||||
Make a grayscale copy for a map.
|
||||
|
||||
Maps must be same size as their parent image.
|
||||
|
||||
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.
|
||||
"""
|
||||
if pdb.gimp_image_base_type(image) == GRAY:
|
||||
return image, drawable
|
||||
|
||||
# Save selection, copy entire image, and restore
|
||||
original_selection = pdb.gimp_selection_save(image)
|
||||
pdb.gimp_selection_all(image) # copy requires selection
|
||||
pdb.gimp_edit_copy(drawable)
|
||||
if original_selection:
|
||||
pdb.gimp_selection_load(original_selection) # restore selection in image
|
||||
pdb.gimp_image_remove_channel(
|
||||
image, original_selection
|
||||
) # cleanup the copied selection mask
|
||||
# !!! Note remove_channel not drawable_delete
|
||||
|
||||
# Make a copy, greyscale
|
||||
temp_image = pdb.gimp_edit_paste_as_new()
|
||||
pdb.gimp_image_convert_grayscale(temp_image)
|
||||
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) :
|
||||
'''
|
||||
User-friendliness:
|
||||
If mode of target is not equal to mode of source source, change modes.
|
||||
Resynthesizer requires target and source to be same mode.
|
||||
Assert target is RGB or GRAY (since is precondition of plugin.)
|
||||
UI decision: make this quiet, presume user intends mode change.
|
||||
But don't permanently change mode of source.
|
||||
Always upgrade GRAY to RGB, not downgrade RGB to GRAY.
|
||||
'''
|
||||
target_mode = pdb.gimp_image_base_type(target_image)
|
||||
source_mode = pdb.gimp_image_base_type(source_image)
|
||||
if target_mode != source_mode :
|
||||
# print("Map style: converted mode\n.")
|
||||
if target_mode == GRAY:
|
||||
pdb.gimp_image_convert_rgb(target_image)
|
||||
else : # target is RGB and source is GRAY
|
||||
# Assert only convert a copy of source,
|
||||
# user NEVER intends original source be altered.
|
||||
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 synchronize_modes(target_image, source_image):
|
||||
"""
|
||||
User-friendliness:
|
||||
If mode of target is not equal to mode of source source, change modes.
|
||||
Resynthesizer requires target and source to be same mode.
|
||||
Assert target is RGB or GRAY (since is precondition of plugin.)
|
||||
UI decision: make this quiet, presume user intends mode change.
|
||||
But don't permanently change mode of source.
|
||||
Always upgrade GRAY to RGB, not downgrade RGB to GRAY.
|
||||
"""
|
||||
target_mode = pdb.gimp_image_base_type(target_image)
|
||||
source_mode = pdb.gimp_image_base_type(source_image)
|
||||
if target_mode != source_mode:
|
||||
# print("Map style: converted mode\n.")
|
||||
if target_mode == GRAY:
|
||||
pdb.gimp_image_convert_rgb(target_image)
|
||||
else: # target is RGB and source is GRAY
|
||||
# Assert only convert a copy of source,
|
||||
# user NEVER intends original source be altered.
|
||||
pdb.gimp_image_convert_rgb(source_image)
|
||||
|
||||
|
||||
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*3.14)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
'''
|
||||
# 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)
|
||||
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.
|
||||
Note the drawable chooser widget in Pygimp does not allow us to prefilter INDEXED mode.
|
||||
So check here and give a warning.
|
||||
'''
|
||||
# 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_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
|
||||
"""
|
||||
# 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_target_base_type = pdb.gimp_image_base_type(image)
|
||||
|
||||
if image == source_image and drawable == source_drawable:
|
||||
is_source_copy = False
|
||||
'''
|
||||
If source is same as target,
|
||||
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:
|
||||
is_source_copy = False
|
||||
"""
|
||||
If source is same as target,
|
||||
then the old resynthesizer required a selection (engine used inverse selection for corpus).
|
||||
New resynthesizer doesn't need a selection.
|
||||
If source same as target, effect is similar to a blur.
|
||||
'''
|
||||
# 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)
|
||||
# Copy source always, for performance, and for possible mode change.
|
||||
is_source_copy = True
|
||||
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)
|
||||
'''
|
||||
"""
|
||||
# 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)
|
||||
# Copy source always, for performance, and for possible mode change.
|
||||
is_source_copy = True
|
||||
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)
|
||||
"""
|
||||
|
||||
"""
|
||||
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
|
||||
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.
|
||||
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:
|
||||
|
||||
|
||||
htile and vtile = 1 since it reduces artifacts around edge
|
||||
|
||||
|
||||
map_weight I linearize since easier on users than an exponential
|
||||
|
||||
|
||||
use_border = 1 since there might be a selection and context (outside target).
|
||||
|
||||
|
||||
9 neighbors (a 3x3 patch) and 200 tries for speed
|
||||
|
||||
'''
|
||||
|
||||
map_weight = calculate_map_weight(percent_transfer)
|
||||
|
||||
# !!! 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)
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
"""
|
||||
|
||||
map_weight = calculate_map_weight(percent_transfer)
|
||||
|
||||
# !!! 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,
|
||||
)
|
||||
|
||||
# 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(
|
||||
"python_fu_map_style",
|
||||
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.",
|
||||
"Lloyd Konneker (bootch nc.rr.com)",
|
||||
"Copyright 2010 Lloyd Konneker",
|
||||
"2010",
|
||||
N_("Style..."),
|
||||
"RGB*, GRAY*",
|
||||
[
|
||||
(PF_IMAGE, "image", "Input image", None),
|
||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||
(PF_DRAWABLE, "source_drawable", _("Source of style:"), None),
|
||||
(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)))
|
||||
],
|
||||
[],
|
||||
transfer_style,
|
||||
menu="<Image>/Filters/Map",
|
||||
domain=("resynthesizer", gimp.locale_directory)
|
||||
)
|
||||
"python_fu_map_style",
|
||||
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.",
|
||||
"Lloyd Konneker (bootch nc.rr.com)",
|
||||
"Copyright 2010 Lloyd Konneker",
|
||||
"2010",
|
||||
N_("Style..."),
|
||||
"RGB*, GRAY*",
|
||||
[
|
||||
(PF_IMAGE, "image", "Input image", None),
|
||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||
(PF_DRAWABLE, "source_drawable", _("Source of style:"), None),
|
||||
(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)),
|
||||
),
|
||||
],
|
||||
[],
|
||||
transfer_style,
|
||||
menu="<Image>/Filters/Map",
|
||||
domain=("resynthesizer", gimp.locale_directory),
|
||||
)
|
||||
|
||||
main()
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ Sometimes called rendering a texture.
|
||||
|
||||
Requires resynthesizer plug-in.
|
||||
|
||||
Author:
|
||||
Authors:
|
||||
lloyd konneker, lkk, bootch at nc.rr.com
|
||||
Gabriel Almir, avlye, avlye.me
|
||||
|
||||
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.3 avlye 01/10/2020 Minor changes for improving developer experience
|
||||
|
||||
License:
|
||||
|
||||
@@ -48,9 +50,9 @@ The continuum of randomness versus speed:
|
||||
- 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.
|
||||
|
||||
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
|
||||
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.
|
||||
OUT New image, possibly resized canvas, same scale and resolution.
|
||||
@@ -64,11 +66,12 @@ gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||
|
||||
debug = False
|
||||
|
||||
|
||||
|
||||
def new_resized_image(image, resize_ratio):
|
||||
# Create new image resized by a ratio from *selection* in the old image
|
||||
|
||||
|
||||
(is_selection, ulx, uly, lrx, lry) = pdb.gimp_selection_bounds(image)
|
||||
|
||||
if not is_selection :
|
||||
# Resynthesizer will use the entire source image as corpus.
|
||||
# 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
|
||||
new_width = int((lrx - ulx) * resize_ratio)
|
||||
new_height = int((lry - uly) * resize_ratio)
|
||||
|
||||
|
||||
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_image = pdb.gimp_image_new(new_width, new_height, new_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_layertype, "Texture", 100, NORMAL_MODE)
|
||||
pdb.gimp_image_add_layer(new_image, new_drawable, 0)
|
||||
|
||||
return new_image, new_drawable
|
||||
|
||||
|
||||
|
||||
def display_image(image):
|
||||
try:
|
||||
gimp.Display(image)
|
||||
except gimp.error:
|
||||
pass # If runmode is NONINTERACTIVE, expect gimp_display_new() to fail
|
||||
|
||||
gimp.displays_flush()
|
||||
|
||||
|
||||
|
||||
def render_texture(image, drawable, resize_ratio=2, make_tile=0):
|
||||
'''
|
||||
Create a randomized texture image from the selection.
|
||||
The image can be suited for further, seamless tiling.
|
||||
Create a randomized texture image from the selection.
|
||||
The image can be suited for further, seamless tiling.
|
||||
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.)
|
||||
|
||||
|
||||
A selection in the source image is optional.
|
||||
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.
|
||||
pdb.gimp_image_undo_disable(image)
|
||||
|
||||
|
||||
'''
|
||||
Create new image, optionally resized, and display for it.
|
||||
'''
|
||||
new_image, new_drawable = new_resized_image(image, resize_ratio)
|
||||
pdb.gimp_image_undo_disable(new_image)
|
||||
|
||||
if not new_drawable:
|
||||
raise RuntimeError, "Failed create layer."
|
||||
|
||||
|
||||
# If using new resynthesizer with animation for debugging
|
||||
if debug:
|
||||
display_image(new_image)
|
||||
|
||||
|
||||
'''
|
||||
copy original into temp and crop it to the selection to save memory in resynthesizer
|
||||
'''
|
||||
temp_image = pdb.gimp_image_duplicate(image)
|
||||
|
||||
if not temp_image:
|
||||
raise RuntimeError, "Failed duplicate image"
|
||||
|
||||
|
||||
# Get bounds, offset of selection
|
||||
(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.
|
||||
pass
|
||||
else :
|
||||
|
||||
if is_selection:
|
||||
pdb.gimp_image_crop(temp_image, lrx - ulx, lry - uly, ulx, uly)
|
||||
|
||||
|
||||
# Don't flatten because it turns transparency to background (white usually)
|
||||
work_layer = pdb.gimp_image_get_active_layer(temp_image)
|
||||
|
||||
if not work_layer:
|
||||
raise RuntimeError, "Failed get active layer"
|
||||
|
||||
|
||||
# Insure the selection is all (not necessary, resynthesizer will use all if no selection.)
|
||||
pdb.gimp_selection_all(temp_image)
|
||||
|
||||
|
||||
# Settings for making edges suitable for seamless tiling afterwards.
|
||||
# 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.
|
||||
# I.E. treat the target as a sphere when matching.
|
||||
if make_tile :
|
||||
htile = 1
|
||||
vtile = 1
|
||||
else :
|
||||
htile = 0
|
||||
vtile = 0
|
||||
|
||||
tile = (1, 1) if make_tile else (0, 0)
|
||||
|
||||
# Call resynthesizer
|
||||
# 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.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
# For version of resynthesizer with uninverted selection
|
||||
# !!! 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)
|
||||
|
||||
# Clean up.
|
||||
|
||||
# Clean up.
|
||||
pdb.gimp_image_delete(temp_image)
|
||||
pdb.gimp_image_undo_enable(image)
|
||||
pdb.gimp_image_undo_enable(new_image)
|
||||
@@ -201,4 +203,3 @@ register(
|
||||
)
|
||||
|
||||
main()
|
||||
|
||||
|
||||
@@ -33,22 +33,22 @@ gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||
def plugin_main(image, drawable, scale_factor):
|
||||
'''
|
||||
Algorithm:
|
||||
|
||||
|
||||
Scale image up.
|
||||
Resynthesize with:
|
||||
corpus = original size image
|
||||
in map = original size image but scaled up and down to blur
|
||||
out map = scaled up image
|
||||
|
||||
|
||||
This restores the detail that scaling up looses.
|
||||
It maintains the aspect ratio of all image features.
|
||||
|
||||
|
||||
Unlike the original smart-enlarge.scm, this alters the original image.
|
||||
|
||||
|
||||
original did not accept an alpha channel
|
||||
'''
|
||||
|
||||
|
||||
|
||||
temp_image1 = pdb.gimp_image_duplicate(image) # duplicate for in map
|
||||
if not temp_image1:
|
||||
raise RuntimeError, "Failed duplicate image"
|
||||
@@ -67,7 +67,7 @@ def plugin_main(image, drawable, scale_factor):
|
||||
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, height)
|
||||
|
||||
|
||||
# scale up the image
|
||||
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_layer1.ID, # input map
|
||||
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_image2)
|
||||
@@ -108,5 +108,5 @@ if __name__ == "__main__" :
|
||||
)
|
||||
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
"""
|
||||
Gimp plugin "Fill with pattern seamless..."
|
||||
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
|
||||
http://www.gnu.org/copyleft/gpl.html
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
from gimpfu import *
|
||||
|
||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True);
|
||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||
|
||||
debug = False
|
||||
|
||||
|
||||
def layer_from_pattern(image, 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_layertype = pdb.gimp_drawable_type(pdb.gimp_image_get_active_layer(image))
|
||||
pattern_width, pattern_height, bpp = pdb.gimp_pattern_get_info(pattern)
|
||||
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,
|
||||
new_layertype, "Texture", 100, NORMAL_MODE)
|
||||
pdb.gimp_image_add_layer(new_image, new_drawable, 0)
|
||||
return new_image, new_drawable
|
||||
|
||||
|
||||
"""
|
||||
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_layertype = pdb.gimp_drawable_type(pdb.gimp_image_get_active_layer(image))
|
||||
pattern_width, pattern_height, bpp = pdb.gimp_pattern_get_info(pattern)
|
||||
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,
|
||||
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):
|
||||
''' Crux of algorithm '''
|
||||
|
||||
# Make drawble from pattern
|
||||
pattern_image, pattern_layer = layer_from_pattern(image, pattern)
|
||||
|
||||
# Fill it with pattern
|
||||
# NOT pass pattern_layer.ID !!!
|
||||
pdb.gimp_drawable_fill(pattern_layer, PATTERN_FILL)
|
||||
|
||||
if debug:
|
||||
gimp.Display(pattern_image)
|
||||
gimp.displays_flush()
|
||||
|
||||
# Resynthesize the selection from the pattern without using context
|
||||
# 0,0,0: Not use_border (context), not tile horiz, not tile vert
|
||||
# -1, -1, 0: No maps and no map weight
|
||||
# DO pass pattern_layer.ID !!!
|
||||
# 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)
|
||||
|
||||
# Clean up
|
||||
if not debug:
|
||||
# Delete image that is displayed throws RuntimeError
|
||||
pdb.gimp_image_delete(pattern_image)
|
||||
|
||||
|
||||
""" Crux of algorithm """
|
||||
|
||||
# Make drawble from pattern
|
||||
pattern_image, pattern_layer = layer_from_pattern(image, pattern)
|
||||
|
||||
# Fill it with pattern
|
||||
# NOT pass pattern_layer.ID !!!
|
||||
pdb.gimp_drawable_fill(pattern_layer, PATTERN_FILL)
|
||||
|
||||
if debug:
|
||||
gimp.Display(pattern_image)
|
||||
gimp.displays_flush()
|
||||
|
||||
# Resynthesize the selection from the pattern without using context
|
||||
# 0,0,0: Not use_border (context), not tile horiz, not tile vert
|
||||
# -1, -1, 0: No maps and no map weight
|
||||
# DO pass pattern_layer.ID !!!
|
||||
# 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
|
||||
)
|
||||
|
||||
# Clean up
|
||||
if not debug:
|
||||
# Delete image that is displayed throws RuntimeError
|
||||
pdb.gimp_image_delete(pattern_image)
|
||||
|
||||
|
||||
def plugin_main(image, drawable, pattern):
|
||||
'''
|
||||
Main: the usual user-friendly precondition checking, postcondition cleanup.
|
||||
pattern is a string
|
||||
'''
|
||||
# User_friendly: if no selection, use entire image.
|
||||
# But the resynthesizer does that for us.
|
||||
|
||||
# Save/restore the context since we change the pattern
|
||||
pdb.gimp_context_push()
|
||||
pdb.gimp_context_set_pattern(pattern)
|
||||
guts(image, drawable, pattern)
|
||||
pdb.gimp_context_pop()
|
||||
|
||||
gimp.displays_flush()
|
||||
"""
|
||||
Main: the usual user-friendly precondition checking, postcondition cleanup.
|
||||
pattern is a string
|
||||
"""
|
||||
# User_friendly: if no selection, use entire image.
|
||||
# But the resynthesizer does that for us.
|
||||
|
||||
# Save/restore the context since we change the pattern
|
||||
pdb.gimp_context_push()
|
||||
pdb.gimp_context_set_pattern(pattern)
|
||||
guts(image, drawable, pattern)
|
||||
pdb.gimp_context_pop()
|
||||
|
||||
gimp.displays_flush()
|
||||
|
||||
|
||||
register(
|
||||
"python_fu_fill_pattern_resynth",
|
||||
N_("Seamlessly fill with a pattern using synthesis."),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"Copyright 2011 Lloyd Konneker",
|
||||
"2011",
|
||||
N_("_Fill with pattern seamless..."),
|
||||
"RGB*, GRAY*",
|
||||
[
|
||||
(PF_IMAGE, "image", "Input image", None),
|
||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||
(PF_PATTERN, "pattern", _("Pattern:"), 'Maple Leaves')
|
||||
],
|
||||
[],
|
||||
plugin_main,
|
||||
menu="<Image>/Edit",
|
||||
domain=("resynthesizer", gimp.locale_directory)
|
||||
)
|
||||
"python_fu_fill_pattern_resynth",
|
||||
N_("Seamlessly fill with a pattern using synthesis."),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"Copyright 2011 Lloyd Konneker",
|
||||
"2011",
|
||||
N_("_Fill with pattern seamless..."),
|
||||
"RGB*, GRAY*",
|
||||
[
|
||||
(PF_IMAGE, "image", "Input image", None),
|
||||
(PF_DRAWABLE, "drawable", "Input drawable", None),
|
||||
(PF_PATTERN, "pattern", _("Pattern:"), "Maple Leaves"),
|
||||
],
|
||||
[],
|
||||
plugin_main,
|
||||
menu="<Image>/Edit",
|
||||
domain=("resynthesizer", gimp.locale_directory),
|
||||
)
|
||||
|
||||
main()
|
||||
|
||||
@@ -7,6 +7,7 @@ Requires resynthesizer plug_in (v2).
|
||||
Author:
|
||||
lloyd konneker (bootch at nc.rr.com)
|
||||
Based on smart_enlarge.scm 2000 by Paul Harrison.
|
||||
Gabriel Almir, avlye
|
||||
|
||||
Version:
|
||||
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):
|
||||
'''
|
||||
Algorithm:
|
||||
|
||||
|
||||
Resynthesize with:
|
||||
corpus = smaller image
|
||||
in map = smaller image but scaled up and down to blur
|
||||
out map = original image
|
||||
|
||||
|
||||
TODO undo
|
||||
|
||||
|
||||
original did not accept an alpha channel
|
||||
'''
|
||||
|
||||
|
||||
|
||||
temp_image1 = pdb.gimp_image_duplicate(image) # duplicate for in map
|
||||
if not temp_image1:
|
||||
raise RuntimeError, "Failed duplicate image"
|
||||
@@ -61,14 +62,14 @@ def plugin_main(image, drawable, scale_factor):
|
||||
if not temp_layer2:
|
||||
raise RuntimeError, "Failed get active layer"
|
||||
|
||||
|
||||
|
||||
width = pdb.gimp_drawable_width(drawable)
|
||||
height = pdb.gimp_drawable_height(drawable)
|
||||
|
||||
|
||||
|
||||
|
||||
# scale input image down, for corpus map
|
||||
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
|
||||
# Final size is same size as corpus map.
|
||||
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_layer1.ID, # input map is blurred smaller original
|
||||
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_image2)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
"""
|
||||
Gimp plugin "Uncrop"
|
||||
|
||||
Increase image/canvas size and synthesize outer band from edge of original.
|
||||
|
||||
Author:
|
||||
lloyd konneker, lkk
|
||||
Gabriel Almir, avlye
|
||||
|
||||
Version:
|
||||
1.0 lkk 5/15/2009 Initial version in scheme, released to Gimp Registry.
|
||||
1.1 lkk 9/21/2009 Translate to python.
|
||||
1.3 avlye 01/11/2020
|
||||
|
||||
License:
|
||||
|
||||
@@ -28,134 +30,148 @@ The GNU Public License is available at
|
||||
http://www.gnu.org/copyleft/gpl.html
|
||||
|
||||
|
||||
The effect for users:
|
||||
The effect for users:
|
||||
widens the field of view, maintaining perspective of original
|
||||
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.)
|
||||
|
||||
Programming notes:
|
||||
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
|
||||
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.
|
||||
OUT larger layer and image. All other layers not enlarged.
|
||||
'''
|
||||
"""
|
||||
|
||||
from gimpfu import *
|
||||
|
||||
gettext.install("resynthesizer", gimp.locale_directory, unicode=True)
|
||||
|
||||
|
||||
|
||||
def resizeImageCentered(image, percentEnlarge):
|
||||
# resize and center image by percent (converted to pixel units)
|
||||
deltaFraction = (percentEnlarge / 100) + 1.0
|
||||
priorWidth = pdb.gimp_image_width(image)
|
||||
priorHeight = pdb.gimp_image_height(image)
|
||||
|
||||
deltaFraction = (percentEnlarge / 100) + 1.0
|
||||
deltaWidth = priorWidth * 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)
|
||||
#if not pdb.gimp_image_resize(image, deltaWidth, deltaHeight, centeredOffX, centeredOffY):
|
||||
# raise RuntimeError, "Failed resize"
|
||||
|
||||
|
||||
|
||||
def shrinkSelectionByPercent(image, percent):
|
||||
# shrink selection by percent (converted to pixel units)
|
||||
deltaFraction = percent / 100
|
||||
# convert to pixel dimensions
|
||||
priorWidth = pdb.gimp_image_width(image)
|
||||
priorHeight = pdb.gimp_image_height(image)
|
||||
|
||||
deltaWidth = priorWidth * 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)
|
||||
maxDelta = max(deltaWidth, deltaHeight) / 2
|
||||
|
||||
maxDelta = max(deltaWidth, deltaHeight) / 2
|
||||
|
||||
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):
|
||||
'''
|
||||
"""
|
||||
Create frisket stencil selection in a temp image to pass as source (corpus) to plugin resynthesizer,
|
||||
which does the substantive work.
|
||||
'''
|
||||
|
||||
if not pdb.gimp_item_is_layer(drawable):
|
||||
pdb.gimp_message(_("A layer must be active, not a channel."))
|
||||
return
|
||||
|
||||
"""
|
||||
|
||||
if not pdb.gimp_drawable_is_layer(drawable):
|
||||
pdb.gimp_message(_("A layer must be active, not a channel."))
|
||||
return
|
||||
|
||||
pdb.gimp_image_undo_group_start(orgImage)
|
||||
|
||||
|
||||
# copy original into temp for later use
|
||||
tempImage = pdb.gimp_image_duplicate(orgImage)
|
||||
|
||||
if not tempImage:
|
||||
raise RuntimeError, "Failed duplicate image"
|
||||
|
||||
'''
|
||||
|
||||
"""
|
||||
Prepare target: enlarge canvas and select the new, blank outer ring
|
||||
'''
|
||||
|
||||
"""
|
||||
|
||||
# Save original bounds to later select outer band
|
||||
pdb.gimp_selection_all(orgImage)
|
||||
selectAllPrior = pdb.gimp_selection_save(orgImage)
|
||||
|
||||
# Resize image alone doesn't resize layer, so resize layer also
|
||||
resizeImageCentered(orgImage, percentEnlargeParam)
|
||||
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.
|
||||
pdb.gimp_selection_invert(orgImage)
|
||||
|
||||
# Assert target image is ready.
|
||||
|
||||
'''
|
||||
|
||||
"""
|
||||
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.
|
||||
'''
|
||||
"""
|
||||
# Working with the original size.
|
||||
# Could be alpha channel transparency
|
||||
workLayer = pdb.gimp_image_get_active_layer(tempImage)
|
||||
|
||||
if not workLayer:
|
||||
raise RuntimeError, "Failed get active layer"
|
||||
|
||||
# Select outer band: select all, shrink
|
||||
pdb.gimp_selection_all(tempImage)
|
||||
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 !!
|
||||
# No need to crop corpus to save memory.
|
||||
|
||||
|
||||
# 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
|
||||
pdb.plug_in_resynthesizer(orgImage, drawable, 0,0,5, workLayer.ID, -1, -1, 0.0, 0.117, 16, 500)
|
||||
|
||||
# Clean up.
|
||||
pdb.plug_in_resynthesizer(
|
||||
orgImage, drawable, 0, 0, 5, workLayer.ID, -1, -1, 0.0, 0.117, 16, 500
|
||||
)
|
||||
|
||||
# Clean up.
|
||||
# Any errors now are moot.
|
||||
pdb.gimp_selection_none(orgImage)
|
||||
pdb.gimp_image_remove_channel(orgImage, selectAllPrior)
|
||||
pdb.gimp_image_undo_group_end(orgImage)
|
||||
pdb.gimp_displays_flush()
|
||||
|
||||
gimp.delete(tempImage) # Comment out to debug corpus creation.
|
||||
|
||||
|
||||
register(
|
||||
"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! "),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"Copyright 2009 Lloyd Konneker",
|
||||
"2009",
|
||||
N_("Uncrop..."),
|
||||
"RGB*, GRAY*",
|
||||
[
|
||||
(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)
|
||||
)
|
||||
"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! "
|
||||
),
|
||||
"Requires separate resynthesizer plugin.",
|
||||
"Lloyd Konneker",
|
||||
"Copyright 2009 Lloyd Konneker",
|
||||
"2009",
|
||||
N_("Uncrop..."),
|
||||
"RGB*, GRAY*",
|
||||
[
|
||||
(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),
|
||||
)
|
||||
|
||||
main()
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user