Repository: python-pillow/Pillow

Issue:
Autocontrast cutoff improvement
<!--
Thank you for reporting an issue.

Follow these guidelines to ensure your issue is handled properly.

If you have a ...

1.  General question: consider asking the question on Stack Overflow
    with the python-imaging-library tag:

    * https://stackoverflow.com/questions/tagged/python-imaging-library

    Do not ask a question in both places.

    If you think you have found a bug or have an unexplained exception
    then file a bug report here.

2.  Bug report: include a self-contained, copy-pastable example that
    generates the issue if possible. Be concise with code posted.
    Guidelines on how to provide a good bug report:

    * https://stackoverflow.com/help/mcve

    Bug reports which follow these guidelines are easier to diagnose,
    and are often handled much more quickly.

3.  Feature request: do a quick search of existing issues
    to make sure this has not been asked before.

We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->

### What did you do?
I am trying to improve image contrast.

### What did you expect to happen?
I expect to be able to control the cutoff values for each side of the histograms. 

### What actually happened?
The cutoff value of PIL.ImageOps.autocontrast() cutoff the histogram from both side. What should happen is the user should be given the custom control to specify the percentage cutoff of both the right and the left side separately
### What are your OS, Python and Pillow versions?

* OS: Windows 10
* Python: Python 3.8.2 
* Pillow: PIL 7.1.2

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->

```python
from PIL import Image, ImageOps

img = Image.open("ky.jpg").convert("L")

edited = ImageOps.autocontrast(img, cutoff=4

edited.save("pil_1.jpg")
```



Discussion:

Resolving patch:
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -70,7 +70,9 @@ def autocontrast(image, cutoff=0, ignore=None):
     becomes white (255).
 
     :param image: The image to process.
-    :param cutoff: How many percent to cut off from the histogram.
+    :param cutoff: The percent to cut off from the histogram on the low and
+                   high ends. Either a tuple of (low, high), or a single
+                   number for both.
     :param ignore: The background pixel value (use None for no background).
     :return: An image.
     """
@@ -88,12 +90,14 @@ def autocontrast(image, cutoff=0, ignore=None):
                     h[ix] = 0
         if cutoff:
             # cut off pixels from both ends of the histogram
+            if not isinstance(cutoff, tuple):
+                cutoff = (cutoff, cutoff)
             # get number of pixels
             n = 0
             for ix in range(256):
                 n = n + h[ix]
             # remove cutoff% pixels from the low end
-            cut = n * cutoff // 100
+            cut = n * cutoff[0] // 100
             for lo in range(256):
                 if cut > h[lo]:
                     cut = cut - h[lo]
@@ -103,8 +107,8 @@ def autocontrast(image, cutoff=0, ignore=None):
                     cut = 0
                 if cut <= 0:
                     break
-            # remove cutoff% samples from the hi end
-            cut = n * cutoff // 100
+            # remove cutoff% samples from the high end
+            cut = n * cutoff[1] // 100
             for hi in range(255, -1, -1):
                 if cut > h[hi]:
                     cut = cut - h[hi]


Test patch:
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -300,3 +300,14 @@ def check(orientation_im):
                     "Tests/images/hopper_orientation_" + str(i) + ext
                 ) as orientation_im:
                     check(orientation_im)
+
+
+def test_autocontrast_cutoff():
+    # Test the cutoff argument of autocontrast
+    with Image.open("Tests/images/bw_gradient.png") as img:
+
+        def autocontrast(cutoff):
+            return ImageOps.autocontrast(img, cutoff).histogram()
+
+        assert autocontrast(10) == autocontrast((10, 10))
+        assert autocontrast(10) != autocontrast((1, 10))