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
----------------------------------------
Related
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:
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.
i am trying to develop an android application in python that fills the area which is touched (exactly like the fill color tool in ms paint, which fills the closed area with color, or if not closed the color spreads everywhere).Given the xy position of touch, how can i fill the transparent area of a RGBA image using PIL or cv2, then save it?
given below is the pseudo code:
#rgb_value is the rgb value of the color with which to fill
#touchx, touchy are the xy positions of touch
#src is the source image, dst is the filename with which to save the output image
def fill_color(src, rgb_value, touchx, touchy, dst):
#code goes here
#then the image is saved
fill_color("freehand.png", [0,0,0], 5, 1000, "freehand(filled).png")
given below is freehand.png:(the patterned area is transparent)
cv2 has cv2.floodFill()
It will get color in start_point and search the same color in neighborhood and replace with color
import cv2
img_before = cv2.imread('image.png')
img_after = img_before.copy()
start_point = (400, 300)
color = (128, 128, 128)
cv2.floodFill(img_after, None, start_point, color)
cv2.imshow('before', img_before)
cv2.imshow('after', img_after)
cv2.waitKey(0)
cv2.destroyAllWindows()
but it works only with RGB but not RBGA so it would need more work.
pillow also has ImageDraw.floodfill() and it work with RGBA. If you work with numpy.array then it will need only to convert from array to Image and later back to array.
from PIL import Image, ImageDraw
img_before = Image.open('image.png')
img_after = img_before.copy()
start_point = (400, 300)
color = (128, 128, 128, 255)
img_draw = ImageDraw.floodfill(img_after, start_point, color, thresh=50)
img_before.show()
img_after.show()
#img_after.save('image_after.png')
I would like to create a PNG using Pillow. The PNG is later used for label printing. So, I try to create an new image using PIL, add some text, a polygon and a QR code. However, I find it rather difficult to get the proper image size (related to the label size which is 7.5 cm x 5.5 cm). I only get there by try and error (it is roughly (275, 204)). When saving, the resolution of the PNG is poor and so the print is.
How do I properly calculate the image size and how can I increase the resolution for saving the image as PNG (or JPG or JPEG).
Here is my code so far:
import qrcode
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
# Create qr code instance
qr = qrcode.QRCode(
version = 1,
error_correction = qrcode.constants.ERROR_CORRECT_H,
box_size = 4,
border = .2,
)
text = "test"
# Create new image
img = Image.new('RGB', (275, 204), color = (220,220,220))
# Add data
qr.add_data(text)
qr.make(fit=True)
# Create an image from the QR Code instance
qrc = qr.make_image(fill_color='black', back_color='white')
draw = ImageDraw.Draw(img)
font1 = ImageFont.truetype("arial.ttf", 25)
font2 = ImageFont.truetype("arial.ttf", 60)
draw.text((65, 150),text,(0,0,0),font=font1)
draw.text((115, 120),text,(0,0,0),font=font2)
draw.polygon(((5, 20), (50, 34), (50, 6)), fill=0)
img.paste(qrc, (80,25))
img.show()
#img.save("image.png", dpi = (300,300))
You'll need to set up relative (label) coordinates and sizes (in cm) for all elements, you want to draw, and then calculate the corresponding image coordinates and sizes (in pixels) w.r.t. the desired resolution. That'll get somehow cumbersome, but I can't think of another way to do this independent of the resolution.
In the following, I left out the whole QR code thing to keep things at least a bit readable.
For comparison, your code as is (without the QR code thing) generates this image on my machine:
If I open that image in Photoshop, it says 9.7 cm x 7.2 cm at 72 dpi. If I calculate the resolutions by hand, I'll get the following results:
275 pixels / 7.5 cm * (2.54 cm/inch) = ca. 93 dpi
204 pixels / 5.5 cm * (2.54 cm/inch) = ca. 94 dpi
We'll need the 94 dpi later.
I approximated the relative (label) coordinates and sizes from your original image.
Now, first let's see the whole code:
from PIL import Image, ImageDraw, ImageFont
# Set up parameters
w_cm, h_cm = (7.5, 5.5) # Real label size in cm
res_x, res_y = (300, 300) # Desired resolution
res_y_old = 94 # Old y resolution (204 / 5.5 * 2.54)
# Inch-to-cm factor
f = 2.54
# Determine image size w.r.t. resolution
w = int(w_cm / f * res_x)
h = int(h_cm / f * res_y)
# Create new image with proper size
img = Image.new('RGB', (w, h), color=(220, 220, 220))
# Draw elements
draw = ImageDraw.Draw(img)
def draw_text(x_cm, y_cm, font_size):
font = ImageFont.truetype('arial.ttf', int(font_size / (res_y_old / res_y)))
x, y = (int(x_cm / f * res_x), int(y_cm / f * res_y))
draw.text((x, y), 'test', (0, 0, 0), font=font)
# Draw texts
draw_text(1.875, 4, 25)
draw_text(3.15, 3.25, 60)
# Polygon
coords_cm = [(0.15, 0.5), (1.35, 0.9), (1.35, 0.1)]
coords = [(int(c[0] / f * res_x), int(c[1] / f * res_y)) for c in coords_cm]
draw.polygon(tuple(coords), fill=(0, 0, 0))
# Save image
img.save('image.png', dpi=(res_x, res_y))
And, let's see the output:
If I open that image in Photoshop, it says 7.5 cm x 5.5 cm at 300 dpi, so exactly, what we wanted.
Regarding the font sizes: I guess, you set up those font sizes manually to get a certain appearance. Unfortunately, font sizes given in points are adjusted to have a certain height on monitors, but they won't scale for printing (or "printed" images). That's why, we need to scale the fonts w.r.t. the old and new resolution to keep the desired text appearance from your original image.
Everything else is repetitive converting relative coordinates and sizes. Do not hesitate to ask, if further explanations are needed!
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
Pillow: 8.1.0
----------------------------------------
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: