mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-04-22 05:54:42 +00:00
commit
424659bd07
2 changed files with 189 additions and 54 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
Loading…
Reference in a new issue