File size: 7,440 Bytes
929aa8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""
Reference: https://gist.github.com/Deali-Axy/e22ea79bfbe785f9017b2e3cd7fdb3eb
"""

import enum
import os
import math
import textwrap
from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageChops
import os

base_path = os.path.abspath(os.path.dirname(__file__))


class WatermarkerStyles(enum.Enum):
    """水印样式"""

    STRIPED = 1  # 斜向重复
    CENTRAL = 2  # 居中


class Watermarker(object):
    """图片水印工具"""

    def __init__(
        self,
        input_image: Image.Image,
        text: str,
        style: WatermarkerStyles,
        angle=30,
        color="#8B8B1B",
        font_file="青鸟华光简琥珀.ttf",
        opacity=0.15,
        size=50,
        space=75,
        chars_per_line=8,
        font_height_crop=1.2,
    ):
        """_summary_

        Parameters
        ----------
        input_image : Image.Image
            PIL图片对象
        text : str
            水印文字
        style : WatermarkerStyles
            水印样式
        angle : int, optional
            水印角度, by default 30
        color : str, optional
            水印颜色, by default "#8B8B1B"
        font_file : str, optional
            字体文件, by default "青鸟华光简琥珀.ttf"
        font_height_crop : float, optional
            字体高度裁剪比例, by default 1.2
        opacity : float, optional
            水印透明度, by default 0.15
        size : int, optional
            字体大小, by default 50
        space : int, optional
            水印间距, by default 75
        chars_per_line : int, optional
            每行字符数, by default 8
        """
        self.input_image = input_image
        self.text = text
        self.style = style
        self.angle = angle
        self.color = color
        self.font_file = os.path.join(base_path, "font", font_file)
        self.font_height_crop = font_height_crop
        self.opacity = opacity
        self.size = size
        self.space = space
        self.chars_per_line = chars_per_line
        self._result_image = None

    @staticmethod
    def set_image_opacity(image: Image, opacity: float):
        alpha = image.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
        image.putalpha(alpha)
        return image

    @staticmethod
    def crop_image_edge(image: Image):
        bg = Image.new(mode="RGBA", size=image.size)
        diff = ImageChops.difference(image, bg)
        bbox = diff.getbbox()
        if bbox:
            return image.crop(bbox)
        return image

    def _add_mark_striped(self):
        origin_image = self.input_image.convert("RGBA")
        width = len(self.text) * self.size
        height = round(self.size * self.font_height_crop)
        watermark_image = Image.new(mode="RGBA", size=(width, height))
        draw_table = ImageDraw.Draw(watermark_image)
        draw_table.text(
            (0, 0),
            self.text,
            fill=self.color,
            font=ImageFont.truetype(self.font_file, size=self.size),
        )
        watermark_image = Watermarker.crop_image_edge(watermark_image)
        Watermarker.set_image_opacity(watermark_image, self.opacity)

        c = int(math.sqrt(origin_image.size[0] ** 2 + origin_image.size[1] ** 2))
        watermark_mask = Image.new(mode="RGBA", size=(c, c))
        y, idx = 0, 0
        while y < c:
            x = -int((watermark_image.size[0] + self.space) * 0.5 * idx)
            idx = (idx + 1) % 2
            while x < c:
                watermark_mask.paste(watermark_image, (x, y))
                x += watermark_image.size[0] + self.space
            y += watermark_image.size[1] + self.space

        watermark_mask = watermark_mask.rotate(self.angle)
        origin_image.paste(
            watermark_mask,
            (int((origin_image.size[0] - c) / 2), int((origin_image.size[1] - c) / 2)),
            mask=watermark_mask.split()[3],
        )
        return origin_image

    def _add_mark_central(self):
        origin_image = self.input_image.convert("RGBA")
        text_lines = textwrap.wrap(self.text, width=self.chars_per_line)
        text = "\n".join(text_lines)
        width = len(text) * self.size
        height = round(self.size * self.font_height_crop * len(text_lines))
        watermark_image = Image.new(mode="RGBA", size=(width, height))
        draw_table = ImageDraw.Draw(watermark_image)
        draw_table.text(
            (0, 0),
            text,
            fill=self.color,
            font=ImageFont.truetype(self.font_file, size=self.size),
        )
        watermark_image = Watermarker.crop_image_edge(watermark_image)
        Watermarker.set_image_opacity(watermark_image, self.opacity)

        c = int(math.sqrt(origin_image.size[0] ** 2 + origin_image.size[1] ** 2))
        watermark_mask = Image.new(mode="RGBA", size=(c, c))
        watermark_mask.paste(
            watermark_image,
            (
                int((watermark_mask.width - watermark_image.width) / 2),
                int((watermark_mask.height - watermark_image.height) / 2),
            ),
        )
        watermark_mask = watermark_mask.rotate(self.angle)

        origin_image.paste(
            watermark_mask,
            (
                int((origin_image.width - watermark_mask.width) / 2),
                int((origin_image.height - watermark_mask.height) / 2),
            ),
            mask=watermark_mask.split()[3],
        )
        return origin_image

    @property
    def image(self):
        if not self._result_image:
            if self.style == WatermarkerStyles.STRIPED:
                self._result_image = self._add_mark_striped()
            elif self.style == WatermarkerStyles.CENTRAL:
                self._result_image = self._add_mark_central()
        return self._result_image

    def save(self, file_path: str, image_format: str = "png"):
        with open(file_path, "wb") as f:
            self.image.save(f, image_format)


# Gradio 接口
def watermark_image(
    image,
    text,
    style,
    angle,
    color,
    opacity,
    size,
    space,
):
    # 创建 Watermarker 实例
    watermarker = Watermarker(
        input_image=image,
        text=text,
        style=(
            WatermarkerStyles.STRIPED
            if style == "STRIPED"
            else WatermarkerStyles.CENTRAL
        ),
        angle=angle,
        color=color,
        opacity=opacity,
        size=size,
        space=space,
    )

    # 返回带水印的图片
    return watermarker.image


if __name__ == "__main__":
    import gradio as gr

    iface = gr.Interface(
        fn=watermark_image,
        inputs=[
            gr.Image(type="pil", label="上传图片", height=400),
            gr.Textbox(label="水印文字"),
            gr.Radio(choices=["STRIPED", "CENTRAL"], label="水印样式"),
            gr.Slider(minimum=0, maximum=360, value=30, label="水印角度"),
            gr.ColorPicker(label="水印颜色"),
            gr.Slider(minimum=0, maximum=1, value=0.15, label="水印透明度"),
            gr.Slider(minimum=10, maximum=100, value=50, label="字体大小"),
            gr.Slider(minimum=10, maximum=200, value=75, label="水印间距"),
        ],
        outputs=gr.Image(type="pil", label="带水印的图片", height=400),
        title="图片水印工具",
        description="上传一张图片,添加水印并下载。",
    )

    iface.launch()