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

Wrong width when creating arbitrary polygon #7104

Closed
miltonllera opened this issue Apr 21, 2023 · 4 comments
Closed

Wrong width when creating arbitrary polygon #7104

miltonllera opened this issue Apr 21, 2023 · 4 comments

Comments

@miltonllera
Copy link

miltonllera commented Apr 21, 2023

What did you do?

I am trying to create a dataset containing Pentomino shapes for some experiment.

To define the shapes, I specify the vertices in the path. Since the shapes are composed of 5 squares in different configurations, distance between the corners must be uniform. In this case I have made each side be 2 units.

I plot them using Pillow, optinally performing some transformation of the input using Matplotlib's Affine2D functionality.

What did you expect to happen?

I expected the sides to be all of equal length.

What actually happened?

Pillow (or matplotlib) seems to add a couple of pixels to some sides and this creates a misalignment in the shape. This misalignment is magnified when performing transformations such as scaling (see example).

As an example, notice the right 'arm' in the 'R' shape used in the example or the dip in the 'U' (currently commented out in the example code).

I have tried manipulating the points ever so slightly but they never land in the right location. At first I thought I was going crazy because of how subtle it is, but for what I need to do, this slight variation is not ideal.

I have seen similar issues (367 and 3747), but they seemed to be solved...

What are your OS, Python and Pillow versions?

  • OS: Ubuntu 20.10
  • Python: 3.10.9
  • Pillow: 9.5.0
"""
https://en.wikipedia.org/wiki/Pentomino
"""

import numpy as np
from PIL import Image, ImageDraw
from matplotlib.path import Path
from matplotlib.transforms import Affine2D


height, width = 60, 60

# R
coordinates = np.array([
    [4, 8, 8, 6, 6, 4, 4, 2, 2, 4],
    [2, 2, 4, 4, 8, 8, 6, 6, 4, 4],
]).T

# U
# coordinates = np.array([
#     [2, 4, 4, 6, 6, 8, 8, 2],
#     [3, 3, 5, 5, 3, 3, 7, 7],
# ]).T

coordinates = coordinates * (width // 10, height // 10)

path = Path(coordinates)

# transform = (
#     Affine2D().translate(-0.5 * width, -0.5 * height) +
#     Affine2D().scale(0.5) +
#     Affine2D().rotate_deg(90.) +
#     Affine2D().translate(0.5 * width, 0.5 * height)
# )

# path = transform.transform_path(path)

canvas = Image.new('RGB', (height, width), (0, 0, 0))
draw = ImageDraw.Draw(canvas)

color = (255, 0, 0)
draw.polygon([tuple(v) for v in path.vertices], fill=color)

image = Image.new('RGB', (height + 4, width + 4), color=(0, 0, 0))
image.paste(canvas, (2, 2))

image.save('example.png')
@radarhere
Copy link
Member

radarhere commented Apr 21, 2023

So, this is the shape that your code draws.

If I insert

print([tuple(v) for v in path.vertices])

I get

[(24.0, 12.0), (48.0, 12.0), (48.0, 24.0), (36.0, 24.0), (36.0, 48.0), (24.0, 48.0), (24.0, 36.0), (12.0, 36.0), (12.0, 24.0), (24.0, 24.0)]

You are drawing a polygon with (12, 24) as the leftmost point and (48, 24) as the rightmost point, and I think expecting it to be 36 pixels across because 48-12=36?

If I change fill to outline, and draw the individual pixels of the vertices, it might become clearer what is happening.

draw.polygon([tuple(v) for v in path.vertices], outline=color)

image = Image.new('RGB', (height + 4, width + 4), color=(0, 0, 0))
image.paste(canvas, (2, 2))
for (x, y) in [tuple(v) for v in path.vertices]:
    image.putpixel((int(x+2), int(y+2)), (0, 255, 0))

Pillow is drawing a polygon connecting all of those pixels, and joining them to make a shape. Thus, it is including the vertices, and instead drawing a shape that is 37 pixels across.

For a simpler example, drawing a filled polygon from [(1, 1), (2, 1), (2, 2), (1, 2)] would result in a 2 by 2 rectangle, because the points are being included.

@miltonllera
Copy link
Author

Ah I see. I in order to get the desired result I must subtract one unit length from vertices that define the right/bottom sides (or top/left), right?

@radarhere
Copy link
Member

One pixel, yes.

@miltonllera
Copy link
Author

Great, thanks for the help! I am closing this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants