Python PIL - How to put text on a video - python

I am writing a program that puts captions on the screen and I am using PIL because it has high-quality text that is way better than the default moviepy text. I currently can put the text on a black screen, I tried using a fully transparent image as a sort of hack but that didn't work. So if anyone knows how to put PIL text on an image, or up the quality of the default moviepy text, let me know, please. Here is my basic code to put white text on a black background:
import numpy as np
from moviepy.editor import *
from PIL import Image, ImageFont, ImageDraw
SIZE = (1280, 720)
DURATION = 2
FPS = 1
TEXT = "I am having a hard time"
FONT_SIZE = 100
TEXT_COLOR_HEX = "#ffffff"
FONT = "Amiri-Bold"
def create_PIL():
img = Image.new("RGB", size=SIZE)
#draw = ImageDraw.Draw(img)
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(f"../Fonts/UniNeue-Trial-Bold.ttf", size=FONT_SIZE)
draw.text((100, 100), TEXT, font=font, fill=TEXT_COLOR_HEX)
img = np.array(img)
return VideoClip(lambda t: img, duration=DURATION).set_fps(FPS)
final = concatenate_videoclips(
[create_PIL()]
)
final.write_videofile('./test.mp4')
I am trying to put this text onto a video, and I need to have custom start and duration times but all this does it put it on a black screen, which is what I expected but not what I want.

Related

How to find the correct Image size

I am trying to convert Text to image in Python
This is the code:
As long as the text is in single line,
For example "Text to write on img 1234567890"
It is fine
But if the text contains "\n" then the image clips and size calculations become incorrect
"Text to write on \n img 1234567890"
Please help
import numpy as np
import time
import text_to_image
from PIL import Image, ImageDraw, ImageFont
import os
from win32api import GetSystemMetrics
def text_on_img(filename='01.png', text="Text to write on \n img 1234567890", size=200, color=(0,0,0), bg='white'):
"Draw a text on an Image, saves it, show it"
fnt = ImageFont.truetype('arial.ttf', size)
# create image
image = Image.new(mode = "RGB", size = (int(size/2)*len(text),size+50), color = bg)
draw = ImageDraw.Draw(image)
# draw text
draw.text((10,10), text, font=fnt, fill=(0,0,0))
# save file
image.save(filename)
# show file
os.system(filename)
text_on_img()
I fixed it perfectly. Please test it.
import os
from PIL import Image, ImageDraw, ImageFont
def text_on_img(filename='01.png', text="Text to write on \n img 1234567890", size=200, color=(0, 0, 0), bg='white'):
"Draw a text on an Image, saves it, show it"
fnt = ImageFont.truetype('arial.ttf', size)
# create image
width = max([int(size/2) * len(line) for line in text.split('\n')])
height = (size + 50) * len(text.split('\n'))
image = Image.new(mode="RGB", size=(width, height), color=bg)
draw = ImageDraw.Draw(image)
# draw text
draw.text((10, 10), text, font=fnt, fill=(0, 0, 0))
# save file
image.save(filename)
# show file
os.system(filename)
text_on_img()
The result:
One approach is multiplying size with the number of occurrences of \n.
n = text.count('\n') + 2
# create image
image = Image.new(mode = "RGB", size = (int(size/2)*len(text),size*n), color = bg)
Result:
If you remove \n from the text, the result will be:
Other examples:

creating a filled in rectangle/shape around text using PIL

I have a pre-existing image that I have added a border to and text, I would like to place a rectangle around the text area to make it similar to a gold embedded plate on a picture or painting.
I am using draw.text to write and position the text, but can't figure out how to do the shape border & fill around the text parameters.
import os
from PIL import Image, ImageOps, ImageDraw, ImageFont
import glob
## SINGLE IMAGE
img = Image.open('Art/AB-2020 (2).jpg')
add_grey_border = ImageOps.expand(img,border=50,fill='grey')
add_black_border = ImageOps.expand(add_grey_border,border=300,fill='black')
add_black_border.save('Art/imaged-with-border.png')
## Function to add Title to image
def add_title(img_src, title):
img = Image.open(img_src, 'r')
draw = ImageDraw.Draw(img)
w, h = img.size
font = ImageFont.truetype("arial.ttf", 100)
text_w, text_h = draw.textsize(title, font)
draw.text(((w - text_w) / 2, (h-100) - text_h), title, (255,255,255),
font=font, stroke_width=2, stroke_fill='#eeda19')
img.save(img_src)
return img_src
add_title('Art/imaged-with-border.png', 'test')
what I have so far:
painting with text
If I can get the nameplate correct I will remove the stroke_fill.
I want it to look like this:
What I am trying to achieve

