PIL如何根据图像大小缩放文本大小

发布于 2024-10-16 02:30:02 字数 269 浏览 0 评论 0原文

我正在尝试动态缩放要放置在不同但已知尺寸的图像上的文本。该文本将被应用为水印。有没有办法根据图像尺寸缩放文本?我不要求文本占据整个表面区域,只是足够可见,以便易于识别且难以删除。我正在使用 Python 成像库版本 1.1.7。在 Linux 上。

我希望能够设置文本大小与图像尺寸的比率,例如大小的 1/10 之类的。

我一直在研究字体大小属性来更改大小,但我没有运气创建一个算法来缩放它。我想知道是否有更好的方法。

关于如何实现这一目标有什么想法吗?

谢谢

I'm trying to dynamically scale text to be placed on images of varying but known dimensions. The text will be applied as a watermark. Is there any way to scale the text in relation to the image dimensions? I don't require that the text take up the whole surface area, just to be visible enough so its easily identifiable and difficult to remove. I'm using Python Imaging Library version 1.1.7. on Linux.

I would like to be able to set the ratio of the text size to the image dimensions, say like 1/10 the size or something.

I have been looking at the font size attribute to change the size but I have had no luck in creating an algorithm to scale it. I'm wondering if there is a better way.

Any ideas on how I could achieve this?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

¢蛋碎的人ぎ生 2024-10-23 02:30:02

您可以增加字体大小,直到找到合适的为止。 font.getsize() 是告诉您渲染文本有多大的函数。

from PIL import ImageFont, ImageDraw, Image

image = Image.open('hsvwheel.png')
draw = ImageDraw.Draw(image)
txt = "Hello World"
fontsize = 1  # starting font size

# portion of image width you want text width to be
img_fraction = 0.50

font = ImageFont.truetype("arial.ttf", fontsize)
while font.getsize(txt)[0] < img_fraction*image.size[0]:
    # iterate until the text size is just larger than the criteria
    fontsize += 1
    font = ImageFont.truetype("arial.ttf", fontsize)

# optionally de-increment to be sure it is less than criteria
fontsize -= 1
font = ImageFont.truetype("arial.ttf", fontsize)

print('final font size',fontsize)
draw.text((10, 25), txt, font=font) # put the text on the image
image.save('hsvwheel_txt.png') # save it

如果这对您来说不够高效,您可以实现根查找方案,但我猜测与其他图像编辑过程相比, font.getsize() 函数只是小菜一碟。

You could just increment the font size until you find a fit. font.getsize() is the function that tells you how large the rendered text is.

from PIL import ImageFont, ImageDraw, Image

image = Image.open('hsvwheel.png')
draw = ImageDraw.Draw(image)
txt = "Hello World"
fontsize = 1  # starting font size

# portion of image width you want text width to be
img_fraction = 0.50

font = ImageFont.truetype("arial.ttf", fontsize)
while font.getsize(txt)[0] < img_fraction*image.size[0]:
    # iterate until the text size is just larger than the criteria
    fontsize += 1
    font = ImageFont.truetype("arial.ttf", fontsize)

# optionally de-increment to be sure it is less than criteria
fontsize -= 1
font = ImageFont.truetype("arial.ttf", fontsize)

print('final font size',fontsize)
draw.text((10, 25), txt, font=font) # put the text on the image
image.save('hsvwheel_txt.png') # save it

If this is not efficient enough for you, you can implement a root-finding scheme, but I'm guessing that the font.getsize() function is small potatoes compared to the rest of your image editing processes.

温柔嚣张 2024-10-23 02:30:02

我知道这是一个老问题,已经用 我也使用过的解决方案。谢谢,@保罗!

尽管每次迭代将字体大小增加一可能会很耗时(至少对我来说在我可怜的小服务器上)。所以例如。小文本(如“Foo”)大约需要 1 - 2 秒,具体取决于图像大小。

为了解决这个问题,我调整了 Pauls 代码,使其搜索数字有点像二分搜索。

breakpoint = img_fraction * photo.size[0]
jumpsize = 75
while True:
    if font.getsize(text)[0] < breakpoint:
        fontsize += jumpsize
    else:
        jumpsize = jumpsize // 2
        fontsize -= jumpsize
    font = ImageFont.truetype(font_path, fontsize)
    if jumpsize <= 1:
        break

像这样,它会增加字体大小,直到它位于断点之上,然后从那里开始上下移动(每次向下将跳跃大小减少一半),直到它具有正确的大小。

这样,我可以将步骤从大约 200 多个减少到大约 10 个,从而将时间从大约 1-2 秒减少到 0.04 到 0.08 秒。

这是 Pauls 代码的直接替换(对于 while 语句及其后面的 2 行,因为您已经在 while 中获得了正确的字体大小)

这是只需几分钟即可完成,因此如有任何改进,我们将不胜感激!我希望这可以帮助那些正在寻找性能更友好的解决方案的人。

I know this is an old question that has already been answered with a solution that I too have used. Thanks, @Paul!

Though with increasing the font size by one for each iteration can be time-consuming (at least for me on my poor little server). So eg. small text (like "Foo") would take around 1 - 2 seconds, depending on the image size.

To solve that I adjusted Pauls code so that it searches for the number somewhat like a binary search.

breakpoint = img_fraction * photo.size[0]
jumpsize = 75
while True:
    if font.getsize(text)[0] < breakpoint:
        fontsize += jumpsize
    else:
        jumpsize = jumpsize // 2
        fontsize -= jumpsize
    font = ImageFont.truetype(font_path, fontsize)
    if jumpsize <= 1:
        break

