Create a PNG and make it transparent - python

I'm trying to dynamically create a PNG (code is working) and make it transparent (code is not working on the color white).
I can create the PNG, but making it transparent isn't working.
Code:
from PIL import Image, ImageDraw, ImageFont
import os
def text_on_img(filename='01.png', text="Hello", size=12):
"Draw a text on an Image, saves it, show it"
fnt = ImageFont.truetype('arial.ttf', 52)
# create image
image = Image.new(mode="RGB", size=(150, 75), color="white")
draw = ImageDraw.Draw(image)
# draw text
draw.text((10, 10), text, font=fnt, fill=(0, 0, 0))
newData = []
newData.append((255, 255, 255, 0))
image.save(filename)

Here's a minimal example that creates the image in RGBA mode and sets the background to be transparent.
from PIL import Image, ImageDraw, ImageFont
fnt = ImageFont.truetype("arial.ttf", 52)
img = Image.new(mode="RGBA", size=(150, 75), color=(255, 255, 255, 127))
draw = ImageDraw.Draw(img)
draw.text((10, 10), "Hello", font=fnt, fill=(0, 0, 0))
img.save("test.png", "PNG")
This creates the following image:
Changing the alpha to 127 (50%) results in the following image being created:

Related

Tkinter - How to remove white background of pictures of buttons and in general

I'm pretty new to Tkinter and I have made for a program a screen of buttons that, when pressed, do a command.
Picture of the screen:
I'm using the pictures with the buttons. The background of the photos make the screen look aesthetically unpleasing, so I have there's a way to remove the white background of the pictures.
Any advice would be appreciated! thank you very much in advance.
Also, here is how I put those image on the screen with the button (example given is keyboard image):
keyb_img = resize_img('/ServerImages/KeyBPhoto.jpg', 100)
self.keyb_img = keyb_img
keyb_share_button = Button(self.top_level, image=self.keyb_img, command=lambda: self.keyb_press('Keyboard_Change'), borderwidth=0).grid(row=2, column=5, pady=100, padx =15)
As #Matiiss pointed out: .jpg images don't have transparent backgrounds.
So we have to remove the background from the image. To do that we can use PIL like this:
import tkinter as tk
from PIL import Image, ImageTk
width = 100
height = 100
image = Image.open("/ServerImages/KeyBPhoto.jpg").convert("RGBA")
image = image.resize((width, height), Image.LANCZOS)
# Load the pizels into memory
pixels = image.load()
# For each pixel in the image
for i in range(image.size[0]):
for j in range(image.size[1]):
# If the pixel is white
if pixels[i, j] == (255, 255, 255, 255):
# Make it transparent
pixels[i, j] = (255, 255, 255, 0)
# Save the now transparent image:
image.save("new_image.png", format="png")
# Show it on the screen
root = tk.Tk()
canvas = tk.Canvas(root, bg="red")
canvas.pack()
tk_image = ImageTk.PhotoImage(image)
canvas.create_image(0, 0, image=tk_image, anchor="nw")
root.mainloop()
Please note that this will only convert the pure while pixels into transparent. If a pixel has a value of #fffffe (pure white is #ffffff), the program will ignore it. Also it will convert all while pixels.
EDIT: by #furas
If you would use numpy array then it could be shorter
import numpy as np
# convert pillow.Image to numpy.array
array = np.array(image)
mask = np.all(array == [255, 255, 255, 255], axis=-1) # png without artefacts
array[ mask ] = [255, 255, 255, 0]
# convert numpy.array to pillow.Image
image = Image.fromarray(array)
But JPG may have artefacts like #fffffe instead of pure white #ffffff and then you can use mask with >=
mask = np.all(array >= [230, 230, 230, 255], axis=-1) # jpg with artefacts
Minimal working code:
I first remove color and later resize it to reduce artefacts.
import tkinter as tk
from PIL import Image, ImageTk
import numpy as np
def convert_pillow(image):
#print('[DEBUG] convert_pillow')
# Load the pizels into memory
pixels = image.load()
# For each pixel in the image
for i in range(image.size[0]):
for j in range(image.size[1]):
# If the pixel is white
if pixels[i, j] == (255, 255, 255, 255):
# Make it transparent
pixels[i, j] = (255, 255, 255, 0)
return image
def convert_numpy(image):
#print('[DEBUG] convert_numpy')
import numpy as np
# convert pillow.Image to numpy.array
array = np.array(image)
#mask = np.all(array == [255, 255, 255, 255], axis=-1) # png without artefacts
mask = np.all(array >= [230, 230, 230, 255], axis=-1) # jpg with artefacts
array[ mask ] = [255, 255, 255, 0]
# convert numpy.array to pillow.Image
image = Image.fromarray(array)
return image
# --- main ---
width = 100
height = 100
filename = "test/rgb.png"
filename = "test/rgb.jpg"
image = Image.open(filename).convert("RGBA")
#image = convert_pillow(image)
image = convert_numpy(image)
# resize after changing color - because resize creates new artefacts
image = image.resize((width, height), Image.LANCZOS)
# Save the now transparent image:
image.save("new_image.png", format="png")
# Show it on the screen
root = tk.Tk()
canvas = tk.Canvas(root, bg="gray")
canvas.pack()
tk_image = ImageTk.PhotoImage(image)
canvas.create_image(0, 0, image=tk_image, anchor="nw")
root.mainloop()
Images for tests:
rgb.png
rgb.jpg
Result:

