Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preserve_tone option to autocontrast #5350

Merged
merged 16 commits into from Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 45 additions & 1 deletion Tests/test_imageops.py
Expand Up @@ -29,6 +29,7 @@ def test_sanity():
ImageOps.autocontrast(hopper("L"), cutoff=(2, 10))
ImageOps.autocontrast(hopper("L"), ignore=[0, 255])
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"), "black", "white")
Expand Down Expand Up @@ -336,7 +337,7 @@ def test_autocontrast_mask_toy_input():
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
with Image.open("Tests/images/iptc.jpg") as img:

Expand All @@ -362,3 +363,46 @@ def test_auto_contrast_mask_real_input():
threshold=2,
msg="autocontrast without mask pixel incorrect",
)


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 list histogram entries equal
elejke marked this conversation as resolved.
Show resolved Hide resolved
# and should be 10% of the image pixels (+-, because integers)
elejke marked this conversation as resolved.
Show resolved Hide resolved
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)


def test_autocontrast_preserve_onecolor():
def _test_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=0, preserve_tone=True
) # single color 10 cutoff
radarhere marked this conversation as resolved.
Show resolved Hide resolved
assert_image_equal(img, out)

_test_one_color((255, 255, 255))
_test_one_color((127, 255, 0))
_test_one_color((127, 127, 127))
_test_one_color((0, 0, 0))
9 changes: 7 additions & 2 deletions src/PIL/ImageOps.py
Expand Up @@ -61,7 +61,7 @@ def _lut(image, lut):
# 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
histogram of the input image (or mask region), removes ``cutoff`` percent of the
Expand All @@ -77,9 +77,14 @@ def autocontrast(image, cutoff=0, ignore=None, mask=None):
:param mask: Histogram used in contrast operation is computed using pixels
within the mask. If no mask is given the entire image is used
for histogram computation.
:param preserve_tone: Preserve image tone in Photoshop-like style autocontrast.
elejke marked this conversation as resolved.
Show resolved Hide resolved
:return: An image.
"""
histogram = image.histogram(mask)
if preserve_tone:
histogram = image.convert("L").histogram(mask)
else:
histogram = image.histogram(mask)

lut = []
for layer in range(0, len(histogram), 256):
h = histogram[layer : layer + 256]
Expand Down