Permalink
Browse files

Arclayer: bend any layer into a circular arc.

  • Loading branch information...
1 parent a1f8317 commit 167b525ef9684853c6f650d7991437c066b74938 @akkana committed Aug 11, 2012
Showing with 140 additions and 0 deletions.
  1. +140 −0 arclayer.py
View
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+
+# arclayer.py: Gimp plug-in to warp one layer along an arc of a given radius.
+
+# Copyright 2002,2008 by Akkana Peck, http://www.shallowsky.com/software/
+# You may use and distribute this plug-in under the terms of the GPL.
+#
+# Thanks to Joao Bueno for some excellent tips on speeding up
+# pixel ops in gimp-python!
+
+import math
+import time
+from gimpfu import *
+from array import array
+
+def python_arc_layer(img, layer, radius, ontop) :
+ gimp.progress_init("Arcing layer" + layer.name + "...")
+
+ # Spinner passes an integer, which will upset later calculations
+ radius = float(radius)
+
+ pdb.gimp_image_undo_group_start(img)
+
+ layername = "arc " + layer.name
+ # Calculate the size for the new layer
+ theta2 = layer.width / radius / 2
+ newWidth = int(2 * radius * math.sin(theta2))
+ if theta2 <= 1.570795 : # PI/2: if we're doing less than a semicircle
+ newHeight = int(layer.height * math.cos(theta2)
+ + .5 * radius * math.sin(theta2) *
+ math.tan(theta2))
+ else :
+ newHeight = layer.height + int(radius * (1 - math.cos(theta2)))
+
+ #print "Old size: ",layer.width,"x",layer.height
+ #print "New size: ",newWidth,"x",newHeight
+ #print "r =", radius, ", theta/2 =", theta2
+
+ # Create the new layer:
+ destDrawable = gimp.Layer(img, layername, newWidth, newHeight,
+ layer.type, layer.opacity, layer.mode)
+ img.add_layer(destDrawable, 0)
+ xoff, yoff = layer.offsets
+ destDrawable.translate(xoff - (newWidth - layer.width)/2,
+ yoff - (newHeight - layer.height)/2)
+
+ # No need to clear the layer here --
+ # we'll initialize it to zero when we create the pixel array.
+
+ srcWidth, srcHeight = layer.width, layer.height
+ srcRgn = layer.get_pixel_rgn(0, 0, srcWidth, srcHeight,
+ False, False)
+ src_pixels = array("B", srcRgn[0:srcWidth, 0:srcHeight])
+
+ dstRgn = destDrawable.get_pixel_rgn(0, 0, newWidth, newHeight,
+ True, True)
+ p_size = len(srcRgn[0,0])
+ dest_pixels = array("B", "\x00" * (newWidth * newHeight * p_size))
+
+ # Finally, loop over the region:
+ for x in xrange(0, srcWidth - 1) :
+ for y in xrange(0, srcHeight) :
+ # Calculate new coordinates
+ phi = theta2 - x/radius
+ if ontop :
+ r = radius - y
+ newy = int(radius - r * math.cos(phi))
+ if (newy < 0) :
+ continue
+ else :
+ r = radius - layer.height + y
+ newy = newHeight \
+ + int(r * math.cos(phi) - radius)
+ if (newy > newHeight) :
+ continue
+ newx = int(newWidth/2 - r * math.sin(phi))
+
+ src_pos = (x + srcWidth * y) * p_size
+ dest_pos = (newx + newWidth * newy) * p_size
+
+ newval = src_pixels[src_pos: src_pos + p_size]
+ dest_pixels[dest_pos : dest_pos + p_size] = newval
+
+ # A fast (but not great) cheat to fill in holes:
+ # Write to two pixel above the new location
+ # and one pixel to the right of it.
+ # If that's a valid location, then later
+ # another set of coordinates will map to it,
+ # and overwrite this value; if not, then it
+ # would have been a hole, and this fills it.
+ if (newy < newHeight-1) :
+ next_row = dest_pos + p_size * newWidth
+ dest_pixels [next_row : next_row + p_size] \
+ = newval
+ if (newx < newWidth-1) :
+ next_col = dest_pos + p_size
+ dest_pixels [next_col : next_col + p_size] \
+ = newval
+
+ #print "Progress:", 100.0 * x / layer.width
+ # Docs say progress_update takes a percent,
+ # but it's really a fraction.
+ # GIMP whines "arclayer.py is updating the progress too often"
+ # but, well, tough. It's only ten times total.
+ progress = float(x)/layer.width
+ if (int(progress * 100) % 20 == 0) :
+ gimp.progress_update(progress)
+
+ # Copy the whole array back to the pixel region:
+ dstRgn[0:newWidth, 0:newHeight] = dest_pixels.tostring()
+
+ destDrawable.flush()
+ destDrawable.merge_shadow(True)
+ destDrawable.update(0, 0, newWidth,newHeight)
+
+ # Remove the old layer
+ #img.remove_layer(layer)
+ layer.visible = False
+
+ pdb.gimp_selection_none(img)
+ pdb.gimp_image_undo_group_end(img)
+
+register(
+ "python_fu_arc_layer",
+ "Bend a layer in an arc",
+ "Bend a layer in an arc",
+ "Akkana Peck",
+ "Akkana Peck",
+ "2002,2008",
+ "<Image>/Filters/Distorts/ArcLayer(py)...",
+ "*",
+ [
+ (PF_SPINNER, "Radius", "Arc Radius (pixels)",
+ 400, (0, 2000, 50)),
+ (PF_TOGGLE, "Top", "Top of circle?", 1),
+ ],
+ [],
+ python_arc_layer)
+
+main()

0 comments on commit 167b525

Please sign in to comment.