Merge pull request #3242 from tsennott/feature-imageops-colorize-three-color

Add feature: ImageOps.colorize to support three colors
This commit is contained in:
Hugo 2018-07-11 20:27:00 +03:00 committed by GitHub
commit 6652f7dcdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 220 additions and 9 deletions

View File

@ -192,6 +192,16 @@ class PillowTestCase(unittest.TestCase):
def assert_not_all_same(self, items, msg=None): def assert_not_all_same(self, items, msg=None):
self.assertFalse(items.count(items[0]) == len(items), msg) self.assertFalse(items.count(items[0]) == len(items), msg)
def assert_tuple_approx_equal(self, actuals, targets, threshold, msg):
"""Tests if actuals has values within threshold from targets"""
value = True
for i, target in enumerate(targets):
value *= (target - threshold <= actuals[i] <= target + threshold)
self.assertTrue(value,
msg + ': ' + repr(actuals) + ' != ' + repr(targets))
def skipKnownBadTest(self, msg=None, platform=None, def skipKnownBadTest(self, msg=None, platform=None,
travis=None, interpreter=None): travis=None, interpreter=None):
# Skip if platform/travis matches, and # Skip if platform/travis matches, and

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

View File

@ -1,6 +1,7 @@
from helper import unittest, PillowTestCase, hopper from helper import unittest, PillowTestCase, hopper
from PIL import ImageOps from PIL import ImageOps
from PIL import Image
class TestImageOps(PillowTestCase): class TestImageOps(PillowTestCase):
@ -94,6 +95,107 @@ class TestImageOps(PillowTestCase):
newimg = ImageOps.scale(i, 0.5) newimg = ImageOps.scale(i, 0.5)
self.assertEqual(newimg.size, (25, 25)) self.assertEqual(newimg.size, (25, 25))
def test_colorize_2color(self):
# Test the colorizing function with 2-color functionality
# Open test image (256px by 10px, black to white)
im = Image.open("Tests/images/bw_gradient.png")
im = im.convert("L")
# Create image with original 2-color functionality
im_test = ImageOps.colorize(im, 'red', 'green')
# Test output image (2-color)
left = (0, 1)
middle = (127, 1)
right = (255, 1)
self.assert_tuple_approx_equal(im_test.getpixel(left),
(255, 0, 0),
threshold=1,
msg='black test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(middle),
(127, 63, 0),
threshold=1,
msg='mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right),
(0, 127, 0),
threshold=1,
msg='white test pixel incorrect')
def test_colorize_2color_offset(self):
# Test the colorizing function with 2-color functionality and offset
# Open test image (256px by 10px, black to white)
im = Image.open("Tests/images/bw_gradient.png")
im = im.convert("L")
# Create image with original 2-color functionality with offsets
im_test = ImageOps.colorize(im,
black='red',
white='green',
blackpoint=50,
whitepoint=100)
# Test output image (2-color) with offsets
left = (25, 1)
middle = (75, 1)
right = (125, 1)
self.assert_tuple_approx_equal(im_test.getpixel(left),
(255, 0, 0),
threshold=1,
msg='black test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(middle),
(127, 63, 0),
threshold=1,
msg='mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right),
(0, 127, 0),
threshold=1,
msg='white test pixel incorrect')
def test_colorize_3color_offset(self):
# Test the colorizing function with 3-color functionality and offset
# Open test image (256px by 10px, black to white)
im = Image.open("Tests/images/bw_gradient.png")
im = im.convert("L")
# Create image with new three color functionality with offsets
im_test = ImageOps.colorize(im,
black='red',
white='green',
mid='blue',
blackpoint=50,
whitepoint=200,
midpoint=100)
# Test output image (3-color) with offsets
left = (25, 1)
left_middle = (75, 1)
middle = (100, 1)
right_middle = (150, 1)
right = (225, 1)
self.assert_tuple_approx_equal(im_test.getpixel(left),
(255, 0, 0),
threshold=1,
msg='black test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(left_middle),
(127, 0, 127),
threshold=1,
msg='low-mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(middle),
(0, 0, 255),
threshold=1,
msg='mid incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right_middle),
(0, 63, 127),
threshold=1,
msg='high-mid test pixel incorrect')
self.assert_tuple_approx_equal(im_test.getpixel(right),
(0, 127, 0),
threshold=1,
msg='white test pixel incorrect')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -0,0 +1,39 @@
5.3.0
-----
API Changes
===========
Deprecations
^^^^^^^^^^^^
These version constants have been deprecated. ``VERSION`` will be removed in
Pillow 6.0.0, and ``PILLOW_VERSION`` will be removed after that.
* ``PIL.VERSION`` (old PIL version 1.1.7)
* ``PIL.PILLOW_VERSION``
* ``PIL.Image.VERSION``
* ``PIL.Image.PILLOW_VERSION``
Use ``PIL.__version__`` instead.
API Additions
=============
ImageOps.colorize
^^^^^^^^^^^^^^^^^
Previously ``ImageOps.colorize`` only supported two-color mapping with
``black`` and ``white`` arguments being mapped to 0 and 255 respectively.
Now it supports three-color mapping with the optional ``mid`` parameter, and
the positions for all three color arguments can each be optionally specified
(``blackpoint``, ``whitepoint`` and ``midpoint``).
For example, with all optional arguments::
ImageOps.colorize(im, black=(32, 37, 79), white='white', mid=(59, 101, 175),
blackpoint=15, whitepoint=240, midpoint=100)
Other Changes
=============

