mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-25 16:20:33 +03:00
Merge pull request #3242 from tsennott/feature-imageops-colorize-three-color
Add feature: ImageOps.colorize to support three colors
This commit is contained in:
commit
6652f7dcdb
|
@ -192,6 +192,16 @@ class PillowTestCase(unittest.TestCase):
|
|||
def assert_not_all_same(self, items, msg=None):
|
||||
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,
|
||||
travis=None, interpreter=None):
|
||||
# Skip if platform/travis matches, and
|
||||
|
|
BIN
Tests/images/bw_gradient.png
Normal file
BIN
Tests/images/bw_gradient.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 B |
|
@ -1,6 +1,7 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
|
||||
from PIL import ImageOps
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class TestImageOps(PillowTestCase):
|
||||
|
@ -94,6 +95,107 @@ class TestImageOps(PillowTestCase):
|
|||
newimg = ImageOps.scale(i, 0.5)
|
||||
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__':
|
||||
unittest.main()
|
||||
|
|
39
docs/releasenotes/5.3.0.rst
Normal file
39
docs/releasenotes/5.3.0.rst
Normal 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
|
||||
=============
|
||||
|
|
@ -6,6 +6,7 @@ Release Notes
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
5.3.0
|
||||
5.2.0
|
||||
5.1.0
|
||||
5.0.0
|
||||
|
|
|
@ -136,28 +136,87 @@ def autocontrast(image, cutoff=0, ignore=None):
|
|||
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**
|
||||
arguments should be RGB tuples; this function calculates a color
|
||||
wedge mapping all black pixels in the source image to the first
|
||||
color, and all white pixels to the second color.
|
||||
Colorize grayscale image.
|
||||
This function calculates a color wedge which maps all black pixels in
|
||||
the source image to the first color and all white pixels to the
|
||||
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 black: The color to use for black 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.
|
||||
"""
|
||||
|
||||
# Initial asserts
|
||||
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")
|
||||
white = _color(white, "RGB")
|
||||
if mid is not None:
|
||||
mid = _color(mid, "RGB")
|
||||
|
||||
# Empty lists for the mapping
|
||||
red = []
|
||||
green = []
|
||||
blue = []
|
||||
for i in range(256):
|
||||
red.append(black[0]+i*(white[0]-black[0])//255)
|
||||
green.append(black[1]+i*(white[1]-black[1])//255)
|
||||
blue.append(black[2]+i*(white[2]-black[2])//255)
|
||||
|
||||
# Create the low-end values
|
||||
for i in range(0, blackpoint):
|
||||
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")
|
||||
return _lut(image, red + green + blue)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user