How to render a TTF-Glyf to a image with FontTools - python

I want to render a glyf of a TT-Font into an image (numpy.array):
from fontTools.ttLib import TTFont
import matplotlib.pyplot as plt
font = TTFont('font.ttf')
glyf = font['glyf']['A']
coords = np.array(glyf.coordinates)
coords = np.swapaxes(coords,0,1)
plt.scatter(coords[0], coords[1])
This are the vertices.
How can I draw the glyf to an numpy.array? I found glyf.draw(...) but I don't found a tutorial or examples how to use it. I also do not found any informations about the pen-concept.
Edit 1:
I found a way to render text with pillow:
from PIL import ImageFont, ImageDraw, Image
image = Image.new(mode='L', size=(128,128), color=224)
draw = ImageDraw.Draw(image)
imageFont = ImageFont.truetype('font.ttf', 64)
draw.text((0, 0), "A", font=imageFont)
image
That is a good start, but I need more control of the final result. There glyf shut be centered and in a size, that does use the space in a more efficient way.
I am also interested in the gridlines, f.ex. baseline and others.
Edit 2:
I found some hints in this question: How to get the font pixel height using PIL's ImageFont class?
from PIL import ImageFont, ImageDraw, Image
x_size = 128
y_size = 128
font_size = 64
imageFont = ImageFont.truetype('font.ttf', font_size)
ascent, descent = imageFont.getmetrics()
image = Image.new(mode='L', size=(x_size, y_size), color=224)
draw = ImageDraw.Draw(image)
text = 'Aj;^'
draw.line([0,ascent,127,ascent], fill=128)
draw.line([0,descent,127,descent], fill=128)
draw.text((0, 0), text, font=imageFont)
image
There are two lines that mark to points on the y-axis. But as you can see, there are characters going down one line. And characters are overlapping in the x-direction, as you can see on the "j" and "A".
I still need more control of the final result.

Related

How can I use pango (HTML subset) with the ImageMagick Python library wand?

My goal is to take a picture and add a centered text to its center. I want to use italics and bold for this text, specified with the HTML-like pango.
I currently have this code:
import os
from wand.image import Image
from wand.drawing import Drawing
from wand.color import Color
with Image(filename='testimg.png') as img:
with Drawing() as draw:
draw.font = 'Arial'
draw.font_size = 36
text = 'pango:<b>Formatted</b> text'
(width, height) = draw.get_font_metrics(img, text).size()
print(width, height)
x = int((img.width - width) / 2)
y = int((img.height - height) / 2)
draw.fill_color = Color('black')
draw.text(x, y, text)
draw(img)
img.save(filename='output.jpg')
However, the text does not get formatted currently, but is simply "pango:Formatted text", and it is very hard to find any documentation.
(Before this approach I tried using pillow, but that does not seem to support anything HTML-like at all)
It kind of works if you create a new image and set the file path to a pango string:
import os
from wand.image import Image
from wand.drawing import Drawing
from wand.color import Color
# Open the image file
with Image(filename='testimg.png') as img:
# Create a Drawing object
with Image(filename="pango:<b>Formatted</b> text") as text_img:
text_img.transparent_color('white', alpha=0, fuzz=0)
text_img.font_path = r"Montserrat-SemiBold.ttf"
text_img.font_size = 36
# Calculate the x and y coordinates to center the text on the image
x = int((img.width - text_img.width) / 2)
y = int((img.height - text_img.height) / 2)
# Draw the text on the image
img.composite(text_img, left=x, top=y)
# Save the image
img.save(filename='outputnew.jpg')
However, the result is very ugly because the text rendering is like on a white background, only that the background is not white:

How can I rotate an emoji, printed as text, on an image?

