I am trying to create images programatically on Python using Pillow library but I'm having problems with the image quality of the text inside the image.
I want to save the Image the I generate to PNG, so I'm setting the DPI when saving according to this, but whether I save with dpi=(72,72) or dpi=(600,600) it visually looks the same.
My code for doing it is the following:
from PIL import Image, ImageDraw, ImageFont
def generate_empty_canvas(width, height, color='white'):
size = (width, height)
return Image.new('RGB', size, color=color)
def draw_text(text, canvas):
font = ImageFont.truetype('Verdana.ttf', 10)
draw = ImageDraw.Draw(canvas)
if '\n' not in text:
draw.text((0, 0), text, font=font, fill='black')
else:
draw.multiline_text((0, 0), text, font=font, fill='black')
def create_sample():
text = 'aaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbbbbbb\nccccccccccccccccccccc'
canvas = generate_empty_canvas(200, 50)
draw_text(text, canvas)
canvas.save('low_quality.png', dpi=(72, 72))
canvas.save('high_quality.png', dpi=(600, 600))
The low_quality.png is:
The high_quality.png is:
As it's visible by the images the quality didn't change.
What am I doing wrong here?
Where do I set the DPI so that the image really has dpi=600?
The DPI values are only metadata on computer images. They give hints on how to display or print an image.
Printing a 360×360 image with 360 dpi will result in a 1×1 inches printout.
A simplified way to explain it: The DPI setting recommends a zoom level for the image.
Saving with other DPIs will not change the content of the image. If you want a larger image create a larger canvas and use a larger font.
Related
Basically, what I want to do with Pillow is:
I want to get an image, and then extend the size of the image from the bottom so I'm able to fit a black rectangle with a four digit code on it. How would I do this? I tried to, but my text ended up being, for some reason, extremely small and unreadable and my rectangle wasn't perfect.
If it makes it easier, here's my image: https://i.stack.imgur.com/o9eYr.jpg
And here's what I want the end result to be: https://i.stack.imgur.com/GZ4uu.jpg (take a look at the bottom of the image)
I would suggest ImageOps.expand to expand your canvas:
#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFont, ImageOps
# Load image
im = Image.open('o9eYr.jpg')
# Define font size, and annotation and height of padding above and below annotation
fontSize = 130
annotation = "GVVL"
padding = 20
# Load font and work out size of annotation
font = ImageFont.truetype("/System/Library/Fonts//Menlo.ttc", fontSize)
tw, th = font.getsize(annotation)
# Extend image at bottom and get height and width of new canvas
extended = ImageOps.expand(im, border=(0,0,0,th+2*padding), fill=(0,0,0))
w, h = extended.size
# Get drawing context and annotate
draw = ImageDraw.Draw(extended)
draw.text(((w-tw)//2, h-th-padding), annotation,(255,255,255),font=font)
extended.save('result.jpg')
You could create a new black image, paste the desired image and add text.
from PIL import Image, ImageFont, ImageDraw
base_img = Image.open('tmp.jpg')
base_size = base_img.size
new_size = (base_size[0], base_size[1] + 150)
img = Image.new("RGB", new_size)
img.paste(base_img, (0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("microsoftsansserif.ttf", 145) # (<font-file>, <font-size>)d
draw.text((base_size[0] // 2 - 150, base_size[1]),"GVVL",(255,255,255),font=font) # (x, y),"text",(r,g,b)
img.save('out.jpg')
Result:
I am trying to write characters in specific locations in an image. I am using Pillow v6, Python 3.6.
Here is my code, I draw char by char, passing the top left point that I calculated.
font = ImageFont.truetype('platechar.tff', 500)
def draw_single_char(img, font, val, tl): #tl = (x,y)
pil_img = Image.fromarray(np.uint8(img))
draw = ImageDraw.Draw(pil_img)
draw.text(tl, val, (0,0,0), font=font)
img = np.array(pil_img)
return img
The output is not centered, I got the character width and height from the font, then with my top left point I draw the rectangle enclosing the character. The character is not centered inside the rectangle.
Font: https://drive.google.com/open?id=1N9rN-AgjK83U9ZDycLKxjeMP3o36vbfg
I want it to be like this (another font)
EDIT
Using Bidu Font editor I was able to remove the horizontal space (blue line). How can I center it vertically?.
Result so far ...
It looks like the font you are using contains non-centered numbers inside originally. So you should choose another font or you can modify your placechar.tff in a special editor for fonts.
Also you can calculate coordinate offsets for each symbol manually, store them into a dictionary and apply it for your text before drawing. It doesn't look like a good idea, but it would work also.
Calculate the width and height of the text to be drawn:
from PIL import Image, ImageDraw, ImageFont
txt='7'
font = ImageFont.truetype('platechar.ttf', 250)
(W, H) = font.getsize(txt)
image = Image.new('RGB', (256, 256), (63, 63, 63, 0))
drawer = ImageDraw.Draw(image)
(offset_w, offset_h) = font.getoffset(txt)
(x, y, W_mask, H_mask) = font.getmask(txt).getbbox()
drawer.text((10, 10 - offset_h), txt, align='center', font=font)
drawer.rectangle((10, 10, W + offset_w, 10 + H - offset_h), outline='black')
drawer.rectangle((x+10, y+10, W_mask+10, H_mask+10), outline='red')
image.show()
image.save('example.png', 'PNG')
After taking the path that #Fomalhaut suggested, using font editor. I found Bidu font editor (link in the question). I was able to fix the horizontal space (also shown in the question). For vertical space, after searching the menus, I found setting option to change the ascent.
I decreased it to 1440, and it worked.
I am working on script that writes over images and makes the background transparent. Output is supposed to be in GIF format.
The script works but for certain images the transparency is not working as expected.
Here is the script
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
CANVAS_HEIGHT = 354
CANVAS_WIDTH = 344
def get_text_mask():
font_style_path = 'Ultra-Regular.ttf'
text_mask_base = Image.new('L', (CANVAS_WIDTH, CANVAS_HEIGHT), 255)
text_mask = text_mask_base.copy()
text_mask_draw = ImageDraw.Draw(text_mask)
font = ImageFont.truetype(font_style_path, 94)
text_mask_width, text_mask_height = text_mask_draw.multiline_textsize("1000\nUsers",
font=font)
text_mask_draw.multiline_text(((CANVAS_WIDTH - text_mask_width) / 2,
(CANVAS_HEIGHT - text_mask_height) / 2),
"1000\nUsers",
font=font,
fill=0,
align='center')
return text_mask
def run():
images = ['image1.png', 'image2.png']
for index, original_image in enumerate(images):
image = Image.open(original_image)
blank_canvas = Image.new('RGBA', (CANVAS_WIDTH, CANVAS_HEIGHT), (255, 255, 255, 0))
text_mask = get_text_mask()
final_canvas = blank_canvas.copy()
for i in xrange(0, CANVAS_WIDTH, image.width):
for j in xrange(0, CANVAS_HEIGHT, image.height):
final_canvas.paste(image, (i, j))
final_canvas.paste(text_mask, mask=text_mask)
final_canvas.convert('P', palette=Image.ADAPTIVE)
final_canvas.save("output-{}.gif".format(index), format="GIF", transparency=0)
run()
image1.png
image2.png
And the font is here
https://bipuljain.com/static/images/Ultra-Regular.ttf
And the output with issue.
And output working fine.
The problem is that your "original image" contains the same index-color that the GIFs use to signify "this pixel is transparent".
Gifs are "palette-based" - one index into this palette is designated as "this is transparent" (see f.e. https://en.wikipedia.org/wiki/GIF)
So if you specify pure black or pure white as color-index that is transparent and your sourceimage already contains pixels with this exact color, they get to be transperent as well.
To avoid this, you could sample your source background-image and choose a "non-existent" color as transparency-color - this would never get to be in your resulting image.
You could also change your source images pixel values - check all pixel and change all "background-ones" a tiny fraction off so they do not get translucent.
Is it possible to render text with PIL, but make the image as small dimension-wise as possible? Currently I am having to make a large image, since I can't know how long the string being entered will be, however the text renders in the top left corner, leaving loads of extra space in the image.
Would it be possible to shrink the image to the size of the text that is rendered? Or predict the size of the text before it is rendered so the correct size image can be created?
Here is the code I am currently using:
font = ImageFont.truetype("BebasNeue.ttf", 45)
image = Image.new("RGBA", (400, 400), None)
draw = ImageDraw.Draw(image)
draw.text((0, 0), self.text, font=font, fill="white")
del draw
The font objects have a method getsize(text) that returns a tuple (width, height).
I use the following two methods to to generate text preview image for a .ttf font file
PIL method:
def make_preview(text, fontfile, imagefile, fontsize=30):
try:
font = ImageFont.truetype(fontfile, fontsize)
text_width, text_height = font.getsize(text)
img = Image.new('RGBA', (text_width, text_height))
draw = ImageDraw.Draw(img)
draw.text((0, 0), text, font=font, fill=(0, 0, 0))
return True
except:
return False
ImageMagick method:
def make_preview(text, fontfile, imagefile, fontsize=30):
p = subprocess.Popen(['convert', '-font', fontfile, '-background',
'transparent', '-gravity', 'center', '-pointsize', str(fontsize),
'-trim', '+repage', 'label:%s' % text, image_file])
return p==0
Both methods create correct preview images most of time but in some rare cases (<2%), the font.getsize(text) just cannot get the correct text size which result in text overflowed provided canvas. ImageMagick has same problem.
Sample fonts and previews:
HANFORD.TTF
http://download.appfile.com/HANFORD.png
NEWTOW.TTF
http://download.appfile.com/NEWTOW.png
MILF.TTF
http://download.appfile.com/MILF.png
SWANSE.TTF
http://download.appfile.com/SWANSE.png
I have looked into ImageMagick's documentations and found the explanation of this problem at http://www.imagemagick.org/Usage/text/#overflow.
Is it possible to detect such text overflows and draw text to fit the canvas as we expected?
Not a programming solution, but when I regenerate your problem, its only happens on your fonts (other fonts like Arial is no problem at all), so I have fixed your font files (by changing ascent/decent metrics). you can download here,
And sorry about Hanford Script Font, its not perfect as you see, height seems ok, but left side is not get drawed, its out of my understanding.
UPDATE: Regarding Hanford Font, Here is a work around, pass extra space in text like " Handford Script", and then crop the extra space in image like img=img.crop(img.getbbox())
alt text http://img64.imageshack.us/img64/1903/hanfordfontworkaround.jpg
UPDATE2:I had to pass color=(255,255,255) in Image.New to get Black Text on White background
img = Image.new('RGBA', (text_width, text_height),color=(255,255,255))
I had a similar issue once in PHP and ImageMagick.
In the end, I solved this by drawing the text on a very large canvas, and then trimming it using the trim/auto-crop functions that shave extra space off the image.
If I understand your preview function right, it is actually already doing exactly that: It should be enough to just remove the width and height settings.
In this case, just specify ImageMagick to use a larger canvas size with a fixed font size and it will draw text at specified point size while keeping its integrity.
def make_preview(text, fontfile, imagefile, fontsize=30):
p = subprocess.call(['convert', '-font', fontfile, '-background',
'transparent', '-gravity', 'center', '-size', '1500x300',
'-pointsize', str(fontsize), '-trim', '+repage', 'label:%s' % text, image_file])
return p==0
If you need to fit text into specified canvas rather than using a fixed point size, you may need to resize the output image after it's created.
PIL doesn't do this very well drawing exotic fonts, no matter what point size you specify to load a font, it always overflows text outside output image.