Like this, it increases the font size until it's above the breakpoint and from there on out it goes up and down with (cutting the jump size in half with each down) until it has the right size.

With that, I could reduce the steps from around 200+ to about 10 and so from around 1-2 sec to 0.04 to 0.08 sec.

This is a drop-in replacement for Pauls code (for the while statement and the 2 lines after it because you already get the font correct font size in the while)

This was done in a few mins so any improvements are appreciated! I hope this can help some who are looking for a bit more performant friendly solution.

冰之心 2024-10-23 02:30:02

一般来说,当您更改字体大小时,字体大小不会发生线性变化。

非线性缩放

现在这通常取决于软件、字体等...这个例子取自 Typophile 并使用 LaTex + Computer Modern 字体。正如您所看到的,它并不完全是线性缩放。因此,如果您在非线性字体缩放方面遇到问题,那么我不确定如何解决它,但一个建议可能是解决。

  1. 将字体渲染为接近您想要的大小,然后通过常规图像缩放算法向上/向下缩放...
  2. 只需接受它不会完全是线性缩放并尝试创建某种将选择的表格/算法字体与图像大小最接近的点大小。

In general when you change the font sizing its not going to be a linear change in size of the font.

Non-linear Scaling

Now this often depends on the software, fonts, etc... This example was taken from Typophile and uses LaTex + Computer Modern font. As you can see its not exactly a linear scaling. So if you are having trouble with non-linear font scaling then I'm not sure how to resolve it, but one suggestion maybe is to.

  1. Render the font as closely to the size that you want, then scale that up/down via regular image scaling algorithm...
  2. Just accept that it won't exactly be linear scaling and try to create some sort of table/algorithm that will select the closest point size for the font to match up with the image size.
留蓝 2024-10-23 02:30:02

尽管其他答案说字体大小不会线性缩放,但在我测试的所有示例中,它们确实线性缩放(在 1-2% 内)。

因此,如果您需要一个在百分之几内工作的更简单、更高效的版本,您可以复制/粘贴以下内容:

from PIL import ImageFont, ImageDraw, Image

def find_font_size(text, font, image, target_width_ratio):
    tested_font_size = 100
    tested_font = ImageFont.truetype(font, tested_font_size)
    observed_width, observed_height = get_text_size(text, image, tested_font)
    estimated_font_size = tested_font_size / (observed_width / image.width) * target_width_ratio
    return round(estimated_font_size)

def get_text_size(text, image, font):
    im = Image.new('RGB', (image.width, image.height))
    draw = ImageDraw.Draw(im)
    return draw.textsize(text, font)

使用函数 find_font_size()(完整示例):

width_ratio = 0.5  # Portion of the image the text width should be (between 0 and 1)
font_family = "arial.ttf"
text = "Hello World"

image = Image.open('image.jpg')
editable_image = ImageDraw.Draw(image)
font_size = find_font_size(text, font_family, image, width_ratio)
font = ImageFont.truetype(font_family, font_size)
print(f"Font size found = {font_size} - Target ratio = {width_ratio} - Measured ratio = {get_text_size(text, image, font)[0] / image.width}")

editable_image.text((10, 10), text, font=font)
image.save('output.png')

然后可以像这样 将打印 225x225 图像:

>> Font size found = 22 - Target ratio = 0.5 - Measured ratio = 0.502

我使用各种字体和图片尺寸测试了 find_font_size(),并且它在所有情况下都有效。

如果你想知道这个函数是如何工作的,基本上可以使用 tested_font_size 来找出如果我们使用这个特定的字体大小来生成文本,将会获得哪个比例。然后,我们使用叉乘规则来获得目标字体大小。

我测试了 tested_font_size 的不同值,发现只要不是太小,就没有任何区别。

Despite other answers saying that font size do not scale linearly, in all the examples that I tested they did scale linearly (within 1-2%).

So if you need a simpler and more efficient version that works within a few percent, you can copy/paste the following:

from PIL import ImageFont, ImageDraw, Image

def find_font_size(text, font, image, target_width_ratio):
    tested_font_size = 100
    tested_font = ImageFont.truetype(font, tested_font_size)
    observed_width, observed_height = get_text_size(text, image, tested_font)
    estimated_font_size = tested_font_size / (observed_width / image.width) * target_width_ratio
    return round(estimated_font_size)

def get_text_size(text, image, font):
    im = Image.new('RGB', (image.width, image.height))
    draw = ImageDraw.Draw(im)
    return draw.textsize(text, font)

The function find_font_size() can then be used like that (full example):

width_ratio = 0.5  # Portion of the image the text width should be (between 0 and 1)
font_family = "arial.ttf"
text = "Hello World"

image = Image.open('image.jpg')
editable_image = ImageDraw.Draw(image)
font_size = find_font_size(text, font_family, image, width_ratio)
font = ImageFont.truetype(font_family, font_size)
print(f"Font size found = {font_size} - Target ratio = {width_ratio} - Measured ratio = {get_text_size(text, image, font)[0] / image.width}")

editable_image.text((10, 10), text, font=font)
image.save('output.png')

Which for a 225x225 image would print:

>> Font size found = 22 - Target ratio = 0.5 - Measured ratio = 0.502

I tested find_font_size() with various fonts and picture sizes, and it worked in all cases.

If you want to know how this function works, basically tested_font_size is used to find out which ratio will be obtained if we use this specific font size to generate the text. Then, we use a cross-multiplication rule to get the targeted font size.

I tested different values for tested_font_size and found that as long as it's not too small, it does not make any difference.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文