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:

enter image description here

enter image description here

enter image description here

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.

Asked By: Linus Tan

||

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:
Spinner without size adjustment

Spinner with size adjustment

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:

Spinner with tight text bounds

Answered By: motto