Skip to content

Commit

Permalink
Merge pull request #2595 from wiredfool/issue_1911
Browse files Browse the repository at this point in the history
Image.Image.alpha_composite Added
  • Loading branch information
wiredfool committed Jul 1, 2017
2 parents 6e2a978 + 92b8db1 commit b9b5d39
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
48 changes: 48 additions & 0 deletions PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,54 @@ def paste(self, im, box=None, mask=None):
else:
self.im.paste(im, box)

def alpha_composite(self, im, dest=(0,0), source=(0,0)):
""" 'In-place' analog of Image.alpha_composite. Composites an image
onto this image.
:param im: image to composite over this one
:param dest: Optional 2 tuple (top, left) specifying the upper
left corner in this (destination) image.
:param source: Optional 2 (top, left) tuple for the upper left
corner in the overlay source image, or 4 tuple (top, left, bottom,
right) for the bounds of the source rectangle
Performance Note: Not currently implemented in-place in the core layer.
"""

if not isinstance(source, tuple):
raise ValueError("Source must be a tuple")
if not isinstance(dest, tuple):
raise ValueError("Destination must be a tuple")
if not len(source) in (2, 4):
raise ValueError("Source must be a 2 or 4-tuple")
if not len(dest) == 2:
raise ValueError("Destination must be a 2-tuple")
if min(source) < 0:
raise ValueError("Source must be non-negative")
if min(dest) < 0:
raise ValueError("Destination must be non-negative")

if len(source) == 2:
source = source + im.size

# over image, crop if it's not the whole thing.
if source == (0,0) + im.size:
overlay = im
else:
overlay = im.crop(source)

# target for the paste
box = dest + (dest[0] + overlay.width, dest[1] + overlay.height)

# destination image. don't copy if we're using the whole image.
if dest == (0,0) + self.size:
background = self
else:
background = self.crop(box)

result = alpha_composite(background, overlay)
self.paste(result, box)

def point(self, lut, mode=None):
"""
Maps this image through a lookup table or function.
Expand Down
38 changes: 38 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,44 @@ def test_alpha_composite(self):
img_colors = sorted(img.getcolors())
self.assertEqual(img_colors, expected_colors)

def test_alpha_inplace(self):
src = Image.new('RGBA', (128,128), 'blue')

over = Image.new('RGBA', (128,128), 'red')
mask = hopper('L')
over.putalpha(mask)

target = Image.alpha_composite(src, over)

# basic
full = src.copy()
full.alpha_composite(over)
self.assert_image_equal(full, target)

# with offset down to right
offset = src.copy()
offset.alpha_composite(over, (64, 64))
self.assert_image_equal(offset.crop((64, 64, 127, 127)),
target.crop((0, 0, 63, 63)))
self.assertEqual(offset.size, (128, 128))

# offset and crop
box = src.copy()
box.alpha_composite(over, (64, 64), (0, 0, 32, 32))
self.assert_image_equal(box.crop((64, 64, 96, 96)),
target.crop((0, 0, 32, 32)))
self.assert_image_equal(box.crop((96, 96, 128, 128)),
src.crop((0, 0, 32, 32)))
self.assertEqual(box.size, (128, 128))

# source point
source = src.copy()
source.alpha_composite(over, (32, 32), (32, 32, 96, 96))

self.assert_image_equal(source.crop((32, 32, 96, 96)),
target.crop((32, 32, 96, 96)))
self.assertEqual(source.size, (128, 128))

def test_registered_extensions_uninitialized(self):
# Arrange
Image._initialized = 0
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/Image.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ An instance of the :py:class:`~PIL.Image.Image` class has the following
methods. Unless otherwise stated, all methods return a new instance of the
:py:class:`~PIL.Image.Image` class, holding the resulting image.


.. automethod:: PIL.Image.Image.alpha_composite
.. automethod:: PIL.Image.Image.convert

The following example converts an RGB image (linearly calibrated according to
Expand Down

0 comments on commit b9b5d39

Please sign in to comment.