53 votes

python PIL dessiner un texte multiligne sur une image

J'essaie d'ajouter du texte en bas de l'image et j'y suis parvenu, mais si mon texte est plus long que la largeur de l'image, il est coupé des deux côtés. Pour simplifier, je voudrais que le texte soit sur plusieurs lignes s'il est plus long que la largeur de l'image. Voici mon code :

FOREGROUND = (255, 255, 255)
WIDTH = 375
HEIGHT = 50
TEXT = 'Chyba najwyzszy czas zadac to pytanie na sniadanie \n Chyba najwyzszy czas zadac to pytanie na sniadanie'
font_path = '/Library/Fonts/Arial.ttf'
font = ImageFont.truetype(font_path, 14, encoding='unic')
text = TEXT.decode('utf-8')
(width, height) = font.getsize(text)

x = Image.open('media/converty/image.png')
y = ImageOps.expand(x,border=2,fill='white')
y = ImageOps.expand(y,border=30,fill='black')

w, h = y.size
bg = Image.new('RGBA', (w, 1000), "#000000")

W, H = bg.size
xo, yo = (W-w)/2, (H-h)/2
bg.paste(y, (xo, 0, xo+w, h))
draw = ImageDraw.Draw(bg)
draw.text(((w - width)/2, w), text, font=font, fill=FOREGROUND)

bg.show()
bg.save('media/converty/test.png')

71voto

unutbu Points 222216

Vous pourriez utiliser textwrap.wrap de rompre text en une liste de chaînes de caractères, chacune au plus width de caractères :

import textwrap
lines = textwrap.wrap(text, width=40)
y_text = h
for line in lines:
    width, height = font.getsize(line)
    draw.text(((w - width) / 2, y_text), line, font=font, fill=FOREGROUND)
    y_text += height

26voto

pryma Points 118

La réponse acceptée consiste à envelopper le texte sans mesurer la police (40 caractères maximum, quelle que soit la taille de la police et la largeur de la boîte). Les résultats ne sont donc qu'approximatifs et peuvent facilement remplir la boîte de façon excessive ou insuffisante.

Voici une bibliothèque simple qui résout correctement le problème : https://gist.github.com/turicas/1455973

18voto

Franck Dernoncourt Points 4769

Pour un exemple complet de travail utilisant unutbu 's astuce (testé avec Python 3.6 et Pillow 5.3.0) :

from PIL import Image, ImageDraw, ImageFont
import textwrap

def draw_multiple_line_text(image, text, font, text_color, text_start_height):
    '''
    From unutbu on [python PIL draw multiline text on image](https://stackoverflow.com/a/7698300/395857)
    '''
    draw = ImageDraw.Draw(image)
    image_width, image_height = image.size
    y_text = text_start_height
    lines = textwrap.wrap(text, width=40)
    for line in lines:
        line_width, line_height = font.getsize(line)
        draw.text(((image_width - line_width) / 2, y_text), 
                  line, font=font, fill=text_color)
        y_text += line_height

def main():
    '''
    Testing draw_multiple_line_text
    '''
    #image_width
    image = Image.new('RGB', (800, 600), color = (0, 0, 0))
    fontsize = 40  # starting font size
    font = ImageFont.truetype("arial.ttf", fontsize)
    text1 = "I try to add text at the bottom of image and actually I've done it, but in case of my text is longer then image width it is cut from both sides, to simplify I would like text to be in multiple lines if it is longer than image width."
    text2 = "You could use textwrap.wrap to break text into a list of strings, each at most width characters long"

    text_color = (200, 200, 200)
    text_start_height = 0
    draw_multiple_line_text(image, text1, font, text_color, text_start_height)
    draw_multiple_line_text(image, text2, font, text_color, 400)
    image.save('pil_text.png')

if __name__ == "__main__":
    main()
    #cProfile.run('main()') # if you want to do some profiling

Résultat :

enter image description here

11voto

Igor Pomaranskiy Points 157

Toutes les recommandations concernant textwrap L'utilisation ne parvient pas à déterminer la largeur correcte pour les polices non monospace (comme Arial, utilisée dans le code d'exemple du sujet).

J'ai écrit une classe d'aide simple pour envelopper le texte en fonction de la taille réelle des lettres de la police :

class TextWrapper(object):
    """ Helper class to wrap text in lines, based on given text, font
        and max allowed line width.
    """

    def __init__(self, text, font, max_width):
        self.text = text
        self.text_lines = [
            ' '.join([w.strip() for w in l.split(' ') if w])
            for l in text.split('\n')
            if l
        ]
        self.font = font
        self.max_width = max_width

        self.draw = ImageDraw.Draw(
            Image.new(
                mode='RGB',
                size=(100, 100)
            )
        )

        self.space_width = self.draw.textsize(
            text=' ',
            font=self.font
        )[0]

    def get_text_width(self, text):
        return self.draw.textsize(
            text=text,
            font=self.font
        )[0]

    def wrapped_text(self):
        wrapped_lines = []
        buf = []
        buf_width = 0

        for line in self.text_lines:
            for word in line.split(' '):
                word_width = self.get_text_width(word)

                expected_width = word_width if not buf else \
                    buf_width + self.space_width + word_width

                if expected_width <= self.max_width:
                    # word fits in line
                    buf_width = expected_width
                    buf.append(word)
                else:
                    # word doesn't fit in line
                    wrapped_lines.append(' '.join(buf))
                    buf = [word]
                    buf_width = word_width

            if buf:
                wrapped_lines.append(' '.join(buf))
                buf = []
                buf_width = 0

        return '\n'.join(wrapped_lines)

Exemple d'utilisation :

wrapper = TextWrapper(text, image_font_intance, 800)
wrapped_text = wrapper.wrapped_text()

Ce n'est probablement pas super rapide, car il rend le texte entier mot par mot, pour déterminer la largeur des mots. Mais dans la plupart des cas, cela devrait suffire.

-2voto

ice20000101 Points 27
text = textwrap.fill("test ",width=35)
self.draw.text((x, y), text, font=font, fill="Black")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X