Merge pull request #5350 from elejke/master

Add preserve_tone option to autocontrast
This commit is contained in:
Andrew Murray 2021-03-30 07:59:57 +11:00 committed by GitHub
commit b0b4fee796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 3 deletions

View File

@ -29,6 +29,7 @@ def test_sanity():
ImageOps.autocontrast(hopper("L"), cutoff=(2, 10)) ImageOps.autocontrast(hopper("L"), cutoff=(2, 10))
ImageOps.autocontrast(hopper("L"), ignore=[0, 255]) ImageOps.autocontrast(hopper("L"), ignore=[0, 255])
ImageOps.autocontrast(hopper("L"), mask=hopper("L")) ImageOps.autocontrast(hopper("L"), mask=hopper("L"))
ImageOps.autocontrast(hopper("L"), preserve_tone=True)
ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
ImageOps.colorize(hopper("L"), "black", "white") ImageOps.colorize(hopper("L"), "black", "white")
@ -336,7 +337,7 @@ def test_autocontrast_mask_toy_input():
assert ImageStat.Stat(result_nomask).median == [128] assert ImageStat.Stat(result_nomask).median == [128]
def test_auto_contrast_mask_real_input(): def test_autocontrast_mask_real_input():
# Test the autocontrast with a rectangular mask # Test the autocontrast with a rectangular mask
with Image.open("Tests/images/iptc.jpg") as img: with Image.open("Tests/images/iptc.jpg") as img:
@ -362,3 +363,52 @@ def test_auto_contrast_mask_real_input():
threshold=2, threshold=2,
msg="autocontrast without mask pixel incorrect", msg="autocontrast without mask pixel incorrect",
) )
def test_autocontrast_preserve_tone():
def autocontrast(mode, preserve_tone):
im = hopper(mode)
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
assert autocontrast("RGB", True) != autocontrast("RGB", False)
assert autocontrast("L", True) == autocontrast("L", False)
def test_autocontrast_preserve_gradient():
gradient = Image.linear_gradient("L")
# test with a grayscale gradient that extends to 0,255.
# Should be a noop.
out = ImageOps.autocontrast(gradient, cutoff=0, preserve_tone=True)
assert_image_equal(gradient, out)
# cutoff the top and bottom
# autocontrast should make the first and last histogram entries equal
# and, with rounding, should be 10% of the image pixels
out = ImageOps.autocontrast(gradient, cutoff=10, preserve_tone=True)
hist = out.histogram()
assert hist[0] == hist[-1]
assert hist[-1] == 256 * round(256 * 0.10)
# in rgb
img = gradient.convert("RGB")
out = ImageOps.autocontrast(img, cutoff=0, preserve_tone=True)
assert_image_equal(img, out)
@pytest.mark.parametrize(
"color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0))
)
def test_autocontrast_preserve_one_color(color):
img = Image.new("RGB", (10, 10), color)
# single color images shouldn't change
out = ImageOps.autocontrast(img, cutoff=0, preserve_tone=True)
assert_image_equal(img, out) # single color, no cutoff
# even if there is a cutoff
out = ImageOps.autocontrast(
img, cutoff=10, preserve_tone=True
) # single color 10 cutoff
assert_image_equal(img, out)

View File

@ -83,6 +83,15 @@ be specified through a keyword argument::
im.save("out.tif", icc_profile=...) im.save("out.tif", icc_profile=...)
ImageOps.autocontrast: preserve_tone
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The default behaviour of :py:meth:`~PIL.ImageOps.autocontrast` is to normalize
separate histograms for each color channel, changing the tone of the image. The new
``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram
for all channels.
Security Security
======== ========

View File

@ -61,7 +61,7 @@ def _lut(image, lut):
# actions # actions
def autocontrast(image, cutoff=0, ignore=None, mask=None): def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False):
""" """
Maximize (normalize) image contrast. This function calculates a Maximize (normalize) image contrast. This function calculates a
histogram of the input image (or mask region), removes ``cutoff`` percent of the histogram of the input image (or mask region), removes ``cutoff`` percent of the
@ -77,9 +77,17 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None):
:param mask: Histogram used in contrast operation is computed using pixels :param mask: Histogram used in contrast operation is computed using pixels
within the mask. If no mask is given the entire image is used within the mask. If no mask is given the entire image is used
for histogram computation. for histogram computation.
:param preserve_tone: Preserve image tone in Photoshop-like style autocontrast.
.. versionadded:: 8.2.0
:return: An image. :return: An image.
""" """
histogram = image.histogram(mask) if preserve_tone:
histogram = image.convert("L").histogram(mask)
else:
histogram = image.histogram(mask)
lut = [] lut = []
for layer in range(0, len(histogram), 256): for layer in range(0, len(histogram), 256):
h = histogram[layer : layer + 256] h = histogram[layer : layer + 256]