【问题标题】:Is there a way to outline text with a dark line in PIL?有没有办法在 PIL 中用黑线勾勒文本?
【发布时间】:2020-10-07 21:45:05
【问题描述】:

我正在使用 python/PIL 在一组 PNG 图像上编写文本。我能够得到我想要的字体,但现在我想用黑色勾勒出文本。

这就是我所拥有的: 如您所见,如果背景为白色,则难以阅读。

这是目标:

有没有办法用 PIL 来完成这个?如果没有,我愿意听取其他建议,但没有任何承诺,因为我已经使用 PIL 在 python 中开始了一个大型项目。

处理图像绘制的代码部分:

for i in range(0,max_frame_int + 1):
    writeimg = Image.open("frameinstance" + str(i) + ".png")
    newimg = Image.new("RGB", writeimg.size)
    newimg.paste(writeimg)
    width_image = newimg.size[0]
    height_image = newimg.size[1]
    draw = ImageDraw.Draw(newimg)
    # font = ImageFont.truetype(<font-file>, <font-size>)
    for font_size in range(50, 0, -1):
        font = ImageFont.truetype("impact.ttf", font_size)
        if font.getsize(meme_text)[0] <= width_image:
            break
    else:
        print('no fonts fit!')

    # draw.text((x, y),"Sample Text",(r,g,b))
    draw.text((int(0.05*width_image), int(0.7*height_image)),meme_text,(255,255,255),font=font)
    newimg.save("newimg" + str(i) +".png")

【问题讨论】:

标签: python image python-3.x image-processing python-imaging-library


【解决方案1】:

你可以看看这个Text Outline Using PIL

import Image, ImageFont, ImageDraw

import win32api, os

x, y = 10, 10

fname1 = "c:/test.jpg"
im = Image.open(fname1)
pointsize = 30
fillcolor = "red"
shadowcolor = "yellow"

text = "hi there"

font = win32api.GetWindowsDirectory() + "\\Fonts\\ARIALBD.TTF"
draw = ImageDraw.Draw(im)
font = ImageFont.truetype(font, pointsize)

# thin border
draw.text((x-1, y), text, font=font, fill=shadowcolor)
draw.text((x+1, y), text, font=font, fill=shadowcolor)
draw.text((x, y-1), text, font=font, fill=shadowcolor)
draw.text((x, y+1), text, font=font, fill=shadowcolor)

# thicker border
draw.text((x-1, y-1), text, font=font, fill=shadowcolor)
draw.text((x+1, y-1), text, font=font, fill=shadowcolor)
draw.text((x-1, y+1), text, font=font, fill=shadowcolor)
draw.text((x+1, y+1), text, font=font, fill=shadowcolor)

# now draw the text over it
draw.text((x, y), text, font=font, fill=fillcolor)

fname2 = "c:/test2.jpg"
im.save(fname2)

os.startfile(fname2)

【讨论】:

  • 要单独提供链接,您可以使用 cmets 部分。如果您有答案或要证明的观点,请使用答案部分。
  • 顺便说一句,这是一个有用的链接:)
  • 当然谢谢。
  • 此解决方案的问题在更高像素尺寸下变得明显。相反,最好使用 ImageDraw.text 支持的内置 stroke_widthstroke_fill 参数:pillow.readthedocs.io/en/stable/reference/…
【解决方案2】:

勾选stroke_width选项,实现stroke/border/outline效果。对于shadow 效果,您可以使用小偏移量进行绘制。 Bellow 是为图像添加字幕的示例。

#!/usr/bin/env python
from PIL import Image, ImageDraw, ImageFont


def add_subtitle(
    bg,
    text="nice",
    xy=("center", 20),
    font="font/SourceHanSansSC-Regular.otf",
    font_size=53,
    font_color=(255, 255, 255),
    stroke=2,
    stroke_color=(0, 0, 0),
    shadow=(4, 4),
    shadow_color=(0, 0, 0),
):
    """draw subtitle on image by pillow
    Args:
        bg(PIL image): image to add subtitle
        text(str): subtitle
        xy(tuple): absolute top left location of subtitle
        ...: extra style of subtitle
    Returns:
        bg(PIL image): image with subtitle
    """
    stroke_width = stroke
    xy = list(xy)
    W, H = bg.width, bg.height
    font = ImageFont.truetype(str(font), font_size)
    w, h = font.getsize(text, stroke_width=stroke_width)
    if xy[0] == "center":
        xy[0] = (W - w) // 2
    if xy[1] == "center":
        xy[1] = (H - h) // 2
    draw = ImageDraw.Draw(bg)
    if shadow:
        draw.text(
            (xy[0] + shadow[0], xy[1] + shadow[1]), text, font=font, fill=shadow_color
        )
    draw.text(
        (xy[0], xy[1]),
        text,
        font=font,
        fill=font_color,
        stroke_width=stroke_width,
        stroke_fill=stroke_color,
    )
    return bg


