Spaces:
Running
Running
| """ | |
| 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 | |
| def set_image_opacity(image: Image, opacity: float): | |
| alpha = image.split()[3] | |
| alpha = ImageEnhance.Brightness(alpha).enhance(opacity) | |
| image.putalpha(alpha) | |
| return image | |
| 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 | |
| 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() | |