This is what I'm doing right now:
from PIL import Image, ImageDraw, ImageFont, ImageFilter
fnt = ImageFont.truetype(font="NotoColorEmoji.ttf", size=109, layout_engine=ImageFont.LAYOUT_RAQM)
im = Image.open('1.png')
im = im.filter(ImageFilter.GaussianBlur(100))
draw = ImageDraw.Draw(im)
draw.text((66, 232), "😀" ,fill="#faa", embedded_color=True, font=fnt)
im.show()
You'll need to
determine the size of the rendered emoji w.r.t. the set up font,
derive a rotation center w.r.t. the location of the rendered text and the determined size,
print the text on some transparent image with the same size of your input image,
rotate that image w.r.t. the desired angle and the derived rotation center, and
paste that text image onto your actual input image.
Here's the code including some visualization overhead:
from PIL import Image, ImageDraw, ImageFont
# Load image
im = Image.open('path/to/your/image.png')
# Set up font, text, location, and rotation angle
fnt = ImageFont.truetype(font="NotoColorEmoji.ttf", size=109,
layout_engine=ImageFont.LAYOUT_RAQM)
txt = '😀'
loc = (50, 50)
ang = 123.45
# Get dimensions of rendered text using the specified font
fnt_dim = fnt.getsize(txt)
# Calculate rotation center, i.e. the center of the emoji, w.r.t. the
# text's location
rot_cnt = (loc[0] + fnt_dim[0] // 2, loc[1] + fnt_dim[1] // 2)
# Generate transparent image of the same size as the input, and print
# the text there
im_txt = Image.new('RGBA', im.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(im_txt)
draw.text(loc, txt, fill="#faa", embedded_color=True, font=fnt)
# Rotate text image w.r.t. the calculated rotation center
im_txt = im_txt.rotate(ang, center=rot_cnt)
# Paste text image onto actual image
im.paste(im_txt, mask=im_txt)
# Just for comparison: Print text upright directly on input image
im2 = im.copy()
draw = ImageDraw.Draw(im2)
draw.text(loc, txt, fill="#faa", embedded_color=True, font=fnt)
# Just for visualization
import matplotlib.pyplot as plt
plt.figure(figsize=(18, 6))
plt.subplot(1, 3, 1), plt.imshow(im)
plt.subplot(1, 3, 2), plt.imshow(im2)
plt.subplot(1, 3, 3), plt.imshow(Image.blend(im, im2, 0.5))
plt.tight_layout(), plt.show()
That'd be the output:
Looking at the blended image of both versions, the location of the rotated emoji is quite perfect. Some distortions are due to the non-square size of the emoji (136, 128).
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.19041-SP0
Python: 3.9.1
PyCharm: 2021.1.2
Matplotlib: 3.4.2
Pillow: 8.2.0
----------------------------------------

How can I insert Monospace fonts into an image with opencv?

Currently, I am able to insert some texts of HERSHEY font into images with openCV API (putText). But it seems openCV are not supporting any monospace font.
I was wondering how I can insert some Monospace or fixed-pitch texts into the image.
You could use PIL/Pillow for that aspect quite easily. OpenCV images are numpy arrays, so you can make a Pillow Image from an OpenCV image with:
PilImage = Image.fromarray(OpenCVimage)
Then you can draw with a mono spaced font using code in my answer here. You only need the 3 lines after the comment "Get a drawing context".
Then you can convert back to OpenCV image with:
OpenCVimage = np.array(PilImage)
That might look like this:
#!/usr/local/bin/python3
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import cv2
# Open image with OpenCV
im_o = cv2.imread('start.png')
# Make into PIL Image
im_p = Image.fromarray(im_o)
# Get a drawing context
draw = ImageDraw.Draw(im_p)
monospace = ImageFont.truetype("/Library/Fonts/Andale Mono.ttf",32)
draw.text((40, 80),"Hopefully monospaced",(255,255,255),font=monospace)
# Convert back to OpenCV image and save
result_o = np.array(im_p)
cv2.imwrite('result.png', result_o)
Alternatively, you could have a function generate a lump of canvas itself, write your text on it, and then splice it into your OpenCV image wherever you want. Something along these lines - though I have no idea of what flexibility you would require so I have not parameterised everything:
#!/usr/local/bin/python3
from PIL import Image, ImageFont, ImageDraw, ImageColor
import numpy as np
import cv2
def GenerateText(size, fontsize, bg, fg, text):
"""Generate a piece of canvas and draw text on it"""
canvas = Image.new('RGB', size, bg)
# Get a drawing context
draw = ImageDraw.Draw(canvas)
monospace = ImageFont.truetype("/Library/Fonts/Andale Mono.ttf",fontsize)
draw.text((10, 10), text, fg, font=monospace)
# Change to BGR order for OpenCV's peculiarities
return cv2.cvtColor(np.array(canvas), cv2.COLOR_RGB2BGR)
# Open image with OpenCV
im_o = cv2.imread('start.png')
# Try some tests
w,h = 350,50
a,b = 20, 80
text = GenerateText((w,h), 32, 'black', 'magenta', "Magenta on black")
im_o[a:a+h, b:b+w] = text
w,h = 200,40
a,b = 120, 280
text = GenerateText((w,h), 18, 'cyan', 'blue', "Blue on cyan")
im_o[a:a+h, b:b+w] = text
cv2.imwrite('result.png', im_o)
Keywords: OpenCV, Python, Numpy, PIL, Pillow, image, image processing, monospace, font, fonts, fixed, fixed width, courier, HERSHEY.

How to draw text with image in background?

I want to make something like this python.
I have the image in background and write text with transparent fill, so that image shows up.
Here's one way I found to do it using the Image.composite() function which is documented here and here.
The approach used is described (very) tersely in this answer to the question Is it possible to mask an image in Python Imaging Library (PIL)? by #Mark Ransom…the following is just an illustration of applying it to accomplish what you want do.
from PIL import Image, ImageDraw, ImageFont
BACKGROUND_IMAGE_FILENAME = 'cookie_cutter_background_cropped.png'
RESULT_IMAGE_FILENAME = 'cookie_cutter_text_result.png'
THE_TEXT = 'LOADED'
FONT_NAME = 'arialbd.ttf' # Arial Bold
# Read the background image and convert to an RGB image with Alpha.
with open(BACKGROUND_IMAGE_FILENAME, 'rb') as file:
bgr_img = Image.open(file)
bgr_img = bgr_img.convert('RGBA') # Give iamge an alpha channel.
bgr_img_width, bgr_img_height = bgr_img.size
cx, cy = bgr_img_width//2, bgr_img_height//2 # Center of image.
# Create a transparent foreground to be result of non-text areas.
fgr_img = Image.new('RGBA', bgr_img.size, color=(0, 0, 0, 0))
font_size = bgr_img_width//len(THE_TEXT)
font = ImageFont.truetype(FONT_NAME, font_size)
txt_width, txt_height = font.getsize(THE_TEXT) # Size of text w/font if rendered.
tx, ty = cx - txt_width//2, cy - txt_height//2 # Center of text.
mask_img = Image.new('L', bgr_img.size, color=255)
mask_img_draw = ImageDraw.Draw(mask_img)
mask_img_draw.text((tx, ty), THE_TEXT, fill=0, font=font, align='center')
res_img = Image.composite(fgr_img, bgr_img, mask_img)
res_img.save(RESULT_IMAGE_FILENAME)
res_img.show()
Which, using the following BACKGROUND_IMAGE:
produced the image shown below, which is it being viewed in Photoshop so the transparent background it has would be discernible (not to scale):
Here's an enlargement, showing the smoothly rendered edges of the characters:

PIL: Can't vertically align text accurately despite taking font into account

I want to create a simple Python script that let's me create an image file and place a word dead-center on that canvas. However, while I can get the word to align horizontally, I just can't get it to the center vertically. I am on MacOS btw. I have tried this so far:
import os
from PIL import Image, ImageDraw, ImageFont
font_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'fonts')
font = ImageFont.truetype(os.path.join(font_path, "Verdana.ttf"), 80)
W, H = (1200,200)
msg = "hola"
im = Image.new("RGB",(W,H),(255,255,255))
draw = ImageDraw.Draw(im)
w, h = draw.textsize(msg, font)
draw.text(((W-w)/2,(H-h)/2), msg, font=font, fill="black")
im.save("output_script.png", "PNG")
I already considered the font in the textsize calculation. But the word still appears roughly 5-10% below the middle. Any ideas?
textsize seems to return the size of the actual text, not that of the line. So it's smaller for ooo than for XXX!
I think you should just use 80 — the size you gave PIL — for the height, although I can't guarantee that it's correct.
ImageFont.getmask(txt) returns the alpha mask bitmap with which text can be centered in the image.
import Image, ImageFont
img = Image.new('L', (32, 32), color=0)
img_w, img_h = img.size
font = ImageFont.truetype('/path/to/font.ttf', 16)
mask = font.getmask('hola') # your text here
mask_w, mask_h = mask.size
d = Image.core.draw(img.im, 0)
d.draw_bitmap(((img_w - mask_w)/2, (img_h - mask_h)/2), mask, 255) # last arg is pixel intensity of text
img.save('test.png')

Categories

Resources