View File

@ -6,6 +6,7 @@ Release Notes
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
5.3.0
5.2.0 5.2.0
5.1.0 5.1.0
5.0.0 5.0.0

View File

@ -136,28 +136,87 @@ def autocontrast(image, cutoff=0, ignore=None):
return _lut(image, lut) return _lut(image, lut)
def colorize(image, black, white): def colorize(image, black, white, mid=None, blackpoint=0,
whitepoint=255, midpoint=127):
""" """
Colorize grayscale image. The **black** and **white** Colorize grayscale image.
arguments should be RGB tuples; this function calculates a color This function calculates a color wedge which maps all black pixels in
wedge mapping all black pixels in the source image to the first the source image to the first color and all white pixels to the
color, and all white pixels to the second color. second color. If **mid** is specified, it uses three-color mapping.
The **black** and **white** arguments should be RGB tuples or color names;
optionally you can use three-color mapping by also specifying **mid**.
Mapping positions for any of the colors can be specified
(e.g. **blackpoint**), where these parameters are the integer
value corresponding to where the corresponding color should be mapped.
These parameters must have logical order, such that
**blackpoint** <= **midpoint** <= **whitepoint** (if **mid** is specified).
:param image: The image to colorize. :param image: The image to colorize.
:param black: The color to use for black input pixels. :param black: The color to use for black input pixels.
:param white: The color to use for white input pixels. :param white: The color to use for white input pixels.
:param mid: The color to use for midtone input pixels.
:param blackpoint: an int value [0, 255] for the black mapping.
:param whitepoint: an int value [0, 255] for the white mapping.
:param midpoint: an int value [0, 255] for the midtone mapping.
:return: An image. :return: An image.
""" """
# Initial asserts
assert image.mode == "L" assert image.mode == "L"
if mid is None:
assert 0 <= blackpoint <= whitepoint <= 255
else:
assert 0 <= blackpoint <= midpoint <= whitepoint <= 255
# Define colors from arguments
black = _color(black, "RGB") black = _color(black, "RGB")
white = _color(white, "RGB") white = _color(white, "RGB")
if mid is not None:
mid = _color(mid, "RGB")
# Empty lists for the mapping
red = [] red = []
green = [] green = []
blue = [] blue = []
for i in range(256):
red.append(black[0]+i*(white[0]-black[0])//255) # Create the low-end values
green.append(black[1]+i*(white[1]-black[1])//255) for i in range(0, blackpoint):
blue.append(black[2]+i*(white[2]-black[2])//255) red.append(black[0])
green.append(black[1])
blue.append(black[2])
# Create the mapping (2-color)
if mid is None:
range_map = range(0, whitepoint - blackpoint)
for i in range_map:
red.append(black[0] + i * (white[0] - black[0]) // len(range_map))
green.append(black[1] + i * (white[1] - black[1]) // len(range_map))
blue.append(black[2] + i * (white[2] - black[2]) // len(range_map))
# Create the mapping (3-color)
else:
range_map1 = range(0, midpoint - blackpoint)
range_map2 = range(0, whitepoint - midpoint)
for i in range_map1:
red.append(black[0] + i * (mid[0] - black[0]) // len(range_map1))
green.append(black[1] + i * (mid[1] - black[1]) // len(range_map1))
blue.append(black[2] + i * (mid[2] - black[2]) // len(range_map1))
for i in range_map2:
red.append(mid[0] + i * (white[0] - mid[0]) // len(range_map2))
green.append(mid[1] + i * (white[1] - mid[1]) // len(range_map2))
blue.append(mid[2] + i * (white[2] - mid[2]) // len(range_map2))
# Create the high-end values
for i in range(0, 256 - whitepoint):
red.append(white[0])
green.append(white[1])
blue.append(white[2])
# Return converted image
image = image.convert("RGB") image = image.convert("RGB")
return _lut(image, red + green + blue) return _lut(image, red + green + blue)