Fixes #382.
[redakcja.git] / apps / sorl / thumbnail / processors.py
1 from PIL import Image, ImageFilter, ImageChops
2 from sorl.thumbnail import utils
3 import re
4
5
6 def dynamic_import(names):
7     imported = []
8     for name in names:
9         # Use rfind rather than rsplit for Python 2.3 compatibility.
10         lastdot = name.rfind('.')
11         modname, attrname = name[:lastdot], name[lastdot + 1:]
12         mod = __import__(modname, {}, {}, [''])
13         imported.append(getattr(mod, attrname))
14     return imported
15
16
17 def get_valid_options(processors):
18     """
19     Returns a list containing unique valid options from a list of processors
20     in correct order.
21     """
22     valid_options = []
23     for processor in processors:
24         if hasattr(processor, 'valid_options'):
25             valid_options.extend([opt for opt in processor.valid_options
26                                   if opt not in valid_options])
27     return valid_options
28
29
30 def colorspace(im, requested_size, opts):
31     if 'bw' in opts and im.mode != "L":
32         im = im.convert("L")
33     elif im.mode not in ("L", "RGB", "RGBA"):
34         im = im.convert("RGB")
35     return im
36 colorspace.valid_options = ('bw',)
37
38
39 def autocrop(im, requested_size, opts):
40     if 'autocrop' in opts:
41         bw = im.convert("1")
42         bw = bw.filter(ImageFilter.MedianFilter)
43         # white bg
44         bg = Image.new("1", im.size, 255)
45         diff = ImageChops.difference(bw, bg)
46         bbox = diff.getbbox()
47         if bbox:
48             im = im.crop(bbox)
49     return im
50 autocrop.valid_options = ('autocrop',)
51
52
53 def scale_and_crop(im, requested_size, opts):
54     x, y = [float(v) for v in im.size]
55     xr, yr = [float(v) for v in requested_size]
56
57     if 'crop' in opts or 'max' in opts:
58         r = max(xr / x, yr / y)
59     else:
60         r = min(xr / x, yr / y)
61
62     if r < 1.0 or (r > 1.0 and 'upscale' in opts):
63         im = im.resize((int(x * r), int(y * r)), resample=Image.ANTIALIAS)
64
65     crop = opts.get('crop') or 'crop' in opts
66     if crop:
67         # Difference (for x and y) between new image size and requested size.
68         x, y = [float(v) for v in im.size]
69         dx, dy = (x - min(x, xr)), (y - min(y, yr))
70         if dx or dy:
71             # Center cropping (default).
72             ex, ey = dx / 2, dy / 2
73             box = [ex, ey, x - ex, y - ey]
74             # See if an edge cropping argument was provided.
75             edge_crop = (isinstance(crop, basestring) and
76                            re.match(r'(?:(-?)(\d+))?,(?:(-?)(\d+))?$', crop))
77             if edge_crop and filter(None, edge_crop.groups()):
78                 x_right, x_crop, y_bottom, y_crop = edge_crop.groups()
79                 if x_crop:
80                     offset = min(x * int(x_crop) / 100, dx)
81                     if x_right:
82                         box[0] = dx - offset
83                         box[2] = x - offset
84                     else:
85                         box[0] = offset
86                         box[2] = x - (dx - offset)
87                 if y_crop:
88                     offset = min(y * int(y_crop) / 100, dy)
89                     if y_bottom:
90                         box[1] = dy - offset
91                         box[3] = y - offset
92                     else:
93                         box[1] = offset
94                         box[3] = y - (dy - offset)
95             # See if the image should be "smart cropped".
96             elif crop == 'smart':
97                 left = top = 0
98                 right, bottom = x, y
99                 while dx:
100                     slice = min(dx, 10)
101                     l_sl = im.crop((0, 0, slice, y))
102                     r_sl = im.crop((x - slice, 0, x, y))
103                     if utils.image_entropy(l_sl) >= utils.image_entropy(r_sl):
104                         right -= slice
105                     else:
106                         left += slice
107                     dx -= slice
108                 while dy:
109                     slice = min(dy, 10)
110                     t_sl = im.crop((0, 0, x, slice))
111                     b_sl = im.crop((0, y - slice, x, y))
112                     if utils.image_entropy(t_sl) >= utils.image_entropy(b_sl):
113                         bottom -= slice
114                     else:
115                         top += slice
116                     dy -= slice
117                 box = (left, top, right, bottom)
118             # Finally, crop the image!
119             im = im.crop([int(v) for v in box])
120     return im
121 scale_and_crop.valid_options = ('crop', 'upscale', 'max')
122
123
124 def filters(im, requested_size, opts):
125     if 'detail' in opts:
126         im = im.filter(ImageFilter.DETAIL)
127     if 'sharpen' in opts:
128         im = im.filter(ImageFilter.SHARPEN)
129     return im
130 filters.valid_options = ('detail', 'sharpen')