明水印生成

明水印生成

注意
本文最后更新于 2023-11-12,文中内容可能已过时。

明水印生成

需求

  1. 只提供单条水印的图片蒙版,调用方自行嵌入。
  2. 指定文本内容,背景色,图片高度宽度,字体大小,空白填充区域,旋转角度等参数

步骤

  1. ImageFont.truetype ​生成字体样式
  2. Image.new ​生成背景​ blank
  3. ImageDraw.Draw ​生成画布​ draw
  4. 将文本根据字体样式以及画布大小进行拆分 – 防止文本过长导致被截断
  5. draw.text ​逐行绘制文本
  6. blank.crop ​裁剪出文字的区域​ text_im
  7. text_im.rotate ​将文本进行旋转后调整大小
  8. 填充空白区域 – Image.new ​创建一个更大的图片,paste ​来将旋转后的图片粘贴至中央后再 resize ​调整图片大小
  9. 将图片转换为 base64 字符串

这么麻烦的主要原因是经过旋转的文字为了不溢出会扩张画布,但是为了画布大小符合要求则需要不断调整画布大小,与此同时的代价就是字体会变小。

但实际最优的解决方法应该是先将画布旋转后根据每行的宽度动态填充文本,但实现比较复杂。如果有更好的实现麻烦大佬指点。

实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# -*- coding: utf-8 -*-

import base64
import io
import os
from typing import List, Tuple, TypedDict

from django.conf import settings
from PIL import Image, ImageDraw, ImageFont
from PIL.ImageFont import FreeTypeFont


class TextSplitter:
    """将文本按照指定宽度进行拆分"""

    def __init__(self, font: FreeTypeFont, max_width: float):
        self.font = font
        self.max_width = max_width

    def split_word(self, word: str):
        """
        将单词按照最大宽度进行拆分
        """

        if self.font.getlength(word) <= self.max_width:
            return [word]
        parts = []
        current_part = ""
        for char in word:
            current_part += char
            if self.font.getlength(current_part) > self.max_width:
                parts.append(current_part[:-1])
                current_part = char
        parts.append(current_part)
        return parts

    def split_text(self, text: str) -> List[str]:
        """
        按照最大宽度对文本进行拆分,优先将单词完整地放在一行,如果单词太长再考虑从中间切开
        """

        words = text.split(" ")
        lines = []
        current_line = ""

        for word in words:
            if not current_line:
                current_line = word
            elif self.font.getlength(f"{current_line} {word}") <= self.max_width:
                current_line += " " + word
            else:
                lines.append(current_line)
                current_line = word

            # 避免行过长
            if self.font.getlength(current_line) > self.max_width:
                parts = self.split_word(current_line)
                lines.extend(parts[:-1])
                # 保留最后一个切片用于下次拼接
                current_line = parts[-1]

        if current_line:
            lines.append(current_line)

        return lines


class Params(TypedDict):
    plaintext: str
    background_color: Tuple[int, int, int, int]
    image_width: int
    image_height: int
    padding: int
    font_size: int
    font_color: Tuple[int, int, int, int]
    rotation_angle: float


def generate(params: Params) -> str:
    """生成明水印"""
    # 获取配置信息
    plaintext = params["plaintext"]
    background_color = params["background_color"]
    image_width = params["image_width"]
    image_height = params["image_height"]
    padding = params["padding"]
    font_size = params["font_size"]
    font_color = params["font_color"]
    rotation_angle = params["rotation_angle"]
    font = ImageFont.truetype(os.path.join(settings.BASE_DIR, "static/font/SourceHanSerifCN-Regular.ttf"), font_size)
    # 生成水印
    text_width, text_height = font.getsize(plaintext)
    blank = Image.new("RGBA", (image_width, image_height), background_color)
    draw = ImageDraw.Draw(blank)
    # 拆分文本为多行
    multiline_text = TextSplitter(font, blank.width).split_text(plaintext)
    # 计算每行文本的高度和总高度
    total_height = text_height * len(multiline_text)
    # 逐行绘制文本
    for i, line in enumerate(multiline_text):
        line_width, _ = font.getsize(line)
        x = (blank.width - line_width) / 2
        y = (blank.height - total_height) / 2 + i * text_height
        draw.text((x, y), line, font=font, fill=font_color)
    # 裁剪出文字
    box = [
        0,
        total_height / 2,
        blank.width,
        blank.height / 2 + total_height / 2,
    ]
    text_im = blank.crop(box)
    # 旋转后裁剪大小
    text_rotate = text_im.rotate(rotation_angle, expand=True)
    text_rotate = text_rotate.resize((image_width, image_height), Image.ANTIALIAS)
    # 填充边距
    img = Image.new("RGBA", (image_width + padding, image_height + padding), background_color)
    paste_box = (int(padding / 2), int(padding / 2))
    img.paste(text_rotate, paste_box)
    img = img.resize((image_width, image_height), Image.ANTIALIAS)
    # 将图片转换为base64字符串
    buffer = io.BytesIO()
    img.save(buffer, format="PNG")
    return base64.b64encode(buffer.getvalue()).decode("utf8")


def test() -> str:
    return generate(
        Params(
            plaintext="Raja abcdefghijklmnopqrstuvwxyz This is a long text 这是一段很长的文本 这是一段很长的文本",
            background_color=(255, 255, 255, 0),
            image_width=260,
            image_height=165,
            padding=40,
            font_size=16,
            font_color=(0, 0, 0, 50),
            rotation_angle=30,
        )
    )

效果

下载

参考

Pillow - 廖雪峰的官方网站 (liaoxuefeng.com)

Python Pillow(PIL)库的用法介绍-CSDN博客

相关内容