How to make these PIL text centered properly
Question:
For context, I’m trying to make a spinnergif. The text options here are just for show, by right i wish to make the number of options flexible.
I’m trying to make the text centered nicely within each pieslice, but for some reason only some are centered but some are not. This is the code snippet for that creates a gif of my spinnner/spinwheel:
import math
import random
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
DIMENSIONS = (500, 500)
CENTER = (250, 250)
RADIUS = 200
NUM_FRAMES = 50
MASK_IMG = Image.open('mask.png')
SPINNER_CENTER_IMG = Image.open('spinner_center.png')
TRIANGLE_IMG = Image.open('triangle.png')
class SpinnerGifMaker:
def __init__(self, options):
self.options = options
colors = []
for _ in enumerate(options):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
a = 128
color = (r, g, b, a)
colors.append(color)
self.colors = colors
frame_list = []
for i in range(NUM_FRAMES):
angle = i * -50
frame = self.getSpinnerFrame(angle)
frame_list.append(frame)
frame_list[0].save('spinner.gif', format='GIF', append_images=frame_list[1:], save_all=True, duration=100,
loop=0)
def getSpinnerFrame(self, angle):
spinner_img = Image.new('RGB', DIMENSIONS, color=(255, 255, 255, 0))
spinner_draw = ImageDraw.Draw(spinner_img, 'RGBA')
num_sections = len(self.options)
for i, option in enumerate(self.options):
start_angle = i * (360 / num_sections)
end_angle = (i + 1) * (360 / num_sections)
color = self.colors[i]
spinner_draw.pieslice(xy=((CENTER[0] - RADIUS, CENTER[1] - RADIUS), (CENTER[0] + RADIUS, CENTER[1] +
RADIUS)),
start=start_angle,
end=end_angle, fill=color, outline='black')
font = ImageFont.truetype("arial.ttf", 30)
_, _, text_width, text_height = spinner_draw.textbbox((0, 0), option, font=font)
section_center_angle = (start_angle + end_angle) / 2
section_center_x = CENTER[0] + RADIUS * 0.6 * math.cos(section_center_angle * math.pi / 180)
section_center_y = CENTER[1] + RADIUS * 0.6 * math.sin(section_center_angle * math.pi / 180)
text_center_x = section_center_x - text_width / 2
text_center_y = section_center_y - text_height / 2
text_angle = 180 - section_center_angle
print(i, option, text_angle, text_width, text_height)
text_img = Image.new('RGBA', (text_width, text_height), color=(255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_img)
text_draw.text((0, 0), option, fill=(0, 0, 0), font=font)
text_img = text_img.rotate(text_angle, expand=True)
spinner_img.paste(text_img, (int(text_center_x), int(text_center_y)), text_img)
spinner_img = spinner_img.rotate(angle, center=CENTER)
spinner_img.putalpha(MASK_IMG)
spinner_img.paste(SPINNER_CENTER_IMG, mask=SPINNER_CENTER_IMG)
spinner_img.paste(TRIANGLE_IMG, mask=TRIANGLE_IMG)
return spinner_img
SpinnerGifMaker(["hi", "play", "sleep", "run", "dance", "eat", "fly", "study"])
and this are some of the frames:
the picture shows some options like ‘eat’, ‘dance’, ‘run’ and ‘study’ are noticeably not properly centered, but i’m not sure why.
I tried adjsuting the center values and all but no matter what there will always be some text options that are not centered nicely.
Answers:
When you rotate the text using expand=True
, the size of text_img
changes. You’ll need to compute the text_center_{x,y}
using the updated size:
text_img = text_img.rotate(text_angle, expand=True)
text_width, text_height = text_img.size
text_center_x = section_center_x - text_width / 2
text_center_y = section_center_y - text_height / 2
We can see more clearly what’s going on by using a translucent background to the text:
That improves things – maybe enough for your purposes? – but the text may not be vertically centered for all labels because the default text anchor uses the ascender rather than the rendered text height. You can specify anchor="lt"
to .textbbox
and .text
to change this:
For context, I’m trying to make a spinnergif. The text options here are just for show, by right i wish to make the number of options flexible.
I’m trying to make the text centered nicely within each pieslice, but for some reason only some are centered but some are not. This is the code snippet for that creates a gif of my spinnner/spinwheel:
import math
import random
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
DIMENSIONS = (500, 500)
CENTER = (250, 250)
RADIUS = 200
NUM_FRAMES = 50
MASK_IMG = Image.open('mask.png')
SPINNER_CENTER_IMG = Image.open('spinner_center.png')
TRIANGLE_IMG = Image.open('triangle.png')
class SpinnerGifMaker:
def __init__(self, options):
self.options = options
colors = []
for _ in enumerate(options):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
a = 128
color = (r, g, b, a)
colors.append(color)
self.colors = colors
frame_list = []
for i in range(NUM_FRAMES):
angle = i * -50
frame = self.getSpinnerFrame(angle)
frame_list.append(frame)
frame_list[0].save('spinner.gif', format='GIF', append_images=frame_list[1:], save_all=True, duration=100,
loop=0)
def getSpinnerFrame(self, angle):
spinner_img = Image.new('RGB', DIMENSIONS, color=(255, 255, 255, 0))
spinner_draw = ImageDraw.Draw(spinner_img, 'RGBA')
num_sections = len(self.options)
for i, option in enumerate(self.options):
start_angle = i * (360 / num_sections)
end_angle = (i + 1) * (360 / num_sections)
color = self.colors[i]
spinner_draw.pieslice(xy=((CENTER[0] - RADIUS, CENTER[1] - RADIUS), (CENTER[0] + RADIUS, CENTER[1] +
RADIUS)),
start=start_angle,
end=end_angle, fill=color, outline='black')
font = ImageFont.truetype("arial.ttf", 30)
_, _, text_width, text_height = spinner_draw.textbbox((0, 0), option, font=font)
section_center_angle = (start_angle + end_angle) / 2
section_center_x = CENTER[0] + RADIUS * 0.6 * math.cos(section_center_angle * math.pi / 180)
section_center_y = CENTER[1] + RADIUS * 0.6 * math.sin(section_center_angle * math.pi / 180)
text_center_x = section_center_x - text_width / 2
text_center_y = section_center_y - text_height / 2
text_angle = 180 - section_center_angle
print(i, option, text_angle, text_width, text_height)
text_img = Image.new('RGBA', (text_width, text_height), color=(255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_img)
text_draw.text((0, 0), option, fill=(0, 0, 0), font=font)
text_img = text_img.rotate(text_angle, expand=True)
spinner_img.paste(text_img, (int(text_center_x), int(text_center_y)), text_img)
spinner_img = spinner_img.rotate(angle, center=CENTER)
spinner_img.putalpha(MASK_IMG)
spinner_img.paste(SPINNER_CENTER_IMG, mask=SPINNER_CENTER_IMG)
spinner_img.paste(TRIANGLE_IMG, mask=TRIANGLE_IMG)
return spinner_img
SpinnerGifMaker(["hi", "play", "sleep", "run", "dance", "eat", "fly", "study"])
and this are some of the frames:
the picture shows some options like ‘eat’, ‘dance’, ‘run’ and ‘study’ are noticeably not properly centered, but i’m not sure why.
I tried adjsuting the center values and all but no matter what there will always be some text options that are not centered nicely.
When you rotate the text using expand=True
, the size of text_img
changes. You’ll need to compute the text_center_{x,y}
using the updated size:
text_img = text_img.rotate(text_angle, expand=True)
text_width, text_height = text_img.size
text_center_x = section_center_x - text_width / 2
text_center_y = section_center_y - text_height / 2
We can see more clearly what’s going on by using a translucent background to the text:
That improves things – maybe enough for your purposes? – but the text may not be vertically centered for all labels because the default text anchor uses the ascender rather than the rendered text height. You can specify anchor="lt"
to .textbbox
and .text
to change this: