4

With PIL, I want to draw a rotated square on an image, by specifying the square side length, and the rotation angle. The square should be white, and the background grey. For example, the following image has a rotation of 45 degrees:

enter image description here

The only way I know how to do rotations in PIL is by rotating the entire image. But if I start with the following image:

enter image description here

And then rotate it by 45 degrees, I get this:

enter image description here

That method just introduces black parts to fill in the "undefined" region of the image.

How can I rotate just the square?

The code to generate my original square (the second figure) is as follows:

from PIL import Image

image = Image.new('L', (100, 100), 127)
pixels = image.load()

for i in range(30, image.size[0] - 30):
    for j in range(30, image.size[1] - 30):
        pixels[i, j] = 255

rotated_image = image.rotate(45)
rotated_image.save("rotated_image.bmp")
Karnivaurus
  • 22,823
  • 57
  • 147
  • 247
  • I'm not quite sure what you're asking for. A "before" and "after" image might be useful here. – Kevin Jan 12 '16 at 15:47
  • I've now updated the question accordingly! – Karnivaurus Jan 12 '16 at 16:01
  • Possible duplicate (or at least this looks to answer the question) of http://stackoverflow.com/questions/5252170/specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand – Foon Jan 12 '16 at 16:04

2 Answers2

9

If all you want to do is draw a solid color square at some arbitrary angle, you can use trigonometry to calculate the vertices of the rotated square, then draw it with polygon.

import math
from PIL import Image, ImageDraw

#finds the straight-line distance between two points
def distance(ax, ay, bx, by):
    return math.sqrt((by - ay)**2 + (bx - ax)**2)

#rotates point `A` about point `B` by `angle` radians clockwise.
def rotated_about(ax, ay, bx, by, angle):
    radius = distance(ax,ay,bx,by)
    angle += math.atan2(ay-by, ax-bx)
    return (
        round(bx + radius * math.cos(angle)),
        round(by + radius * math.sin(angle))
    )

image = Image.new('L', (100, 100), 127)
draw = ImageDraw.Draw(image)

square_center = (50,50)
square_length = 40

square_vertices = (
    (square_center[0] + square_length / 2, square_center[1] + square_length / 2),
    (square_center[0] + square_length / 2, square_center[1] - square_length / 2),
    (square_center[0] - square_length / 2, square_center[1] - square_length / 2),
    (square_center[0] - square_length / 2, square_center[1] + square_length / 2)
)

square_vertices = [rotated_about(x,y, square_center[0], square_center[1], math.radians(45)) for x,y in square_vertices]

draw.polygon(square_vertices, fill=255)

image.save("output.png")

Result:

enter image description here

Kevin
  • 74,910
  • 12
  • 133
  • 166
2

Let's generalize to a rectangle:

  • length l in x and width w in y.
  • using a "rotation matrix".

Rotation code:

import math

def makeRectangle(l, w, theta, offset=(0,0)):
    c, s = math.cos(theta), math.sin(theta)
    rectCoords = [(l/2.0, w/2.0), (l/2.0, -w/2.0), (-l/2.0, -w/2.0), (-l/2.0, w/2.0)]
    return [(c*x-s*y+offset[0], s*x+c*y+offset[1]) for (x,y) in rectCoords]

Drawing code:

from PIL import Image
from PIL import ImageDraw
import math

L=512; W=512
image = Image.new("1", (L, W))
draw = ImageDraw.Draw(image)

vertices = makeRectangle(100, 200, 45*math.pi/180, offset=(L/2, W/2))
draw.polygon(vertices, fill=1)

image.save("test.png")
Sparkler
  • 2,581
  • 1
  • 22
  • 41