if __name__ == "__main__":
    bg = Image.open("./Screenshot_20200626-131218.png")
    bg = add_subtitle(bg)
    bg.save("out.png")

【讨论】:

  • 这是迄今为止最好的答案。完全符合 OP 的要求,无需额外代码(stroke_width 和 stroke_fill 参数完全不同)。
【解决方案3】:

当我需要为帧计数器执行此问题时,这就是我处理问题的方式。如果您开始将这个厚度推得太远,请注意,那么您将需要更多的平局来覆盖您缺少的区域。

from PIL import Image,ImageDraw,ImageFont
import os

#setting varibles
imgFile = "frame_0.jpg"
output = "frame_edit_0.jpg"
font = ImageFont.truetype("arial.ttf", 30)
text = "SEQ_00100_SHOT_0004_FR_0001"
textColor = 'white'
shadowColor = 'black'
outlineAmount = 3

#open image
img = Image.open(imgFile)
draw = ImageDraw.Draw(img)

#get the size of the image
imgWidth,imgHeight = img.size

#get text size
txtWidth, txtHeight = draw.textsize(text, font=font)

#get location to place text
x = imgWidth - txtWidth - 100
y = imgHeight - txtHeight - 100

#create outline text
for adj in range(outlineAmount):
    #move right
    draw.text((x-adj, y), text, font=font, fill=shadowColor)
    #move left
    draw.text((x+adj, y), text, font=font, fill=shadowColor)
    #move up
    draw.text((x, y+adj), text, font=font, fill=shadowColor)
    #move down
    draw.text((x, y-adj), text, font=font, fill=shadowColor)
    #diagnal left up
    draw.text((x-adj, y+adj), text, font=font, fill=shadowColor)
    #diagnal right up
    draw.text((x+adj, y+adj), text, font=font, fill=shadowColor)
    #diagnal left down
    draw.text((x-adj, y-adj), text, font=font, fill=shadowColor)
    #diagnal right down
    draw.text((x+adj, y-adj), text, font=font, fill=shadowColor)

#create normal text on image
draw.text((x,y), text, font=font, fill=textColor)

img.save(output)
print 'Finished'
os.startfile(output)

【讨论】:

  • 这对我有用。我在内部使用了一个循环来缩短代码:``` dxy = [(dx,dy) for dx in [-1,0,1] for dy in [-1,0,1]] for i in range(1, 1+outline): for dx,dy in dxy: draw.text((x+dxi, y+dyi), **kwshadow)```
【解决方案4】:
import Image, ImageFont, ImageDraw

import win32api, os

x, y = 10, 10

fname1 = "c:/test.jpg"
im = Image.open(fname1)
pointsize = 30
fillcolor = "red"
shadowcolor = "yellow"

text = "hi there"

font = win32api.GetWindowsDirectory() + "\\Fonts\\ARIALBD.TTF"
draw = ImageDraw.Draw(im)
font = ImageFont.truetype(font, pointsize)

# thin border
draw.text((x-1, y), text, font=font, fill=shadowcolor)
draw.text((x+1, y), text, font=font, fill=shadowcolor)
draw.text((x, y-1), text, font=font, fill=shadowcolor)
draw.text((x, y+1), text, font=font, fill=shadowcolor)

# thicker border
draw.text((x-1, y-1), text, font=font, fill=shadowcolor)
draw.text((x+1, y-1), text, font=font, fill=shadowcolor)
draw.text((x-1, y+1), text, font=font, fill=shadowcolor)
draw.text((x+1, y+1), text, font=font, fill=shadowcolor)

# now draw the text over it
draw.text((x, y), text, font=font, fill=fillcolor)

fname2 = "c:/test2.jpg"
im.save(fname2)

os.startfile(fname2)

【讨论】:

    【解决方案5】:

    我在互联网上寻找,我看到 PIL 不支持添加文本边框。在这种情况下,我根据Alec Bennett solution 创建了这个提案方法。

    我在该解决方案中发现的问题是如何在较大边框尺寸上创建平滑边框。 Problem Example

    如果我们走过 360 度或 2pi 弧度,添加相同的文本,我们可以更好地实现文本边框功能。

    这是示例函数

        def add_text(image, text, location, font, fontsize=14, fontcolor=(0, 0, 0), 
                     border=0, border_color=(0, 0, 0), points=15):
            font_format = ImageFont.truetype(font, fontsize)
            drawer = ImageDraw.Draw(image)
    
            if border:
                (x, y) = location
                for step in range(0, math.floor(border * points), 1):
                    angle = step * 2 * math.pi / math.floor(border * points)
                    drawer.text((x - border * math.cos(angle), y - border * math.sin(angle)), text, border_color, font=font_format)
            drawer.text(location, text, fontcolor, font=font_format)
            return image
    

    同样的图片、文字和配置生成下一个Final Result

    【讨论】:

      猜你喜欢
      • 2012-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-27
      • 1970-01-01
      • 2014-01-30
      • 2021-03-31
      相关资源
      最近更新 更多