#!/usr/bin/env python ''' Gimp plugin. Create new image having texture synthesized from the selection. Works best if selection is natural (fractal). Can work with man-made regular texture. Works worst with man-made, structured but not regular, symbols. Sometimes called rendering a texture. Requires resynthesizer plug-in. Author: lloyd konneker, lkk, bootch at nc.rr.com Version: 1.0 lkk 7/15/2010 Initial version License: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. The GNU Public License is available at http://www.gnu.org/copyleft/gpl.html The effect for users: Similar to "Fill resynthesized pattern" except: - here the arguments are reversed: you select a texture and create a new image instead of selecting an area and choosing a pattern. - here the result is less random (since Fill resynthesized adds noise.) Different from tiling since: - seamless and irregular pattern The continuum of randomness versus speed: - Filters.Map.Tile is fast but has seams and regular pattern (even if you use "Make Seamless" first.) - Filter.Render.Texture a tile followed by tiling is seamless but still has regularity. - 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 an image that you can then use to tile with especially if 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. ''' from gimpfu import * 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. new_width = pdb.gimp_image_width(image) * resize_ratio new_height = pdb.gimp_image_height(image) * resize_ratio new_type = pdb.gimp_image_base_type(image) # same as source else : # Resize new image in proportion to selection in source new_width = (lrx - ulx) * resize_ratio new_height = (lry - uly) * resize_ratio new_type = pdb.gimp_image_base_type(image) # same as source print "new type", new_type new_image = gimp.Image(new_width, new_height, new_type) new_drawable = pdb.gimp_layer_new(new_image, new_width, new_height, new_type, "Texture", 100, NORMAL_MODE) print "Image type", new_image.base_type, "Drawable type", new_drawable.type pdb.gimp_image_add_layer(new_image, new_drawable, 0) return new_image, new_drawable 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. 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 using new resynthesizer with animation for debugging, uncomment this # gimp.Display(new_image) # gimp.displays_flush() # debug if not new_drawable: raise RuntimeError, "Failed create layer." ''' 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 : pdb.gimp_image_crop(temp_image, lrx - ulx, lry - uly, ulx, uly) # Flatten (source everything visible) and to activate a layer in temp pdb.gimp_image_flatten(temp_image) 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 # !!! The crux: call the 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. # TODO a quality setting # This is for the current resynthesizer, which has a deficient Python API and needs and inverted selection pdb.gimp_selection_invert(temp_image) pdb.plug_in_resynthesizer(new_image, new_drawable, htile, vtile, 0, work_layer.ID, -1, -1, 0.0, 0.117, 9, 200) # !!! This if for the next version of resynthesizer, with clean Python interface and uninverted selection # pdb.plug_in_resynthesizer2(new_image, new_drawable, htile, vtile, 0, work_layer, None, None, 0.0, 0.117, 9, 200) # Show the results gimp.Display(new_image) pdb.gimp_displays_flush() # Clean up. pdb.gimp_image_delete(temp_image) pdb.gimp_image_undo_enable(image) pdb.gimp_image_undo_enable(new_image) return new_image register( "python_fu_render_texture", "Create a new image with texture from the current image or selection. Optionally, create image edges suited for further, seamless tiling. Requires separate resynthesizer plug-in.", "New image is the same scale but seamless and irregular. Use 'Map>Tile' for less randomness. Use 'Edit>Fill resynthesized pattern' for more randomness. ", "Lloyd Konneker (bootch nc.rr.com)", "Copyright 2010 Lloyd Konneker", "2010", "/Filters/Render/_Texture...", "RGB*, GRAY*", [ # Spinner is digital and linear, slider is analog but exponential (PF_SPINNER, "resize_ratio", "Ratio of size of new image to source selection", 2, (0.5, 10, 0.5)), (PF_TOGGLE, "make_tile", "Make new image edges suitable for seamless tiling", 0 ) ], [(PF_IMAGE, "new_image", "New, synthesized texture.")], render_texture ) main()