How can I draw text with different stroke and fill colors on images with python?
Asked Answered
E

6

18

How can I draw text with different stroke and fill colors on images with python?

Here is some text with red stroke and gray fill.

Example

I tried to do this with PIL but there was no option for setting the stroke color.

Encincture answered 8/11, 2011 at 11:35 Comment(0)
R
16

Using cairo (with much code taken from here):

import cairo

def text_extent(font, font_size, text, *args, **kwargs):
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
    ctx = cairo.Context(surface)
    ctx.select_font_face(font, *args, **kwargs)
    ctx.set_font_size(font_size)
    return ctx.text_extents(text)

text='Example'
font="Sans"
font_size=55.0
font_args=[cairo.FONT_SLANT_NORMAL]
(x_bearing, y_bearing, text_width, text_height,
 x_advance, y_advance) = text_extent(font, font_size, text, *font_args)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(text_width), int(text_height))
ctx = cairo.Context(surface)
ctx.select_font_face(font, *font_args)
ctx.set_font_size(font_size)
ctx.move_to(-x_bearing, -y_bearing)
ctx.text_path(text)
ctx.set_source_rgb(0.47, 0.47, 0.47)
ctx.fill_preserve()
ctx.set_source_rgb(1, 0, 0)
ctx.set_line_width(1.5)
ctx.stroke()

surface.write_to_png("/tmp/out.png")

enter image description here

Rosebay answered 8/11, 2011 at 12:52 Comment(4)
Do I need to know the dimensions of the text to use this?Defector
I can get the needed info with Context.text_extents("Example") but after that I need to create a new surface with the correct width and height.Defector
Yes, unfortunately, I do not know a better way.Rosebay
@unutbu, i ported your code into C++ successfully, but having an issue reg transparency. can you check it out here ThanksBevy
G
9

PIL doesn't support this but you can fake it: Render the text four or eight times with the outline color using one pixel offsets:

x+1,y
x-1,y
x  ,y+1
x  ,y-1 

(four times version)

x+1,y+1
x  ,y+1
x-1,y+1

x+1,y
x-1,y

x+1,y-1
x  ,y-1 
x-1,y-1

(eight times version)

and then once at x,y with the fill color.

Gounod answered 8/11, 2011 at 12:45 Comment(3)
The 8x version is basically equivalent to rendering 4 times on only the diagonals.Liquesce
@HassanBaig You won't notice it.Gounod
It only works if the border is thin, for example, 1 pixel. If you use a wider border, the produced text is not so satisfactory.Lacto
R
7

Using imagemagick:

import subprocess

args = {
    'bgColor': 'transparent',
    'fgColor': 'light slate grey',
    'fgOutlineColor': 'red',
    'text': 'Example',
    'size': 72,
    'geometry': '350x100!',
    'output': '/tmp/out.png',
    'font': 'helvetica'
}

cmd = ['convert', 'xc:{bgColor}', '-resize', '{geometry}', '-gravity', 'Center', 
       '-font', '{font}', '-pointsize', '{size}', '-fill', '{fgColor}', 
       '-stroke', '{fgOutlineColor}', '-draw', "text 0,0 '{text}'", '-trim', '{output}']
cmd = [item.format(**args) for item in cmd]

proc = subprocess.Popen(cmd)
proc.communicate()

enter image description here

Rosebay answered 8/11, 2011 at 12:35 Comment(6)
Do I need to know the dimensions of the output text before I can create it?Defector
You may use any geometry you wish -- imagemagick will create an image of exactly that geometry as long as you use the exclamation point -- but if the font size is too large, part of the text may be cropped off.Rosebay
I would like to get a picture that is the exact size of the text. No cropped text and no extra space on some side.Defector
You can crop to the size of the text using the -trim option.Rosebay
It would be better to put the command and it's options into a list and then replace variables in each element. That would solve the problem of spaces and/or quote characters in arguments.Gounod
@AaronDigulla: Thanks for the suggestion/improvement.Rosebay
N
6

They added a native solution within Pillow since 6.2.0: https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.PIL.ImageDraw.ImageDraw.text

You now specify the stroke_fill which is the color of the outline and stroke_width which is the thickness of the outline.

Niklaus answered 15/3, 2020 at 16:32 Comment(2)
To update pillow, pip install -U pillow. This works for Pillow 7.2.Lacto
pillow.readthedocs.io/en/stable/reference/… link to the specific heading.Tay
B
2

You can use Inkscape:

import subprocess
subprocess.call("inkscape in.svg --export-text-to-path --export-plain-svg out.svg", shell = True)

note: you have to download Inkscape to use this, so not practical for permanent use

Boltonia answered 3/10, 2015 at 17:54 Comment(0)
L
2

Complementing @Aaron Digulla's answer, this is how you fake the outline color in PIL:

from PIL import Image, ImageDraw, ImageFont

def main():
    text_color = (255, 0, 0)
    outline_color = (0, 0, 255)
    size = (512, 256)
    img = Image.new(mode='RGB', size=size, color=(255, 255, 255))
    font = ImageFont.truetype(font="C:/WINDOWS/Fonts/STKAITI.TTF", size=100)
    drawer = ImageDraw.Draw(img)

    x = 10
    y = 10
    bd_w = 1
    drawer.text((x-bd_w, y), "测试文字", font=font, fill=outline_color)
    drawer.text((x, y-bd_w), "测试文字", font=font, fill=outline_color)
    drawer.text((x+bd_w, y), "测试文字", font=font, fill=outline_color)
    drawer.text((x, y+bd_w), "测试文字", font=font, fill=outline_color)

    drawer.text((x+bd_w, y-bd_w), "测试文字", font=font, fill=outline_color)
    drawer.text((x-bd_w, y-bd_w), "测试文字", font=font, fill=outline_color)
    drawer.text((x-bd_w, y+bd_w), "测试文字", font=font, fill=outline_color)
    drawer.text((x+bd_w, y+bd_w), "测试文字", font=font, fill=outline_color)

    drawer.text((x, y), "测试文字", font=font, fill=text_color)

    img.show()


if __name__ == "__main__":
    main()

But be warned that this method will produce unsatisfactory text if bd_w (border width) is set a little higher. See the following image for effect of bd_w:

border width = 1

enter image description here

border width = 4

enter image description here

border width = 7

enter image description here

It seems that for large text keeping border width below 4 is acceptable.

Lacto answered 20/11, 2018 at 10:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.