mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 17:24:31 +03:00
Add center and translate option to Image.rotate.
This commit is contained in:
parent
f3751a1f3a
commit
90077b3975
57
PIL/Image.py
57
PIL/Image.py
|
@ -1555,7 +1555,7 @@ class Image(object):
|
|||
|
||||
return self._new(self.im.resize(size, resample))
|
||||
|
||||
def rotate(self, angle, resample=NEAREST, expand=0):
|
||||
def rotate(self, angle, resample=NEAREST, expand=0, center=None, translate=None):
|
||||
"""
|
||||
Returns a rotated copy of this image. This method returns a
|
||||
copy of this image, rotated the given number of degrees counter
|
||||
|
@ -1569,10 +1569,14 @@ class Image(object):
|
|||
(cubic spline interpolation in a 4x4 environment).
|
||||
If omitted, or if the image has mode "1" or "P", it is
|
||||
set :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`.
|
||||
:param center: Optional center of rotation (a 2-tuple). Origin is
|
||||
the upper left corner. Default is the center of the image.
|
||||
:param translate: An optional final translation.
|
||||
:param expand: Optional expansion flag. If true, expands the output
|
||||
image to make it large enough to hold the entire rotated image.
|
||||
If false or omitted, make the output image the same size as the
|
||||
input image.
|
||||
input image. Note that the expand flag assumes rotation around
|
||||
the center and no translation.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
||||
|
@ -1588,32 +1592,59 @@ class Image(object):
|
|||
if angle == 270 and expand:
|
||||
return self.transpose(ROTATE_270)
|
||||
|
||||
# Calculate the affine matrix. Note that this is the reverse
|
||||
# transformation (from destination image to source) because we
|
||||
# want to interpolate the (discrete) destination pixel from
|
||||
# the local area around the (floating) source pixel.
|
||||
|
||||
# The matrix we actually want (note that it operates from the right):
|
||||
# (1, 0, tx) (1, 0, cx) ( cos a, sin a, 0) (1, 0, -cx)
|
||||
# (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy)
|
||||
# (0, 0, 1) (0, 0, 1) ( 0, 0, 1) (0, 0, 1)
|
||||
|
||||
# The reverse matrix is thus:
|
||||
# (1, 0, cx) ( cos -a, sin -a, 0) (1, 0, -cx) (1, 0, -tx)
|
||||
# (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty)
|
||||
# (0, 0, 1) ( 0, 0, 1) (0, 0, 1) (0, 0, 1)
|
||||
|
||||
# In any case, the final translation may be updated at the end to
|
||||
# compensate for the expand flag.
|
||||
|
||||
w, h = self.size
|
||||
|
||||
if translate is None:
|
||||
translate = [0, 0]
|
||||
if center is None:
|
||||
center = [w / 2.0, h / 2.0]
|
||||
|
||||
angle = - math.radians(angle)
|
||||
matrix = [
|
||||
round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0,
|
||||
round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0
|
||||
]
|
||||
|
||||
def transform(x, y, matrix=matrix):
|
||||
]
|
||||
def transform(x, y, matrix):
|
||||
(a, b, c, d, e, f) = matrix
|
||||
return a*x + b*y + c, d*x + e*y + f
|
||||
matrix[2], matrix[5] = transform(-center[0] - translate[0], -center[1] - translate[1], matrix)
|
||||
matrix[2] += center[0]
|
||||
matrix[5] += center[1]
|
||||
|
||||
w, h = self.size
|
||||
if expand:
|
||||
# calculate output size
|
||||
xx = []
|
||||
yy = []
|
||||
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
||||
x, y = transform(x, y)
|
||||
x, y = transform(x, y, matrix)
|
||||
xx.append(x)
|
||||
yy.append(y)
|
||||
w = int(math.ceil(max(xx)) - math.floor(min(xx)))
|
||||
h = int(math.ceil(max(yy)) - math.floor(min(yy)))
|
||||
nw = int(math.ceil(max(xx)) - math.floor(min(xx)))
|
||||
nh = int(math.ceil(max(yy)) - math.floor(min(yy)))
|
||||
|
||||
# adjust center
|
||||
x, y = transform(w / 2.0, h / 2.0)
|
||||
matrix[2] = self.size[0] / 2.0 - x
|
||||
matrix[5] = self.size[1] / 2.0 - y
|
||||
# We multiply a translation matrix from the right. Because of its
|
||||
# special form, this is the same as taking the image of the translation vector
|
||||
# as new translation vector.
|
||||
matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix)
|
||||
w, h = nw, nh
|
||||
|
||||
return self.transform((w, h), AFFINE, matrix, resample)
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ from PIL import Image
|
|||
class TestImageRotate(PillowTestCase):
|
||||
|
||||
def test_rotate(self):
|
||||
def rotate(im, mode, angle):
|
||||
out = im.rotate(angle)
|
||||
def rotate(im, mode, angle, center=None, translate=None):
|
||||
out = im.rotate(angle, center=center, translate=translate)
|
||||
self.assertEqual(out.mode, mode)
|
||||
self.assertEqual(out.size, im.size) # default rotate clips output
|
||||
out = im.rotate(angle, expand=1)
|
||||
out = im.rotate(angle, center=center, translate=translate, expand=1)
|
||||
self.assertEqual(out.mode, mode)
|
||||
if angle % 180 == 0:
|
||||
self.assertEqual(out.size, im.size)
|
||||
|
@ -31,6 +31,10 @@ class TestImageRotate(PillowTestCase):
|
|||
im = Image.new('RGB',(0,0))
|
||||
rotate(im, im.mode, angle)
|
||||
|
||||
rotate(im, im.mode, 45, center=(0, 0))
|
||||
rotate(im, im.mode, 45, translate=(im.size[0]/2, 0))
|
||||
rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0]/2, 0))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue
Block a user