Source code for wolfhece.textpillow

"""
Author: HECE - University of Liege, Pierre Archambeau
Date: 2024

Copyright (c) 2024 University of Liege. All rights reserved.

This script and its content are protected by copyright law. Unauthorized
copying or distribution of this file, via any medium, is strictly prohibited.
"""

from os.path import exists, join, realpath, dirname
from queue import PriorityQueue
from PIL import Image,ImageDraw,ImageFont
import numpy as np
import logging
from enum import Enum
from pathlib import Path

[docs] class Font_Priority(Enum):
[docs] WIDTH = 1
[docs] HEIGHT = 2
[docs] FONTSIZE= 3
#1--4--7 #| | | #2--5--8 #| | | #3--6--9
[docs] class Relative_Position(Enum):
[docs] TOP_LEFT = 1
[docs] TOP = 4
[docs] TOP_RIGHT = 7
[docs] LEFT = 2
[docs] CENTER = 5
[docs] RIGHT = 8
[docs] BOTTOM_LEFT = 3
[docs] BOTTOM = 6
[docs] BOTTOM_RIGHT = 9
[docs] def load_font(fontname:str, fontsize:int=10): if exists(fontname): font=ImageFont.truetype(fontname,fontsize) elif exists(join(join(dirname(realpath(__file__)),'fonts'),fontname)): font=ImageFont.truetype(join(join(dirname(realpath(__file__)),'fonts'),fontname),fontsize) else: logging.error("Font file not found -- Check your parameter") return font
[docs] class Text_Infos(): """ Properties of Text_Image Class """ def __init__(self, priority=Font_Priority.WIDTH, orientationbase=(1,0), fontname="arial.ttf", fontsize=10, colour=(0,0,0,255), dimspix=(100,100), dimsreal=(0,0), relative_position=Relative_Position.CENTER) -> None: self.lengthpix = dimspix[0] # length in pixels self.heightpix = dimspix[1] # height in pixels self.lengthreal = dimsreal[0] # length in real coordinates self.heightreal = dimsreal[1] # height in real coordinates self.scalex = 1 # scale factor along X self.scaley = 1 # scale factor along Y self.orientationbase = orientationbase # orientation unit vector - example : (1,0) -> along X, (0,1) -> along Y, (1/sqrt(2),1/sqrt(2)) -> 45° if isinstance(relative_position,int): if relative_position==Relative_Position.TOP_LEFT.value: relative_position = Relative_Position.TOP_LEFT elif relative_position==Relative_Position.TOP_RIGHT.value: relative_position = Relative_Position.TOP_RIGHT elif relative_position==Relative_Position.BOTTOM_RIGHT.value: relative_position = Relative_Position.BOTTOM_RIGHT elif relative_position==Relative_Position.BOTTOM_LEFT.value: relative_position = Relative_Position.BOTTOM_LEFT elif relative_position==Relative_Position.CENTER.value: relative_position = Relative_Position.CENTER elif relative_position==Relative_Position.TOP.value: relative_position = Relative_Position.TOP elif relative_position==Relative_Position.LEFT.value: relative_position = Relative_Position.LEFT elif relative_position==Relative_Position.RIGHT.value: relative_position = Relative_Position.RIGHT elif relative_position==Relative_Position.BOTTOM.value: relative_position = Relative_Position.BOTTOM self.relative_position = relative_position if isinstance(priority,int): if priority==Font_Priority.WIDTH.value: priority = Font_Priority.WIDTH elif priority==Font_Priority.HEIGHT.value: priority = Font_Priority.HEIGHT elif priority==Font_Priority.FONTSIZE.value: priority = Font_Priority.FONTSIZE self.priority = priority # WIDTH respect width, HEIGHT respect height, FONTSIZE respect font size :-) if fontname.lower().endswith('.ttf'): self.fontname = fontname.lower() # .ttf file name else: self.fontname = fontname.lower() + '.ttf' pathfont = Path(self.fontname) if not (Path(__file__).parent / 'fonts' / self.fontname).exists(): self.fontname = "arial.ttf" logging.debug(f"Font file not found -- Check your parameter. Using default font {self.fontname}") self.fontsize = fontsize # Font size if isinstance(colour,int): def getRGBfromI(rgbint): blue = rgbint & 255 green = (rgbint >> 8) & 255 red = (rgbint >> 16) & 255 return red, green, blue r,g,b = getRGBfromI(colour) colour = (r,g,b,255) self.colour = colour # RGBA - (R,G,B,A)
[docs] def setsize_pixels(self,w,h): self.lengthpix = w self.heightpix = h
[docs] def setsize_real(self,wh=(0,0),scales=(0,0)): """Evalue la taille en pixel sur base de la taille réelle :param wh (float, float): largeur et hauteur dans le système réel :param scales (tuple, optional): Facteur d'échelle selon x et y. Defaults to (0,0) Le facteur d'échelle est évalué comme le rapport entre la taille en pixel et la taille réelle. Exemple : 0.5 --> 2x plus petit en pixels qu'en réel. """ if self.priority == Font_Priority.FONTSIZE: return if scales != (0,0): self.scalex=scales[0] self.scaley=scales[1] if wh!=(0,0): self.lengthreal=wh[0] self.heightreal=wh[1] self.lengthpix = self.lengthreal*self.scalex self.heightpix = self.heightreal*self.scaley
[docs] def findsize(self,text:str): """Trouve la taille en pixel sur base du texte et de la taille de police en cours Args: text (str): Texte à utiliser """ font = load_font(self.fontname, self.fontsize) left,top,right,bottom = font.getbbox(text) #, language=self.language) self.lengthpix = right-left self.heightpix = bottom-top
[docs] def adapt_fontsize(self,text): old = self.fontsize if self.priority == Font_Priority.FONTSIZE: return not old==self.fontsize w = self.lengthpix h = self.heightpix self.findsize(text) scalex=1 scaley=1 if w!=0: scalex = self.lengthpix / w if h!=0: scaley = self.heightpix / h if self.priority == Font_Priority.WIDTH: self.fontsize = int(self.fontsize/scalex) elif self.priority == Font_Priority.HEIGHT: self.fontsize = int(self.fontsize/scaley) self.fontsize=min(self.fontsize,200) self.findsize(text) return abs(old-self.fontsize)>3
[docs] def findscale(self,dx,dy,w,h): self.scalex = w/dx self.scaley = h/dy
[docs] def setscale(self,sx=1,sy=1): self.scalex = sx self.scaley = sy if self.scalex == 0: self.scalex=1. if self.scaley == 0: self.scaley=1.
[docs] def getcorners(self, xcenter, ycenter): orientx = self.orientationbase orienty = (-orientx[1], orientx[0]) if self.scalex == 0: self.scalex=1. if self.scaley == 0: self.scaley=1. l2scale = self.lengthpix/self.scalex/2 h2scale = self.heightpix/self.scaley/2 x1 = xcenter - orienty[0]*h2scale - orientx[0]*l2scale x2 = xcenter - orienty[0]*h2scale + orientx[0]*l2scale x3 = xcenter + orienty[0]*h2scale + orientx[0]*l2scale x4 = xcenter + orienty[0]*h2scale - orientx[0]*l2scale y1 = ycenter - orienty[1]*h2scale - orientx[1]*l2scale y2 = ycenter - orienty[1]*h2scale + orientx[1]*l2scale y3 = ycenter + orienty[1]*h2scale + orientx[1]*l2scale y4 = ycenter + orienty[1]*h2scale - orientx[1]*l2scale if self.relative_position == Relative_Position.LEFT: x1 -= l2scale x2 -= l2scale x3 -= l2scale x4 -= l2scale elif self.relative_position == Relative_Position.RIGHT: x1 += l2scale x2 += l2scale x3 += l2scale x4 += l2scale elif self.relative_position == Relative_Position.TOP: y1 += h2scale y2 += h2scale y3 += h2scale y4 += h2scale elif self.relative_position == Relative_Position.BOTTOM: y1 -= h2scale y2 -= h2scale y3 -= h2scale y4 -= h2scale elif self.relative_position == Relative_Position.TOP_LEFT: x1 -= l2scale x2 -= l2scale x3 -= l2scale x4 -= l2scale y1 += h2scale y2 += h2scale y3 += h2scale y4 += h2scale elif self.relative_position == Relative_Position.TOP_RIGHT: x1 += l2scale x2 += l2scale x3 += l2scale x4 += l2scale y1 += h2scale y2 += h2scale y3 += h2scale y4 += h2scale elif self.relative_position == Relative_Position.BOTTOM_LEFT: x1 -= l2scale x2 -= l2scale x3 -= l2scale x4 -= l2scale y1 -= h2scale y2 -= h2scale y3 -= h2scale y4 -= h2scale elif self.relative_position == Relative_Position.BOTTOM_RIGHT: x1 += l2scale x2 += l2scale x3 += l2scale x4 += l2scale y1 -= h2scale y2 -= h2scale y3 -= h2scale y4 -= h2scale x=[x1,x2,x3,x4] y=[y1,y2,y3,y4] return x,y
[docs] def getminmax(self, xcenter, ycenter): x,y = self.getcorners(xcenter,ycenter) return np.min(x),np.max(x),np.min(y),np.max(y)
[docs] class Text_Image(): def __init__(self, text:str, proptext:Text_Infos, language='en') -> None: self.text = text self.width = proptext.lengthpix self.height = proptext.heightpix self.fontname = proptext.fontname self.color = proptext.colour self.language = language self._font10 = load_font(self.fontname, 10) self.priority = proptext.priority self._image:Image = None if proptext.priority == Font_Priority.FONTSIZE: self.cur_sizefont = proptext.fontsize else: self.cur_sizefont = 10 self.create_image() @property
[docs] def image(self) -> Image: return self._image
[docs] def create_image(self): if self.text == "": return if self.priority == Font_Priority.WIDTH: left,top,right,bottom = self._font10.getbbox(self.text, language=self.language ) scale = self.width/(right-left) self.cur_sizefont = int(10*scale) elif self.priority == Font_Priority.HEIGHT: left,top,right,bottom = self._font10.getbbox(self.text, language=self.language ) scale = self.height/(bottom-top) self.cur_sizefont = int(10*scale) elif self.priority == Font_Priority.FONTSIZE: scale = 1 self.curfont = load_font(self.fontname, self.cur_sizefont) self.imagemask = self.curfont.getmask(self.text, language=self.language) #, mode="L") left,top,right,bottom = self.curfont.getbbox(self.text, language=self.language ) self._image = Image.new('RGBA', self.imagemask.size, (255,255,255,0)) drawer = ImageDraw.Draw(self._image) drawer.text((0,-top), text=self.text, font=self.curfont, fill=self.color, language=self.language ) pass
[docs] def show_image(self): if self._image is not None: self._image.show()
if __name__=='__main__':
[docs] myprop = Text_Infos(Font_Priority.WIDTH,fontname="sanserif.ttf",colour=(50,255,60,255))
myprop.lengthpix=300 myprop.adapt_fontsize('test') myprop = Text_Infos(Font_Priority.WIDTH,fontname="arial.ttf",colour=(50,255,60,255)) myprop.lengthpix=300 mytest = Text_Image("Test",myprop) mytest.show_image() myprop = Text_Infos(Font_Priority.HEIGHT,fontname="arial.ttf",colour=(50,255,60,255)) myprop.heightpix=300 mytest = Text_Image("Test", myprop) mytest.show_image() myprop = Text_Infos(Font_Priority.WIDTH,fontname="arial.ttf",colour=(50,255,60,255)) myprop.setsize_real((300,200),(1,1)) mytest = Text_Image("Test",myprop) mytest.show_image() myprop = Text_Infos(Font_Priority.WIDTH,fontname="arial.ttf",colour=(50,255,60,255)) myprop.setsize_real((300,200),(.5,.5)) mytest = Text_Image("Test",myprop) mytest.show_image() myprop = Text_Infos(Font_Priority.WIDTH,fontname="arial.ttf",colour=(50,255,60,255)) myprop.setsize_real((300,200),(.5,.5)) mytest = Text_Image("Test éàç$ùö",myprop) mytest.show_image() myprop = Text_Infos(Font_Priority.HEIGHT,fontname="arial.ttf",colour=(50,255,60,255)) myprop.setsize_real((300,500),(.5,.5)) mytest = Text_Image("Test éàç$ùö",myprop) mytest.show_image()