Draw text image without crop need by PIL - python

I would like to draw a text by using PIL. But my problem is I need to crop the text image again after run the program. The thing i need is only text, no border. Any one can suggest?
Thank you.
This is my code:
import Image, ImageDraw, ImageFont
def draw (text, size, color) :
fontPath = '/home/FreeSansBold.ttf'
font = ImageFont.truetype(fontPath, size)
size2 = font.getsize(text)
im = Image.new('RGBA', size2, (0, 0, 0, 0))
draw = ImageDraw.Draw(im)
draw.text((0, 0), text, font=font, fill=color)
im.save(text +'.png')
drawA = draw('A', 200, 'green')
drawC = draw('C', 200, 'blue')
drawG = draw('G', 200, 'yellow')
drawT = draw('T', 200, 'red')

Could you clarify what you mean by no border? Are you wanting text tight against edge of the image? If so this should work:
import Image, ImageDraw, ImageFont
def draw (text, size, color) :
fontPath = '/home/FreeSansBold.ttf'
font = ImageFont.truetype(fontPath, size)
size2 = font.getsize(text)
im = Image.new('RGBA', size2, (0, 0, 0, 0))
draw = ImageDraw.Draw(im)
draw.text((0, 0), text, font=font, fill=color)
pixels = im.load()
width, height = im.size
max_x = max_y = 0
min_y = height
min_x = width
# find the corners that bound the letter by looking for
# non-transparent pixels
transparent = (0, 0, 0, 0)
for x in xrange(width):
for y in xrange(height):
p = pixels[x,y]
if p != transparent:
min_x = min(x, min_x)
min_y = min(y, min_y)
max_x = max(x, max_x)
max_y = max(y, max_y)
cropped = im.crop((min_x, min_y, max_x, max_y))
cropped.save(text +'.png')
drawA = draw('A', 200, 'green')
drawC = draw('C', 200, 'blue')
drawG = draw('G', 200, 'yellow')
drawT = draw('T', 200, 'red')
It produces an image like this (I filled in the transparent pixels with red to show the bounds of the image better: http://img43.imageshack.us/img43/3066/awithbg.png

Related

How to set anchor to fit the text at center of image with PIL.ImageDraw.Draw.text?

Drawn text on an image with Pillow library, tried to fit the text anchored at center of image by option anchor='mm', but it looks not exactly as center of image.
Demo code
from PIL import Image, ImageDraw, ImageFont
im = Image.new("RGBA", (500, 500), (255, 255, 255, 255))
font = ImageFont.truetype(font='arial.ttf', size=320)
draw = ImageDraw.Draw(im)
draw.text((250, 250), "123", font=font, fill='black', anchor='mm')
im.show()
result:
expectation:
The reason why the text looks misaligned is because there is a little margin at the left of the 1 and pillow will align the text including this margin.
Adding 0 at both ends will visualize that it is correctly centered and that there is a large margin at 1.
If you want to align the text excluding margins, ImageFont.getmask is helpful.
def get_offset_for_true_mm(text, draw, font):
anchor_bbox = draw.textbbox((0, 0), text, font=font, anchor='lt')
anchor_center = (anchor_bbox[0] + anchor_bbox[2]) // 2, (anchor_bbox[1] + anchor_bbox[3]) // 2
mask_bbox = font.getmask(text).getbbox()
mask_center = (mask_bbox[0] + mask_bbox[2]) // 2, (mask_bbox[1] + mask_bbox[3]) // 2
return anchor_center[0] - mask_center[0], anchor_center[1] - mask_center[1]
im = Image.new("RGBA", (500, 500), (255, 255, 255, 255))
font = ImageFont.truetype(font='arial.ttf', size=320)
draw = ImageDraw.Draw(im)
text = "123"
offset = get_offset_for_true_mm(text, draw, font)
draw.text((250 + offset[0], 250 + offset[1]), text, font=font, fill='black', anchor='mm')
im.show()
Result:

Making .png files with transparent pixels

I can make some .png files using PIL and tkinter:
import tkinter
from PIL import ImageGrab
canvas = tkinter.Canvas(width=100, height=100)
SomeInterestingPhotoImage = PhotoImage(file='Path/To/My/file.png')
canvas.create_image(0, 0, image=SomeInterestingPhotoImage, anchor='nw')
x0 = canvas.winfo_rootx()
y0 = canvas.winfo_rooty()
x1 = x0 + canvas.winfo_width()
y1 = y0 + canvas.winfo_height()
image = ImageGrab.grab((x0, y0, x1, y1))
image.save(name.png)
# I need to make some pixels transparent.
Here I can make an .png file, but all pixels have some colour. I need to make white pixels of canvas (not of image) transparent.
import glob
from PIL import Image
def transparent(myimage):
img = Image.open(myimage)
img = img.convert("RGBA")
pixdata = img.load()
width, height = img.size
for y in range(height):
for x in range(width):
if pixdata[x, y] == (0, 0, 0, 255):
pixdata[x, y] = (0, 0, 0, 0)
img.save(myimage, "PNG")
for image in glob.glob("*.png"):
transparent('mypic.png')
This code will basically change black (0, 0, 0, 255) to transparent (0, 0, 0, 0) pixels

How to move the circular image to the exact center position of the bigger image?

I want to move the circular image to the exact center position of the bigger image. How to do accomplish that task accurately?
from IPython.display import display
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
def show_saved_image(str):
img = Image.open(str)
display(img)
im1 = Image.open('rocket.jpg')
im2 = Image.open('lena.jpg')
#height, width, channels = im1.shape
im1_width, im1_height = im1.size
im1_centreX, im1_centreY = int(im1_width/2), int(im1_height/2)
im2_width, im2_height = im2.size
im2_centreX, im2_centreY = int(im2_width/2), int(im2_height/2)
print(im1_width, im1_height)
print(im2_width, im2_height)
radius = int(min(im2_width/2, im2_height/2))
ulX, ulY = im2_centreX-radius, im2_centreY-radius
lrX, lrY = im2_centreX+radius, im2_centreY+radius
desired_pointX, desired_pointY = im1_centreX-radius, im1_centreY-radius
# ![rocket_pillow_paste_out](data/dst/rocket_pillow_paste_out.jpg)
mask_im = Image.new("L", im2.size, 0)
draw = ImageDraw.Draw(mask_im)
draw.ellipse((ulX, ulY, lrX, lrY), fill=255)
# mask_im_blur = mask_im.filter(ImageFilter.GaussianBlur(10))
# mask_im_blur.save('mask_circle_blur.jpg', quality=95)
back_im = im1.copy()
back_im.paste(im2, (desired_pointX, desired_pointY), mask_im)
#back_im.paste(im2, (desired_pointX, desired_pointY), mask_im_blur)
back_im.save('output.jpg', quality=95)
im = Image.open('output.jpg')
draw = ImageDraw.Draw(im)
draw.ellipse((im1_centreX-4, im1_centreY-4, im1_centreX+4, im1_centreY+4 ), fill=(0, 255, 0), outline=(0, 0, 0))
draw.ellipse((desired_pointX-4, desired_pointY-4, desired_pointX+4, desired_pointY+4 ), fill=(255, 0, 0), outline=(0, 0, 0))
im.save('imagedraw.jpg', quality=95)
show_saved_image("imagedraw.jpg")
Images:
rocket.jpg
lena.jpg
If there is another way, then please help me with that, too.
You just need to modify this line
desired_pointX, desired_pointY = im1_centreX - radius, im1_centreY - radius
to
desired_pointX, desired_pointY = im1_centreX - int(im2_width/2), im1_centreY - int(im2_height/2)
Your mask_im has shape im2.size, so you need to adapt to that, not just the radius of the circle. Since radius is int(im2_height/2), the vertical alignment is fine, but radius is smaller than int(im2_width/2), that's why the insufficient shift leftwards.

Is there a way, to draw Text with a gradient color with Pillow?

I am about to create an image with text on it. So far everything is working fine. Now, for the fine-tuning, I thought it would be nice to have the text with a gradient color.
This is the point I am currently at.
This is what I want to have.
I've managed to generate following image:
using that script:
from PIL import Image, ImageFont, ImageDraw
OUTPUT_IMAGE = '53952270.png'
BG_COLOR = (0, 102, 0)
TEXT = 'STIEFELSTANGE'
TEXT_COLOR = (255, 255, 255)
SHADOW_COLOR = (231, 255, 227)
image = Image.new('RGB', (212, 45), color=BG_COLOR)
draw = ImageDraw.Draw(image)
font = ImageFont.truetype('impact', 36)
text_size = font.getsize(TEXT)
draw.text((0, 0), TEXT, font=font)
pixels = image.load()
size = image.size
x_list = []
y_list = []
for x in range(size[0]):
for y in range(size[1]):
if pixels[x, y] == TEXT_COLOR:
x_list.append(x)
y_list.append(y)
shadow_height = text_size[1]/4
for x, y in zip(x_list, y_list):
if y < min(y_list) + shadow_height or y > max(y_list)-shadow_height:
pixels[x, y] = SHADOW_COLOR
image.save(OUTPUT_IMAGE)
I have got it worked. Unfortunately it is realy slow, for huge Textsizes (like 100).
Someone have an Ide to simplifie this Function?
It is creating This Text for example: example
async def createTextWithOutline(self, image, x, y, text, font, outlineAmount, textColor, shadowColor, rarity):
# create outline text
drawObject = ImageDraw.Draw(image)
for adjX in range(outlineAmount):
for adjY in range(outlineAmount):
drawObject.text((x + adjX, y + adjY), text, font=font, fill=shadowColor)
drawObject.text((x + adjX, y - adjY), text, font=font, fill=shadowColor)
drawObject.text((x - adjX, y + adjY), text, font=font, fill=shadowColor)
drawObject.text((x - adjX, y - adjY), text, font=font, fill=shadowColor)
drawObject.text((x, y), text, font=font, fill=textColor)
if rarity != None:
if 'legendary' == rarity:
color = legendaryShadowColor
elif 'epic' == rarity:
color = epicShadowColor
elif 'rare' == rarity:
color = rareShadowColor
elif 'uncommon' == rarity:
color = uncommonShadowColor
else:
color = commonInnerColor
x_list = []
y_list = []
pixels = image.load()
size = drawObject.textsize(text, font=font)
for i in range(int(x), int(x+size[0])):
for j in range(int(y), int(y+size[1])):
if pixels[i, j] == textColor + (255, ):
x_list.append(i)
y_list.append(j)
shaderHeight = size[1]//3.5
for i, j in zip(x_list, y_list):
if j < min(y_list) + shaderHeight or j > max(y_list) - shaderHeight:
pixels[i,j] = color + (255, )

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