0

main.py

from PIL import Image, ImageDraw, ImageFont

base_img = Image.new("RGBA", (100, 100), (0, 255, 0))
txt = 'Sample <del color=red>text<del>'
rotate = 120
font = ImageFont.load_default(12)
SS = 4

temp_draw = ImageDraw.Draw(Image.new("RGBA", (max(1, int(0 * SS)), max(1, int(0 * SS))),(0, 0, 0, 0)))

bbox = temp_draw.textbbox((0, 0), txt, font)
txt_w, txt_h = bbox[2] - bbox[0], bbox[3] - bbox[1]

temp_img = Image.new("RGBA", (max(1, int(txt_w * SS)), max(1, int(txt_h * SS))), (0, 0, 0, 0))
temp_draw = ImageDraw.Draw(temp_img)

temp_draw.text((0, 0), txt, (0, 0, 0), font)

if 0 <= rotate <= 360:
    temp_img = temp_img.rotate(-rotate, expand=True, resample=Image.BICUBIC)
    b = temp_img.getbbox()
    w, h = temp_img.size

    if 0 <= rotate <= 90:
        x0 = -b[0]
        y0 = -b[1]
    elif 90 < rotate <= 180:
        x0 = -b[0]
        y0 = -b[1]
    elif 180 < rotate <= 270:
        x0 = -(w - b[2])
        y0 = -(h - b[3])
    elif 270 < rotate < 360:
        x0 = -b[0]
        y0 = -(h - b[3])

base_img.paste(temp_img, (x0, y0), temp_img)
base_img.show()

In short:

  • There's a problem with text shifting when rotating.
  • When the text rotates 0-90°, everything works fine. Otherwise, the text shifts somewhere, even extending beyond the image boundaries.
  • The text itself rotates correctly; the problem is precisely the incorrect text positioning (the shift needs to be adjusted).

So far, it only works with the 0-90 degree branch. I also tweaked a few things, and it seems to be working almost perfectly for 91-180: y0 is definitely correct, but something else needs to be subtracted from x0...

6
  • We can't run your code to see problem. You could add images with results which you get and with descriptions where is wrong postion. Or create minimal working code so we could see it. Commented Sep 26 at 20:41
  • it doesn't rotate Text but rectangle image with text. But this text may have different "margins" on sides (ie. because Y and y may have different space/margin at the bottom) and it may need to find correct center point at before rotation and resize image to have the same margins on all sides. See image on page Text anchors Commented Sep 26 at 20:52
  • @furas I tried working with text anchors, but it didn't seem to help. Here is my project with all dependencies on GitHub: github.com/aaanananasss/draw-problem I checked everything, so it should start, and it will be easier to solve the problem (I left what is really important and necessary). Commented Sep 27 at 21:36
  • better create minimal working code and put it in question. Nobody will download code from repo and test many line of code which are not important in this problem. Commented Sep 28 at 10:12
  • did you try to use rotate(..., center=(0,0))? It should rotate around top left corner of text. Doc:Image.rotate Commented Sep 28 at 10:28

2 Answers 2

1

If I understand you want to rotate around the bottom left corner.

I would use geometry for this instead of calculations.

First I describe it using images.
To make it more visible I use gray background instead of transparent.
And I add red dot in center of rotation.

