X-Git-Url: https://git.mdrn.pl/redakcja.git/blobdiff_plain/04b05946ab640eaf9135a73772b704dd41c323e7..717c14c014e3c988bcb392599a47ffa92630da78:/apps/sorl/thumbnail/processors.py diff --git a/apps/sorl/thumbnail/processors.py b/apps/sorl/thumbnail/processors.py new file mode 100644 index 00000000..a6c17416 --- /dev/null +++ b/apps/sorl/thumbnail/processors.py @@ -0,0 +1,130 @@ +from PIL import Image, ImageFilter, ImageChops +from sorl.thumbnail import utils +import re + + +def dynamic_import(names): + imported = [] + for name in names: + # Use rfind rather than rsplit for Python 2.3 compatibility. + lastdot = name.rfind('.') + modname, attrname = name[:lastdot], name[lastdot + 1:] + mod = __import__(modname, {}, {}, ['']) + imported.append(getattr(mod, attrname)) + return imported + + +def get_valid_options(processors): + """ + Returns a list containing unique valid options from a list of processors + in correct order. + """ + valid_options = [] + for processor in processors: + if hasattr(processor, 'valid_options'): + valid_options.extend([opt for opt in processor.valid_options + if opt not in valid_options]) + return valid_options + + +def colorspace(im, requested_size, opts): + if 'bw' in opts and im.mode != "L": + im = im.convert("L") + elif im.mode not in ("L", "RGB", "RGBA"): + im = im.convert("RGB") + return im +colorspace.valid_options = ('bw',) + + +def autocrop(im, requested_size, opts): + if 'autocrop' in opts: + bw = im.convert("1") + bw = bw.filter(ImageFilter.MedianFilter) + # white bg + bg = Image.new("1", im.size, 255) + diff = ImageChops.difference(bw, bg) + bbox = diff.getbbox() + if bbox: + im = im.crop(bbox) + return im +autocrop.valid_options = ('autocrop',) + + +def scale_and_crop(im, requested_size, opts): + x, y = [float(v) for v in im.size] + xr, yr = [float(v) for v in requested_size] + + if 'crop' in opts or 'max' in opts: + r = max(xr / x, yr / y) + else: + r = min(xr / x, yr / y) + + if r < 1.0 or (r > 1.0 and 'upscale' in opts): + im = im.resize((int(x * r), int(y * r)), resample=Image.ANTIALIAS) + + 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] + 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) + # See if the image should be "smart cropped". + elif crop == 'smart': + left = top = 0 + right, bottom = x, y + while dx: + slice = min(dx, 10) + l_sl = im.crop((0, 0, slice, y)) + r_sl = im.crop((x - slice, 0, x, y)) + if utils.image_entropy(l_sl) >= utils.image_entropy(r_sl): + right -= slice + else: + left += slice + dx -= slice + while dy: + slice = min(dy, 10) + t_sl = im.crop((0, 0, x, slice)) + b_sl = im.crop((0, y - slice, x, y)) + if utils.image_entropy(t_sl) >= utils.image_entropy(b_sl): + bottom -= slice + else: + top += slice + dy -= slice + box = (left, top, right, bottom) + # Finally, crop the image! + im = im.crop([int(v) for v in box]) + return im +scale_and_crop.valid_options = ('crop', 'upscale', 'max') + + +def filters(im, requested_size, opts): + if 'detail' in opts: + im = im.filter(ImageFilter.DETAIL) + if 'sharpen' in opts: + im = im.filter(ImageFilter.SHARPEN) + return im +filters.valid_options = ('detail', 'sharpen')