Repository: python-pillow/Pillow

Issue:
ImageOps.expand distorts image and converts png to grayscale
<!--
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?
Resize an image and add a black border

### What did you expect to happen?
Image would be resized with a black border

### What actually happened?
Image is distorted converted to grayscale, resized and border added

### What are your OS, Python and Pillow versions?

* OS: Fedora 33
* Python: 3.8.6
* Pillow: 8.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.
-->

```shell
git clone https://github.com/hampusborgos/country-flags.git
cd country-flags/png1000px
```

```python
from PIL import Image, ImageOps
i = Image.open('./bo.png')
i = i.resize((128,96))

# File saves correctly
i.save('./test.png')

i = ImageOps.expand(i, border=10, fill='black')

# File gets converted to grayscale and is distorted
i.save('./test.png')
```


Discussion:
The bug is because the image is in P mode. If you are interested in an immediate solution, convert it to RGB.
```python
from PIL import Image, ImageOps
i = Image.open('./bo.png')
i = i.resize((128,96))

# File saves correctly
i.save('./test.png')

i = ImageOps.expand(i.convert("RGB"), border=10, fill='black')

# File gets converted to grayscale and is distorted
i.save('./test.png')
```
Thanks! Would this still be classified as a bug? I think it could be considered one?
Yes, this is still a bug that we should address.
I faced the same problem and solved it by converting it to RGB, Thanks.

Resolving patch:
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -399,7 +399,7 @@ def expand(image, border=0, fill=0):
         out.paste(image, (left, top))
 
         draw = ImageDraw.Draw(out)
-        draw.rectangle((0, 0, width, height), outline=color, width=border)
+        draw.rectangle((0, 0, width - 1, height - 1), outline=color, width=border)
     else:
         out = Image.new(image.mode, (width, height), color)
         out.paste(image, (left, top))


Test patch:
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -161,7 +161,13 @@ def test_expand_palette():
     im_expanded = ImageOps.expand(im, 10, (255, 0, 0))
 
     px = im_expanded.convert("RGB").load()
-    assert px[0, 0] == (255, 0, 0)
+    for b in range(10):
+        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 y in range(im_expanded.height):
+            assert px[b, x] == (255, 0, 0)
+            assert px[b, im_expanded.width - 1 - b] == (255, 0, 0)
 
     im_cropped = im_expanded.crop(
         (10, 10, im_expanded.width - 10, im_expanded.height - 10)