Repository: python-pillow/Pillow

Issue:
ImageOps.expand error with tuple border
<!--
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?
Call ImageOps.expand with a tuple `border`.

### What did you expect to happen?
Input image will be expanded.

### What actually happened?
It raises a TypeError that says in draw_rectangle function, `width` requires an integer but got a tuple.

### What are your OS, Python and Pillow versions?

* OS:  Ubuntu
* Python:  3.7.5
* Pillow:  8.3.1

<!--
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.
-->

I find in PR #5552 ImageOps.expand was modified and add two lines of code to draw the output, which are not necessary.
I guess if it is code for debug. If not, please notice that `border` may be a tuple and can not be passed to draw.rectangle as `width`.

```python
def expand(image, border=0, fill=0):
    """
    Add border to the image
    :param image: The image to expand.
    :param border: Border width, in pixels.
    :param fill: Pixel fill value (a color value).  Default is 0 (black).
    :return: An image.
    """
    left, top, right, bottom = _border(border)
    width = left + image.size[0] + right
    height = top + image.size[1] + bottom
    color = _color(fill, image.mode)
    if image.mode == "P" and image.palette:
        out = Image.new(image.mode, (width, height))
        out.putpalette(image.palette)
        out.paste(image, (left, top))

        draw = ImageDraw.Draw(out)
        draw.rectangle((0, 0, width, height), outline=color, width=border) # TypeError from here
    else:
        out = Image.new(image.mode, (width, height), color)
        out.paste(image, (left, top))
    return out
```



Discussion:
@radarhere may know this.

Resolving patch:
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -21,7 +21,7 @@
 import operator
 import re
 
-from . import Image, ImageDraw
+from . import Image
 
 #
 # helpers
@@ -395,15 +395,16 @@ def expand(image, border=0, fill=0):
     height = top + image.size[1] + bottom
     color = _color(fill, image.mode)
     if image.mode == "P" and image.palette:
-        out = Image.new(image.mode, (width, height))
-        out.putpalette(image.palette)
-        out.paste(image, (left, top))
-
-        draw = ImageDraw.Draw(out)
-        draw.rectangle((0, 0, width - 1, height - 1), outline=color, width=border)
+        image.load()
+        palette = image.palette.copy()
+        if isinstance(color, tuple):
+            color = palette.getcolor(color)
     else:
-        out = Image.new(image.mode, (width, height), color)
-        out.paste(image, (left, top))
+        palette = None
+    out = Image.new(image.mode, (width, height), color)
+    if palette:
+        out.putpalette(palette.palette)
+    out.paste(image, (left, top))
     return out
 
 


Test patch:
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -156,23 +156,31 @@ def test_scale():
     assert newimg.size == (25, 25)
 
 
-def test_expand_palette():
-    im = Image.open("Tests/images/p_16.tga")
-    im_expanded = ImageOps.expand(im, 10, (255, 0, 0))
-
-    px = im_expanded.convert("RGB").load()
-    for b in range(10):
+@pytest.mark.parametrize("border", (10, (1, 2, 3, 4)))
+def test_expand_palette(border):
+    with Image.open("Tests/images/p_16.tga") as im:
+        im_expanded = ImageOps.expand(im, border, (255, 0, 0))
+
+        if isinstance(border, int):
+            left = top = right = bottom = border
+        else:
+            left, top, right, bottom = border
+        px = im_expanded.convert("RGB").load()
         for x in range(im_expanded.width):
-            assert px[x, b] == (255, 0, 0)
-            assert px[x, im_expanded.height - 1 - b] == (255, 0, 0)
+            for b in range(top):
+                assert px[x, b] == (255, 0, 0)
+            for b in range(bottom):
+                assert px[x, im_expanded.height - 1 - b] == (255, 0, 0)
         for y in range(im_expanded.height):
-            assert px[b, x] == (255, 0, 0)
-            assert px[b, im_expanded.width - 1 - b] == (255, 0, 0)
+            for b in range(left):
+                assert px[b, y] == (255, 0, 0)
+            for b in range(right):
+                assert px[im_expanded.width - 1 - b, y] == (255, 0, 0)
 
-    im_cropped = im_expanded.crop(
-        (10, 10, im_expanded.width - 10, im_expanded.height - 10)
-    )
-    assert_image_equal(im_cropped, im)
+        im_cropped = im_expanded.crop(
+            (left, top, im_expanded.width - right, im_expanded.height - bottom)
+        )
+        assert_image_equal(im_cropped, im)
 
 
 def test_colorize_2color():