How do I reduce the number of colors used in ImageDraw.text

I have a folder with .ttf and .otf fonts and would like to write them on my ImageDraw object but with NO shading. A single RGB only. I have tried bitmap fonts but they A) don't look nice and B) use more than one color anyway.
I have read that there is a library for converting .bdf to .pil. If I convert arial.ttf to arial.bdf and then to arial.pil, will this be what I'm looking for? The text will almost always be dropped onto a background--so should I consider writing the text first on a blank canvas, do a color reduction, and then paste that canvas onto my background?
I have previously made this program using Java and it writes text very nicely on my bitmaps. One color, symmetrical, etc. Image below.
Below are the two attempts with python. The blockier one is a bitmap font, the other is regular arial.ttf.
Here is my code:
def personalize(self):
names = self.personalize_entry.get("1.0", 'end-1c').split('\n')
num_names = len(names)
num_grids = math.ceil(num_names/20)
answer = ask_grid_background()
separator = Image.new('RGB', (473, 1), color_dict['P'])
background = Image.new('RGB', (473, 821), color_dict['.'])
if answer:
showinfo("Bitmap", "Give me the design.")
file_path = filedialog.askopenfilename()
filename = path_leaf(file_path)
filename = filename[:-4]
__, __, center = read(file_path)
if center == 0:
messagebox.showinfo("Hmmm", f"I couldn't find a center...are you sure this is a basic set up?")
return False
img = Image.open(file_path)
size_num = img.size
section = img.crop((5, (size_num[1] - 55 - center), 478, (size_num[1] - center - 15)))
background.paste(separator, (0, 0))
for i in range(20):
background.paste(section, (0, (41 * i + 1)))
background.paste(separator, (0, (41 * i) + 41))
else:
background.paste(separator, (0, 0))
for i in range(20):
# background.paste(section,(0,(41*i+1)))
background.paste(separator, (0, (41 * i) + 41))
draw = ImageDraw.Draw(background)
fnt = ImageFont.truetype("Fonts/PIXEAB__.ttf",36)
draw.text((10, 10), names[0], font=fnt, fill=(0, 0, 0))
background.show()
ImageDraw has an undocumented member fontmode, which can be set to '1' (cf. Pillow's image modes) to turn off the anti-aliasing of the rendered text.
Let's compare common rendered text, draw.fontmode is implicitly set to 'L':
from PIL import Image, ImageDraw, ImageFont
image = Image.new('RGB', (800, 200), (255, 255, 255))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype('arial.ttf', 150)
draw.text((10, 10), 'Hello World', font=font, fill=(0, 0, 0))
image.save('image.png')
Now, let's explicitly set draw.fontmode = '1':
from PIL import Image, ImageDraw, ImageFont
image = Image.new('RGB', (800, 200), (255, 255, 255))
draw = ImageDraw.Draw(image)
draw.fontmode = '1'
font = ImageFont.truetype('arial.ttf', 150)
draw.text((10, 10), 'Hello World', font=font, fill=(0, 0, 0))
image.save('image.png')
Et voilà – no anti-aliasing, all pixels are solid black.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
PyCharm: 2021.1
Pillow: 8.2.0
----------------------------------------

GUI button problems

I want create a GUI for my python script.
I managed to define a background and now i would like to add my buttons but problem is here. Like you can see on the following screen
Here's the portion of my code where i define the image
root = tk.Tk()
root.wm_attributes("-topmost", True)
root.wm_attributes("-transparent", True)
root.config(bg='systemTransparent')
NewAccountImg=tk.PhotoImage(file="NewAccountImg.gif")
background_image=tk.PhotoImage(file="fond.png")
background_label=tk.Label(root, image=background_image)
background_label.place(x=0, y=0, relwidth=1, relheight=1)
And
root.newaccount = tk.Button(root,text="New Account",command=partial(Set_Acc_and_Hide,root),image=NewAccountImg)
How can I make my button transparent?
You should use PIL library for image.
PIL supports transparency.
Here is an example:
from PIL import Image, ImageDraw
img = Image.new('RGBA', (100, 100), (255, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.ellipse((25, 25, 75, 75), fill=(255, 0, 0))
img.save('test1.gif', 'GIF', transparency=0)
# this generates gif of red colored circle with white background(transparent background).
img.save('test2.gif', 'GIF', transparency=1)
# this generates gif of white colored circle(transparent circle) with red background.

How to draw Chinese text on the image using `cv2.putText`correctly? (Python+OpenCV)

I use python OpenCV (Windows 10, Python 2.7) to write text in image, when the text is English it works, but when I use Chinese text it write messy code in the image.
Below is my code:
# coding=utf-8
import cv2
import numpy as np
text = "Hello world" # just work
# text = "内容理解团队" # messy text in the image
cv2.putText(img, text,
cord,
font,
fontScale,
fontColor,
lineType)
# Display the image
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
When text = "Hello world" # just work, below is the output image:
When text = "内容理解团队" # Chinese text, draw messy text in the image, below is the output image:
What's wrong? Does opencv putText don't support other language text?
The cv2.putText don't support no-ascii char in my knowledge. Try to use PIL to draw NO-ASCII(such Chinese) on the image.
import numpy as np
from PIL import ImageFont, ImageDraw, Image
import cv2
import time
## Make canvas and set the color
img = np.zeros((200,400,3),np.uint8)
b,g,r,a = 0,255,0,0
## Use cv2.FONT_HERSHEY_XXX to write English.
text = time.strftime("%Y/%m/%d %H:%M:%S %Z", time.localtime())
cv2.putText(img, text, (50,50), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (b,g,r), 1, cv2.LINE_AA)
## Use simsum.ttc to write Chinese.
fontpath = "./simsun.ttc" # <== 这里是宋体路径
font = ImageFont.truetype(fontpath, 32)
img_pil = Image.fromarray(img)
draw = ImageDraw.Draw(img_pil)
draw.text((50, 80), "端午节就要到了。。。", font = font, fill = (b, g, r, a))
img = np.array(img_pil)
cv2.putText(img, "--- by Silencer", (200,150), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (b,g,r), 1, cv2.LINE_AA)
## Display
cv2.imshow("res", img);cv2.waitKey();cv2.destroyAllWindows()
#cv2.imwrite("res.png", img)
Refer to my another answer:
Load TrueType Font to OpenCV
According to this opencv forum, putText is only able to support a small ascii subset of characters and does not support unicode characters which are other symboles like chinese and arabic characters.
However, you can try to use PIL instead and follow the answer posted here and see if it works out for you.
Quick Start
np_img = np.ones((64, 32, 3), dtype=np.uint8) * 255 # background with white color
draw_text = init_parameters(cv2_img_add_text, text_size=32, text_rgb_color=(0, 0, 255), font='kaiu.ttf', replace=True)
draw_text(np_img, '您', (0, 0))
draw_text(np_img, '好', (0, 32))
cv2.imshow('demo', np_img), cv2.waitKey(0)
what is init_parameters() and cv2_img_add_text()?
see as below:
EXAMPLE
from typing import Tuple
import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont
# define decorator
def init_parameters(fun, **init_dict):
"""
help you to set the parameters in one's habits
"""
def job(*args, **option):
option.update(init_dict)
return fun(*args, **option)
return job
def cv2_img_add_text(img, text, left_corner: Tuple[int, int],
text_rgb_color=(255, 0, 0), text_size=24, font='mingliu.ttc', **option):
"""
USAGE:
cv2_img_add_text(img, '中文', (0, 0), text_rgb_color=(0, 255, 0), text_size=12, font='mingliu.ttc')
"""
pil_img = img
if isinstance(pil_img, np.ndarray):
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
font_text = ImageFont.truetype(font=font, size=text_size, encoding=option.get('encoding', 'utf-8'))
draw.text(left_corner, text, text_rgb_color, font=font_text)
cv2_img = cv2.cvtColor(np.asarray(pil_img), cv2.COLOR_RGB2BGR)
if option.get('replace'):
img[:] = cv2_img[:]
return None
return cv2_img
def main():
np_img = np.ones(IMG_SHAPE, dtype=np.uint8) * 255 # background with white color
np_img = cv2_img_add_text(np_img, 'Hello\nWorld', (0, 0), text_rgb_color=(255, 0, 0), text_size=TEXT_SIZE)
np_img = cv2_img_add_text(np_img, '中文', (0, LINE_HEIGHT * 2), text_rgb_color=(0, 255, 0), text_size=TEXT_SIZE)
cur_y = LINE_HEIGHT * 3
draw_text = init_parameters(cv2_img_add_text, text_size=TEXT_SIZE, text_rgb_color=(0, 128, 255), font='kaiu.ttf', replace=True)
for msg in ('笑傲江湖', '滄海一聲笑'):
draw_text(np_img, msg, (0, cur_y))
cur_y += LINE_HEIGHT + 1
draw_text(np_img,
"""123
456
789
""", (0, cur_y))
cv2.imshow('demo', np_img), cv2.waitKey(0)
if __name__ == '__main__':
IMG_HEIGHT, IMG_WIDTH, CHANNEL = IMG_SHAPE = (250, 160, 3)
TEXT_SIZE = LINE_HEIGHT = 32
main()

How to get the font pixel height using PIL's ImageFont class?

I am using PIL' ImageFont module to load fonts to generate text images.
I want the text to tightly bound to the edge, however, when using the ImageFont to get the font height, It seems that it includes the character's padding. As the red rectangle indicates.
c = 'A'
font = ImageFont.truetype(font_path, font_size)
width = font.getsize(c)[0]
height = font.getsize(c)[1]
im = Image.new("RGBA", (width, height), (0, 0, 0))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'A', (255, 255, 255), font=font)
im.show('charimg')
If I can get the actual height of the character, then I could skip the bounding rows in the bottom rectangle, could this info got from the font?
Thank you.
Exact size depends on many factors. I'll just show you how to calculate different metrics of font.
font = ImageFont.truetype('arial.ttf', font_size)
ascent, descent = font.getmetrics()
(width, baseline), (offset_x, offset_y) = font.font.getsize(text)
Height of red area: offset_y
Height of green area: ascent - offset_y
Height of blue area: descent
Black rectangle: font.getmask(text).getbbox()
Hope it helps.
The top voted answer is outdated. There is a new function in Pillow 8.0.0: ImageDraw.textbbox.
See the release notes for other text-related functions added in Pillow 8.0.0.
Note that ImageDraw.textsize, ImageFont.getsize and ImageFont.getoffset are broken, and should not be used for new code. These have been effectively replaced by the new functions with a cleaner API. See the documentaion for details.
To get a tight bounding box for a whole string you can use the following code:
from PIL import Image, ImageDraw, ImageFont
image = Image.new("RGB", (200, 80))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("arial.ttf", 30)
draw.text((20, 20), "Hello World", font=font)
bbox = draw.textbbox((20, 20), "Hello World", font=font)
draw.rectangle(bbox, outline="red")
print(bbox)
# (20, 26, 175, 48)
image.show()
You can combine it with the new ImageDraw.textlength to get individual bounding boxes per letter:
from PIL import Image, ImageDraw, ImageFont
image = Image.new("RGB", (200, 80))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("arial.ttf", 30)
xy = (20, 20)
text = "Example"
draw.text(xy, text, font=font)
x, y = xy
for c in text:
bbox = draw.textbbox((x, y), c, font=font)
draw.rectangle(bbox, outline="red")
x += draw.textlength(c, font=font)
image.show()
Note that this ignores the effect of kerning. Kering is currently broken with basic text layout, but could introduce a slight inaccuracy with Raqm layout. To fix it you would add the text length of pairs of letters instead:
for a, b in zip(text, text[1:] + " "):
bbox = draw.textbbox((x, y), a, font=font)
draw.rectangle(bbox, outline="red")
x += draw.textlength(a + b, font=font) - draw.textlength(b, font=font)
from PIL import Image, ImageDraw, ImageFont
im = Image.new('RGB', (400, 300), (200, 200, 200))
text = 'AQj'
font = ImageFont.truetype('arial.ttf', size=220)
ascent, descent = font.getmetrics()
(width, height), (offset_x, offset_y) = font.font.getsize(text)
draw = ImageDraw.Draw(im)
draw.rectangle([(0, 0), (width, offset_y)], fill=(237, 127, 130)) # Red
draw.rectangle([(0, offset_y), (width, ascent)], fill=(202, 229, 134)) # Green
draw.rectangle([(0, ascent), (width, ascent + descent)], fill=(134, 190, 229)) # Blue
draw.rectangle(font.getmask(text).getbbox(), outline=(0, 0, 0)) # Black
draw.text((0, 0), text, font=font, fill=(0, 0, 0))
im.save('result.jpg')
print(width, height)
print(offset_x, offset_y)
print('Red height', offset_y)
print('Green height', ascent - offset_y)
print('Blue height', descent)
print('Black', font.getmask(text).getbbox())
result
Calculate area pixel
from PIL import Image, ImageDraw, ImageFont
im = Image.new('RGB', (400, 300), (200, 200, 200))
text = 'AQj'
font = ImageFont.truetype('arial.ttf', size=220)
ascent, descent = font.getmetrics()
(width, height), (offset_x, offset_y) = font.font.getsize(text)
draw = ImageDraw.Draw(im)
draw.rectangle([(0, offset_y), (font.getmask(text).getbbox()[2], ascent + descent)], fill=(202, 229, 134))
draw.text((0, 0), text, font=font, fill=(0, 0, 0))
im.save('result.jpg')
print('Font pixel', (ascent + descent - offset_y) * (font.getmask(text).getbbox()[2]))
result

Categories