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:
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...
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)
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)-rotate_width//2, -rotate_height//2)-rotate_width//2, -rotate_height//2And the same without gray background
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)
Angle: 30
Angle: 45
Angle: 60
Angle: 90