By Using python pil, I want to make image centered with text from url and download. How to do it?

I input a text at url, and want to get downloaded image that the text is centered.
for example,
when I input,
http://127.0.0.1:8000/app/hello, then an image is downloaded.
That image centers the text 'hello'
def homework(request, name):
text = name
img = Image.new('RGB',(256,256),(0,0,0))
draw = ImageDraw.Draw(img)
font = ImageFont.load_default().font
draw.text((128,128), text, font = font, fill="black")
with open(img, 'rb') as f:
response = HttpResponse(f, content_type='image/jpeg')
response['Content-Disposition'] = 'attachment; filename="textimage"'
return response
By using python pil.
How to do it?
#install PIL (Pillow) via Pip
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
#position of Text
x = 50
y = 50
#font color as tuple
color = (255, 255, 255)
#download the image to the local system and open it
img = Image.open("1.jpg")
#draw image and prepare font
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('arial.ttf', size=30)
#draw text on image
draw.text((x, y),"Sample Text", color,font=font)
#save the new image, that contains the text
img.save('sample-out.jpg')

How to convert a string to an image?

I started to learn python a week ago and want to write a small program that converts a email to a image (.png) so that it can be shared on forums without risking to get lots of spam mails.
It seems like the python standard library doesn't contain a module that can do that but I've found out that there's a PIL module for it (PIL.ImageDraw).
My problem is that I can't seem to get it working.
So basically my questions are:
How to draw a text onto a image.
How to create a blank (white) image
Is there a way to do this without actually creating a file so that I can show it in a GUI before saving it?
Current Code:
import Image
import ImageDraw
import ImageFont
def getSize(txt, font):
testImg = Image.new('RGB', (1, 1))
testDraw = ImageDraw.Draw(testImg)
return testDraw.textsize(txt, font)
if __name__ == '__main__':
fontname = "Arial.ttf"
fontsize = 11
text = "example#gmail.com"
colorText = "black"
colorOutline = "red"
colorBackground = "white"
font = ImageFont.truetype(fontname, fontsize)
width, height = getSize(text, font)
img = Image.new('RGB', (width+4, height+4), colorBackground)
d = ImageDraw.Draw(img)
d.text((2, height/2), text, fill=colorText, font=font)
d.rectangle((0, 0, width+3, height+3), outline=colorOutline)
img.save("D:/image.png")
use ImageDraw.text - but it doesn't do any formating, it just prints string at the given location
img = Image.new('RGB', (200, 100))
d = ImageDraw.Draw(img)
d.text((20, 20), 'Hello', fill=(255, 0, 0))
to find out the text size:
text_width, text_height = d.textsize('Hello')
When creating image, add an aditional argument with the required color (white):
img = Image.new('RGB', (200, 100), (255, 255, 255))
until you save the image with Image.save method, there would be no file. Then it's only a matter of a proper transformation to put it into your GUI's format for display. This can be done by encoding the image into an in-memory image file:
import cStringIO
s = cStringIO.StringIO()
img.save(s, 'png')
in_memory_file = s.getvalue()
or if you use python3:
import io
s = io.BytesIO()
img.save(s, 'png')
in_memory_file = s.getvalue()
this can be then send to GUI. Or you can send direct raw bitmap data:
raw_img_data = img.tostring()
The first 3 lines are not complete, when I'm not wrong. The correct code would be:
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

How can I convert canvas content to an image?

