Merge pull request #95 from jdriscoll/new_processors

New processors
This commit is contained in:
Bryan Veloso 2012-02-12 13:54:12 -08:00
commit 424659bd07
2 changed files with 189 additions and 54 deletions

View file

@ -10,7 +10,7 @@ class Side(object):
ALL = (TOP, RIGHT, BOTTOM, LEFT)
def crop(img, bbox, sides=Side.ALL):
def _crop(img, bbox, sides=Side.ALL):
bbox = (
bbox[0] if Side.LEFT in sides else 0,
bbox[1] if Side.TOP in sides else 0,
@ -67,22 +67,123 @@ class TrimBorderColor(object):
bbox = diff.getbbox()
if bbox:
img = crop(img, bbox, self.sides)
img = _crop(img, bbox, self.sides)
return img
class BasicCrop(object):
"""Crops an image to the specified rectangular region.
"""
def __init__(self, x, y, width, height):
"""
:param x: The x position of the clipping box, in pixels.
:param y: The y position of the clipping box, in pixels.
:param width: The width position of the clipping box, in pixels.
:param height: The height position of the clipping box, in pixels.
"""
self.x = x
self.y = y
self.width = width
self.height = height
def process(self, img):
box = (self.x, self.y, self.x + self.width, self.y + self.height)
return img.crop(box)
class Crop(object):
"""
Crops an image , cropping it to the specified width and height
relative to the anchor.
"""
TOP_LEFT = 'tl'
TOP = 't'
TOP_RIGHT = 'tr'
BOTTOM_LEFT = 'bl'
BOTTOM = 'b'
BOTTOM_RIGHT = 'br'
CENTER = 'c'
LEFT = 'l'
RIGHT = 'r'
_ANCHOR_PTS = {
TOP_LEFT: (0, 0),
TOP: (0.5, 0),
TOP_RIGHT: (1, 0),
LEFT: (0, 0.5),
CENTER: (0.5, 0.5),
RIGHT: (1, 0.5),
BOTTOM_LEFT: (0, 1),
BOTTOM: (0.5, 1),
BOTTOM_RIGHT: (1, 1),
}
def __init__(self, width=None, height=None, anchor=None):
"""
:param width: The target width, in pixels.
:param height: The target height, in pixels.
:param anchor: Specifies which part of the image should be retained
when cropping. Valid values are:
- Crop.TOP_LEFT
- Crop.TOP
- Crop.TOP_RIGHT
- Crop.LEFT
- Crop.CENTER
- Crop.RIGHT
- Crop.BOTTOM_LEFT
- Crop.BOTTOM
- Crop.BOTTOM_RIGHT
You may also pass a tuple that indicates the percentages of excess
to be trimmed from each dimension. For example, ``(0, 0)``
corresponds to "top left", ``(0.5, 0.5)`` to "center" and ``(1, 1)``
to "bottom right". This is basically the same as using percentages
in CSS background positions.
"""
self.width = width
self.height = height
self.anchor = anchor
def process(self, img):
original_width, original_height = img.size
new_width, new_height = min(original_width, self.width), \
min(original_height, self.height)
trim_x, trim_y = original_width - new_width, \
original_height - new_height
# If the user passed in one of the string values, convert it to a
# percentage tuple.
anchor = self.anchor or Crop.CENTER
if anchor in Crop._ANCHOR_PTS.keys():
anchor = Crop._ANCHOR_PTS[anchor]
x = int(float(trim_x) * float(anchor[0]))
y = int(float(trim_y) * float(anchor[1]))
return BasicCrop(x, y, new_width, new_height).process(img)
class SmartCrop(object):
"""
Crop an image 'smartly' -- based on smart crop implementation from easy-thumbnails:
Crop an image to the specified dimensions, whittling away the parts of the
image with the least entropy.
Based on smart crop implementation from easy-thumbnails:
https://github.com/SmileyChris/easy-thumbnails/blob/master/easy_thumbnails/processors.py#L193
Smart cropping whittles away the parts of the image with the least entropy.
"""
def __init__(self, width=None, height=None):
"""
:param width: The target width, in pixels.
:param height: The target height, in pixels.
"""
self.width = width
self.height = height
@ -135,5 +236,4 @@ class SmartCrop(object):
box = (left, top, right, bottom)
img = img.crop(box)
return img

View file

@ -1,34 +1,64 @@
from imagekit.lib import Image
from .crop import SmartCrop as _SmartCrop
from . import crop
import warnings
class BasicResize(object):
"""
Resizes an image to the specified width and height.
"""
def __init__(self, width, height):
"""
:param width: The target width, in pixels.
:param height: The target height, in pixels.
"""
self.width = width
self.height = height
def process(self, img):
return img.resize((self.width, self.height), Image.ANTIALIAS)
class Cover(object):
"""
Resizes the image to the smallest possible size that will entirely cover the
provided dimensions. You probably won't be using this processor directly,
but it's used internally by ``Fill`` and ``SmartFill``.
"""
def __init__(self, width, height):
"""
:param width: The target width, in pixels.
:param height: The target height, in pixels.
"""
self.width, self.height = width, height
def process(self, img):
original_width, original_height = img.size
ratio = max(float(self.width) / original_width,
float(self.height) / original_height)
new_width, new_height = (int(original_width * ratio),
int(original_height * ratio))
return BasicResize(new_width, new_height).process(img)
class Fill(object):
"""
Resizes an image , cropping it to the specified width and height.
Resizes an image , cropping it to the exact specified width and height.
"""
TOP_LEFT = 'tl'
TOP = 't'
TOP_RIGHT = 'tr'
BOTTOM_LEFT = 'bl'
BOTTOM = 'b'
BOTTOM_RIGHT = 'br'
CENTER = 'c'
LEFT = 'l'
RIGHT = 'r'
_ANCHOR_PTS = {
TOP_LEFT: (0, 0),
TOP: (0.5, 0),
TOP_RIGHT: (1, 0),
LEFT: (0, 0.5),
CENTER: (0.5, 0.5),
RIGHT: (1, 0.5),
BOTTOM_LEFT: (0, 1),
BOTTOM: (0.5, 1),
BOTTOM_RIGHT: (1, 1),
}
TOP_LEFT = crop.Crop.TOP_LEFT
TOP = crop.Crop.TOP
TOP_RIGHT = crop.Crop.TOP_RIGHT
BOTTOM_LEFT = crop.Crop.BOTTOM_LEFT
BOTTOM = crop.Crop.BOTTOM
BOTTOM_RIGHT = crop.Crop.BOTTOM_RIGHT
CENTER = crop.Crop.CENTER
LEFT = crop.Crop.LEFT
RIGHT = crop.Crop.RIGHT
def __init__(self, width=None, height=None, anchor=None):
"""
@ -53,26 +83,30 @@ class Fill(object):
self.anchor = anchor
def process(self, img):
cur_width, cur_height = img.size
horizontal_anchor, vertical_anchor = Fill._ANCHOR_PTS[self.anchor or \
Fill.CENTER]
ratio = max(float(self.width) / cur_width, float(self.height) / cur_height)
resize_x, resize_y = ((cur_width * ratio), (cur_height * ratio))
crop_x, crop_y = (abs(self.width - resize_x), abs(self.height - resize_y))
x_diff, y_diff = (int(crop_x / 2), int(crop_y / 2))
box_left, box_right = {
0: (0, self.width),
0.5: (int(x_diff), int(x_diff + self.width)),
1: (int(crop_x), int(resize_x)),
}[horizontal_anchor]
box_upper, box_lower = {
0: (0, self.height),
0.5: (int(y_diff), int(y_diff + self.height)),
1: (int(crop_y), int(resize_y)),
}[vertical_anchor]
box = (box_left, box_upper, box_right, box_lower)
img = img.resize((int(resize_x), int(resize_y)), Image.ANTIALIAS).crop(box)
return img
img = Cover(self.width, self.height).process(img)
return crop.Crop(self.width, self.height,
anchor=self.anchor).process(img)
class SmartFill(object):
"""
The ``SmartFill`` processor is identical to ``Fill``, except that it uses
entropy to crop the image instead of a user-specified anchor point.
Internally, it simply runs the ``resize.Cover`` and ``crop.SmartCrop``
processors in series.
"""
def __init__(self, width, height):
"""
:param width: The target width, in pixels.
:param height: The target height, in pixels.
"""
self.width, self.height = width, height
def process(self, img):
img = Cover(self.width, self.height).process(img)
return crop.SmartCrop(self.width, self.height).process(img)
class Crop(Fill):
@ -94,8 +128,8 @@ class Fit(object):
:param upscale: A boolean value specifying whether the image should
be enlarged if its dimensions are smaller than the target
dimensions.
:param mat_color: If set, the target image size will be enforced and
the specified color will be used as background color to pad the image.
:param mat_color: If set, the target image size will be enforced and the
specified color will be used as a background color to pad the image.
"""
self.width = width
@ -117,7 +151,8 @@ class Fit(object):
int(round(cur_height * ratio)))
if (cur_width > new_dimensions[0] or cur_height > new_dimensions[1]) or \
self.upscale:
img = img.resize(new_dimensions, Image.ANTIALIAS)
img = BasicResize(new_dimensions[0],
new_dimensions[1]).process(img)
if self.mat_color:
new_img = Image.new('RGBA', (self.width, self.height), self.mat_color)
new_img.paste(img, ((self.width - img.size[0]) / 2, (self.height - img.size[1]) / 2))
@ -125,7 +160,7 @@ class Fit(object):
return img
class SmartCrop(_SmartCrop):
class SmartCrop(crop.SmartCrop):
def __init__(self, *args, **kwargs):
warnings.warn('The SmartCrop processor has been moved to'
' `imagekit.processors.crop.SmartCrop`, where it belongs.',