Skip to content

常用代码片段

1. 横向合并两张图片的函数

python
from PIL import Image


def merge_images_horizontally(img_path1, img_path2, output_path):
    # 打开图片
    img1 = Image.open(img_path1)
    img2 = Image.open(img_path2)

    # 获取图片尺寸
    width1, height1 = img1.size
    width2, height2 = img2.size

    # 按较大的高度调整较小图片的大小,并保持比例
    if height1 > height2:
        new_height = height1
        new_width = int((width2 / height2) * new_height)
        img2 = img2.resize((new_width, new_height), Image.Resampling.LANCZOS)
    else:
        new_height = height2
        new_width = int((width1 / height1) * new_height)
        img1 = img1.resize((new_width, new_height), Image.Resampling.LANCZOS)

    # 获取调整后的图片尺寸
    width1, height1 = img1.size
    width2, height2 = img2.size

    # 创建一个新的图像,用来存放合并后的图片
    new_img = Image.new('RGB', (width1 + width2, height1))

    # 将两张图片粘贴到新的图像中
    new_img.paste(img1, (0, 0))
    new_img.paste(img2, (width1, 0))

    # 保存合并后的图片
    new_img.save(output_path)

2. 给图片增加水印的工具

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


class WatermarkerStyles(enum.Enum):
    """水印样式"""
    STRIPED = 1  # 斜向重复
    CENTRAL = 2  # 居中


class Watermarker:
    """图片水印工具"""

    def __init__(
            self, image_path: str, text: str,
            style: WatermarkerStyles,
            angle=30,
            color='#D3D3D3',
            font_file='./arial.ttf',
            font_height_crop=1.2,
            opacity=0.15,
            quality=80,
            size=30,
            space=100,
            chars_per_line=8,
    ):
        """
        :param image_path:
        :param text: 水印文字
        :param angle: 角度
        :param color: 水印颜色
        :param font_file: 字体文件名
        :param font_height_crop: 水印字体高度裁剪大小(默认即可,按需调整)
        :param opacity: 水印透明度
        :param quality: 图片质量
        :param size: 水印单个文字的大小
        :param space: 水印的间距(仅斜向重复样式有效)
        :param chars_per_line: 每行字数(超过就换行,仅居中水印有效)
        """
        self.image_path = image_path
        self.text = text
        self.style = style
        self.angle = angle
        self.color = color
        self.font_file = font_file
        self.font_height_crop = font_height_crop
        self.opacity = opacity
        self.quality = quality
        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):
        """设置图片透明度"""
        assert 0 <= opacity <= 1
        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()
        return image.crop(bbox) if bbox else image

    def _generate_watermark_image(self, text: str):
        """生成水印图片"""
        width = len(text) * self.size
        height = round(self.size * self.font_height_crop)
        watermark_image = Image.new(mode='RGBA', size=(width, height))
        draw_table = ImageDraw.Draw(im=watermark_image)
        draw_table.text(
            xy=(0, 0),
            text=text,
            fill=self.color,
            font=ImageFont.truetype(self.font_file, size=self.size)
        )
        watermark_image = Watermarker.crop_image_edge(watermark_image)
        return Watermarker.set_image_opacity(watermark_image, self.opacity)

    def _add_mark_striped(self):
        """添加斜向重复水印"""
        origin_image = Image.open(self.image_path)
        origin_image = ImageOps.exif_transpose(origin_image)
        watermark_image = self._generate_watermark_image(self.text)
        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)
        if origin_image.mode != 'RGBA':
            origin_image = origin_image.convert('RGBA')
        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 = Image.open(self.image_path)
        origin_image = ImageOps.exif_transpose(origin_image)
        text_lines = textwrap.wrap(self.text, width=self.chars_per_line)
        text = '\n'.join(text_lines)
        watermark_image = self._generate_watermark_image(text)
        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)
        if origin_image.mode != 'RGBA':
            origin_image = origin_image.convert('RGBA')
        box = (
            int((origin_image.width - watermark_mask.width) / 2),
            int((origin_image.height - watermark_mask.height) / 2))
        origin_image.paste(watermark_mask, box, 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)

    def show(self):
        """显示带水印的图像"""
        self.image.show()

3. 向指定目录下文件批量写入文字水印

python
import os
import fnmatch
import shutil

from test import Watermarker, WatermarkerStyles

# 指定目录和文件扩展名
directory = '文件绝对路径'
extensions = ['*.png', '*.jpg', '*.jpeg']
filter_dirs = ['dist', 'public']


def find_files(directory, extensions):
    for root, dirs, files in os.walk(directory):
        for extension in extensions:
            for filename in fnmatch.filter(files, extension):
                # 过滤文件夹
                if any(filter_dir in root for filter_dir in filter_dirs):
                    continue
                
                # 原始路径
                origin_path = str(os.path.join(root, filename))

                # 从原始路径备份一个
                name, ext = os.path.splitext(filename)
                new_name = f"{name}_origin{ext}"
                os.rename(origin_path, os.path.join(root, new_name))
                new_path = str(os.path.join(root, new_name))
                
                # 向新文件路径写入水印
                Watermarker(image_path=new_path, text='https://yqqy.top', style=WatermarkerStyles.STRIPED, space=120).save(origin_path)
                print(origin_path + " >> 加水印完成")
                
                # 将原始路径文件移动到备份文件夹
                shutil.move(new_path, "./origin_blog/"+new_name)