from Tkinter import *
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50)
cv.pack()
root.mainloop()
I want to convert canvas content to a bitmap or other image, and then do other operations, such as rotating or scaling the image, or changing its coordinates.
Bitmaps can improve efficiency to show if I am no longer drawing.
What should I do?
You can either generate a postscript document (to feed into some other tool: ImageMagick, Ghostscript, etc):
from Tkinter import *
root = Tk()
cv = Canvas(root)
cv.create_rectangle(10,10,50,50)
cv.pack()
root.mainloop()
cv.update()
cv.postscript(file="file_name.ps", colormode='color')
root.mainloop()
or draw the same image in parallel on PIL and on Tkinter's canvas (see: Saving a Tkinter Canvas Drawing (Python)). For example (inspired by the same article):
from Tkinter import *
import Image, ImageDraw
width = 400
height = 300
center = height//2
white = (255, 255, 255)
green = (0,128,0)
root = Tk()
# Tkinter create a canvas to draw on
cv = Canvas(root, width=width, height=height, bg='white')
cv.pack()
# PIL create an empty image and draw object to draw on
# memory only, not visible
image1 = Image.new("RGB", (width, height), white)
draw = ImageDraw.Draw(image1)
# do the Tkinter canvas drawings (visible)
cv.create_line([0, center, width, center], fill='green')
# do the PIL image/draw (in memory) drawings
draw.line([0, center, width, center], green)
# PIL image can be saved as .png .jpg .gif or .bmp file (among others)
filename = "my_drawing.jpg"
image1.save(filename)
root.mainloop()
I have found a great way of doing this which is really helpful. For it, you need the PIL module. Here is the code:
from PIL import ImageGrab
def getter(widget):
x=root.winfo_rootx()+widget.winfo_x()
y=root.winfo_rooty()+widget.winfo_y()
x1=x+widget.winfo_width()
y1=y+widget.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save("file path here")
What this does is you pass a widget name into the function. The command root.winfo_rootx() and the root.winfo_rooty() get the pixel position of the top left of the overall root window.
Then, the widget.winfo_x() and widget.winfo_y() are added to, basically just get the pixel coordinate of the top left hand pixel of the widget which you want to capture (at pixels (x,y) of your screen).
I then find the (x1,y1) which is the bottom left pixel of the widget. The ImageGrab.grab() makes a printscreen, and I then crop it to only get the bit containing the widget. Although not perfect, and won't make the best possible image, this is a great tool for just getting a image of any widget and saving it.
If you have any questions, post a comment! Hope this helped!
Use Pillow to convert from Postscript to PNG
from PIL import Image
def save_as_png(canvas,fileName):
# save postscipt image
canvas.postscript(file = fileName + '.eps')
# use PIL to convert to PNG
img = Image.open(fileName + '.eps')
img.save(fileName + '.png', 'png')
Maybe you can try to use widget_winfo_id to get the HWND of the canvas.
import win32gui
from PIL import ImageGrab
HWND = canvas.winfo_id() # get the handle of the canvas
rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
im = ImageGrab.grab(rect) # get image of the current location
A better way for #B.Jenkins's answer that doesn't need a reference to the root object:
from PIL import ImageGrab
def save_widget_as_image(widget, file_name):
ImageGrab.grab(bbox=(
widget.winfo_rootx(),
widget.winfo_rooty(),
widget.winfo_rootx() + widget.winfo_width(),
widget.winfo_rooty() + widget.winfo_height()
)).save(file_name)
On my system had serious issues with ghostscript and the ImageGrab in general. Solution draw on PIL Image, save as a file, load file on PhotoImage, which is used to create new TKinter Canvas.
canvas = Canvas(win, width=IMG_W, height=IMG_H)
img = PILImg.new("RGB", (IMG_W, IMG_H), "#000")
draw = ImageDraw.Draw(img)
draw.rectangle([x,y,w,h], fill=color, outline=border)
img.save("stock-chart.png")
copyImg = PhotoImage(file="stock-chart.png")
canvas.create_image(IMG_W_2, IMG_H_2, image=copyImg)

Categories