Chris Beaven avatar Chris Beaven committed 2f27579

Rather than just centering the image before cropping, also allow cropping from an edge.

Comments (0)

Files changed (2)

     Crop the source image height or width to exactly match the requested
     thumbnail size (the default is to proportionally resize the source image
     to fit within the requested thumbnail size).
+    
+    By default, the image is centered before being cropped. To crop from the
+    edges, pass a comma separated string containing the ``x`` and ``y``
+    percentage offsets (negative values go from the right/bottom). Some
+    examples follow:
+    
+    * ``crop="0,0"`` will crop from the top and right.
+
+    * ``crop="-0,-10"`` will crop from bottom with a 10% offset and the right.
+    
+    * ``crop=",0"`` will crop from the top, keeping the default centering for
+      the x axis.
 
 max
     Will resize the image to the same size as the *crop* option but it

sorl/thumbnail/processors.py

 from PIL import Image, ImageFilter, ImageChops
+import re
 
 
 def dynamic_import(names):
     if r < 1.0 or (r > 1.0 and 'upscale' in opts):
         im = im.resize((int(x * r), int(y * r)), resample=Image.ANTIALIAS)
 
-    if 'crop' in opts:
+    crop = opts.get('crop') or 'crop' in opts
+    if crop:
+        # Difference (for x and y) between new image size and requested size.
         x, y = [float(v) for v in im.size]
-        ex, ey = (x - min(x, xr)) / 2, (y - min(y, yr)) / 2
-        if ex or ey:
-            im = im.crop((int(ex), int(ey), int(x - ex), int(y - ey)))
+        dx, dy = (x - min(x, xr)), (y - min(y, yr))
+        if dx or dy:
+            # Center cropping (default).
+            ex, ey = dx / 2, dy / 2
+            box = [ex, ey, x - ex, y - ey]
+            # See if an edge cropping argument was provided. 
+            edge_crop = (isinstance(crop, basestring) and
+                           re.match(r'(?:(-?)(\d+))?,(?:(-?)(\d+))?$', crop))
+            if edge_crop and filter(None, edge_crop.groups()):
+                x_right, x_crop, y_bottom, y_crop = edge_crop.groups()
+                if x_crop:
+                    offset = min(x * int(x_crop) / 100, dx)
+                    if x_right:
+                        box[0] = dx - offset
+                        box[2] = x - offset
+                    else:
+                        box[0] = offset
+                        box[2] = x - (dx - offset)
+                if y_crop:
+                    offset = min(y * int(y_crop) / 100, dy)
+                    if y_bottom:
+                        box[1] = dy - offset
+                        box[3] = y - offset
+                    else:
+                        box[1] = offset
+                        box[3] = y - (dy - offset)
+            #assert False, (box, (dx, dy), (xr, yr))
+            im = im.crop([int(v) for v in box])
     return im
 scale_and_crop.valid_options = ('crop', 'upscale', 'max')
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.