(I use real_height = ascender + descender instead of value from font.getbbox(text) so it puts text in the same place when it has letters like y j g (which add space at the bottom) and when it doesn't have y j g)

  1. Create image with text

original text

  1. Create new image with size width*2,height*2 and put text so bottom left corner is in the center of new image (so red dot is in new_width//2, new_height//2)

text with resized background

  1. Rotate (120 degrees) around center of new image
    (so red dot is still in the center -rotate_width//2, -rotate_height//2)

rotated image

  1. Put it on final image using offset -rotate_width//2, -rotate_height//2

put on final imagee

And the same without gray background

enter image description here


Here minimal working code which generates all of the images.

import sys
from PIL import Image, ImageFont, ImageDraw

TEST = True

if TEST:
    TRANSPARENT = "gray"  # for tests
else:
    TRANSPARENT = (0, 0, 0, 0)  # (x,x,x,0)
    # TRANSPARENT = (0, 0, 0, 128)  # (x,x,x,0)

# --- get angle as parameter

if len(sys.argv) == 1:
    angle = 45 + 90
else:
    angle = int(sys.argv[1])

# --- create real height (the same when text has `y` or not) ---

# [Text anchors - Pillow (PIL Fork) 11.3.0 documentation](https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html#text-anchors)

font = ImageFont.truetype("Arial.ttf", 40)

ascender, descender = font.getmetrics()
real_height = ascender + descender
print(f"{real_height = }")

# ---

text = "Stackoverflow"

x0, y0, x1, y1 = font.getbbox(text)
box_w = x1 - x0
box_h = y1 - y0
print("box xy:", x0, y0, x1, y1)
print("box w, h:", box_w, box_h)

# --- create text with image ---

txt = Image.new("RGBA", (box_w, real_height), TRANSPARENT)
txt_draw = ImageDraw.Draw(txt)
txt_draw.text((-x0, 0), text, font=font)

txt.save("output-1-text.png")

# --- resize background ---

for_rotation = Image.new(
    "RGBA",
    (box_w * 2, real_height * 2),
    TRANSPARENT,
)
for_rotation.paste(txt, (box_w, 0))

# - add red dot -
if TEST:
    for_rotation_draw = ImageDraw.Draw(for_rotation)
    for_rotation_draw.circle((box_w, real_height), 2, fill="red")
    for_rotation_draw.circle((box_w, real_height), 5, outline="red")
    for_rotation_draw.line(
        (box_w - 10, real_height, box_w + 10, real_height), fill="red", width=1
    )
    for_rotation_draw.line(
        (box_w, real_height - 10, box_w, real_height + 10), fill="red", width=1
    )

for_rotation.save("output-2-for-rotation.png")

# --- rotate image ---

rotated = for_rotation.rotate(-angle, expand=True)

rotated_w, rotated_h = rotated.size
print("rotated:", rotated_w, rotated_h)

rotated.save(f"output-3-rotated-{angle}.png")

# --- put text on final image ---

offset_w = -rotated_w // 2
offset_h = -rotated_h // 2

final = Image.new("RGBA", (box_w, box_w), "green")
# combined = Image.alpha_composite(new, txt)  # different function without destination position
final.alpha_composite(rotated, dest=(offset_w, offset_h))

final.save(f"output-4-final-{angle}.png")

Angle: 135 (45+90)

enter image description here

Angle: 30

enter image description here

Angle: 45

enter image description here

Angle: 60

enter image description here

Angle: 90

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

0

Can not test your code due to the missing parts, but I guess it's a similar problem as when you need to rotate a bounding box on an image in object recognition projects. You might need to look up some info about that.

Next is a piece of code I once used in such a project. I used the cv2 package.

The "point_motor" was the center of the bounding box. It needed to be "rotated" & "shifted" as the image rotated. This worked with any angle and any image size (e.g. width <> height).

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    if rotation > 0:
        (height, width) = img.shape[:2]
        (center_x, center_y) = (width // 2, height // 2)

        M = cv2.getRotationMatrix2D((center_x, center_y), rotation, 1)
        cos = np.abs(M[0, 0])
        sin = np.abs(M[0, 1])
        nW = int((height * sin) + (width * cos))
        nH = int((height * cos) + (width * sin))
        M[0, 2] += (nW / 2) - center_x
        M[1, 2] += (nH / 2) - center_y

        if point_motor.size > 0:
            point_motor = np.dot(M, [point_motor[0], point_motor[1], 1])
        img = cv2.warpAffine(src=img, M=M, dsize=(nW, nH))

1 Comment

Yes, I know that in the current code the text is just an image (temp_img) and I am trying to adjust its x0, y0 offset to fit onto the base image. I've attached clearer examples and generally working files to my GitHub project: github.com/aaanananasss/draw-problem , where you can see the images (I'm not allowed on StackOverflow due to my rank) and related files.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.