美文网首页
一个生成随机水印的脚本

一个生成随机水印的脚本

作者: 愤愤的有痣青年 | 来源:发表于2019-09-29 09:47 被阅读0次

一个需求是对图片加上一个随机水印,另外,如果图片是透明背景色则加上一个随机背景后再加水印.
水印中的内容由代码中的CONTENT_PATH文件控制,其内容格式为:

{
    "汉字":{
        "font_size":1,  # 字的宽高比
        "weight":5, # 数量的权重,值越大此类型出现的频率越高
        "data":"术可丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由"    # 内容,将随机从此字段中读取
    },  # 内容类型
    "数字":{
        "weight":2,
        "font_size":0.5,
        "data":"0123456789"
    },
    "字母":{
        "weight":2,
        "font_size":0.5,
        "data":"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
    }

}

代码

"""
对图片生成随机水印,水印的随机项有:
字体透明度随机(不会完全透明)
字体随机
水印内容随机
水印长度随机
字体大小随机
字体颜色随机
字体背景颜色随机
字体背景透明度随机
水印位置随机
水印角度随机
"""
import json
import math
import os
import random

from PIL import Image, ImageDraw, ImageFont

SAVE_ROOT = 'src/result/'   # 存储目录
ORIGINAL_ROOT = 'src/original/' # 原图目录
FONT_ROOT_PATH = 'src/font/'    # 字体目录
CONTENT_PATH = 'src/content.txt' # 内容配置项
class Single(object):
    __cls_cache = {}

    def __new__(cls, *args, **kwargs):
        if cls not in cls.__cls_cache:
            cls.__cls_cache[cls] = object.__new__(cls)

        return cls.__cls_cache[cls]


# 字体随机
class Font(Single):
    

    def __init__(self):
        self._init_all_font_path()
        self._tmp_font_object = {}

    def _init_all_font_path(self):
        self._font_paths = [os.path.join(self.FONT_ROOT_PATH, path) for path in os.listdir(self.FONT_ROOT_PATH)]

    def get_font(self, font_size):
        path = self._font_paths[random.randint(0, len(self._font_paths) - 1)]
        if path not in self._tmp_font_object:
            self._tmp_font_object[path] = ImageFont.truetype(path, font_size)

        return self._tmp_font_object[path], path


# 内容随机
class Content(Single):


    def __init__(self):
        self._init_content()

    def _init_content(self):
        self._content_data = json.loads(open(self.CONTENT_PATH, 'rb').read().decode())
        contyent_keys = list(self._content_data.keys())
        self._content_keys = []
        for key in self._content_data.keys():
            self._content_keys.extend([key] * self._content_data[key]['weight'])

    def get_content(self, content_size):
        font_type = self._content_keys[random.randint(0, len(self._content_keys) - 1)]
        num = int(content_size / self._content_data[font_type]['font_size'])
        data = self._content_data[font_type]['data']
        return "".join([data[random.randint(0, len(data) - 1)] for _ in range(num)])


# 大小随机
class Size(object):
    """
    大小为水印大小,其由字体数量,字体大小组成,其中字体数量组成的宽度为字体大小的[2, 20]倍, 且水印最终缩放的宽度不会超过图片宽度的1/2
    """

    def __init__(self, img):
        self._img = img
        self._init_size()

    def _init_size(self):
        _max = self._img.width // 1.2

        while True:
            self._width = random.randint(int(_max * 0.3), _max)  # 字体总长
            self._num = max(exponential_function(18, False), 3)  # 字符数量
            self._height = self._width // self._num  # 字体大小
            if self._height >= 10:
                break

        self.font_rect = random.randint(1, 2)  # 字体在生成时放大的倍数(后续需要缩回来)
        self._font_size = self.font_rect * self._height
        self.padding = int(self._height * random.random() * self.font_rect)  # 边框填充值

    @property
    def font_size(self):
        return self._font_size

    @property
    def font_num(self):
        return self._num

    @property
    def angle(self):
        # 旋转角度
        return exponential_function(45)


def exponential_function(max_x, negative_number=True):
    if negative_number:
        k = random.randint(-1, 1)
        if k == 0:
            return 0
    else:
        k = 1
    return k * (max_x - int(math.sqrt(random.randint(0, max_x ** 2))))


# 颜色随机
class Color(object):
    def __init__(self):
        self.color()

    def color(self):
        self.font_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        self.back_color = tuple([255 - k for k in self.font_color])

        self.font_mask = random.randint(200, 255)

        self.back_mask = abs(exponential_function(self.font_mask, True))


# 位置随机
class Position(Single):

    def get_position(self, origin_size):

        def position(rect_size):
            # 图片中心点位于的热点位置
            w, h = [k // 2 for k in origin_size]
            width_space = origin_size[0] - rect_size[0]
            height_space = origin_size[1] - rect_size[1]

            rw_half, rh_half = [k // 2 for k in rect_size]

            for _ in range(20):
                bais_w = exponential_function(w)
                bais_h = exponential_function(h)
                rw, rh = bais_w - rw_half + w, bais_h + rh_half + h
                if min(rw, rh) > 0 and rw + rect_size[0] < origin_size[0] and rh + rect_size[1] < origin_size[1]:
                    break
            else:
                raise Exception("坐标失败 {} {}".format(origin_size, rect_size))
            return (rw, rh)

        return position


class Draw(object):

    def _resize(self, size, max_size=(1000, 1000)):
        size = list(size)
        if size[0] > max_size[0]:
            size[1] = (size[1] * max_size[0]) // size[0]
            size[0] = max_size[0]

        if size[1] > max_size[1]:
            size[0] = (size[0] * max_size[1]) // size[1]
            size[1] = max_size[1]

        return size

    def __init__(self, image, font, content, colors, position, size):
        self._image = image
        self._font = font
        self._colors = colors
        self.__position = position
        self._size = size
        self._content = content
        self.draw_font()

    def draw_font(self):
        draw = ImageDraw.Draw(Image.new('L', (1, 1)))
        text_size = draw.textsize(self._content, font=self._font)

        rect_size = (text_size[0] + self._size.padding, text_size[1] + self._size.padding)
        real_rect_size = self._resize([k // self._size.font_rect for k in rect_size],
                                      [int(k * 0.75) for k in self._image.size])

        self._position = self.__position(real_rect_size)
        rect_canvas = Image.new(
            'RGB',
            rect_size,
            self._colors.back_color)

        rect_mask = Image.new('L', rect_size, self._colors.back_mask)

        draw = ImageDraw.Draw(rect_canvas)
        x, y = self._size.padding // 2, self._size.padding // 2
        draw.text(
            (x, y),
            self._content,
            self._colors.font_color,
            font=self._font
        )
        draw = ImageDraw.Draw(rect_mask)
        draw.text(
            (x, y),
            self._content,
            self._colors.font_mask,
            font=self._font
        )
        r, g, b = rect_canvas.split()

        img = Image.merge('RGBA', (r, g, b, rect_mask)).resize(real_rect_size)

        model = Image.new('RGBA', self._image.size, (255, 255, 255, 0))
        model.paste(img, self._position)
        model = model.rotate(self._size.angle, fillcolor=0)
        r, g, b, mask = model.split()
        rgb = Image.merge('RGB', (r, g, b))
        self.img = Image.composite(rgb, self._image, mask)


def run(image_name):
    if not os.path.exists(image_name):
        return
    try:
        image = Image.open(image_name)
        k = len(image.split())
        if k == 1:
            return
        elif k == 4:
            images = PhotoChangeColor(image=image).composite()
            image = images[random.randint(0, len(images) - 1)]
            if image.mode != 'RGB':
                image = image.convert('RGB')

    except Exception as e:
        print(e)
        return
    data = os.path.split(image_name)[1].split(".")
    if len(data) == 1:
        data.append('jpg')
    name, suffix = data[0], data[-1]

    save_name = os.path.join(SAVE_ROOT, "{}_{}.{}")

    for i in range(2):
        for j in range(3):
            try:
                size = Size(image)
                font, path = font_model.get_font(size.font_size)
                content = Content().get_content(size.font_num)
                colors = Color()
                position = Position().get_position(image.size)
                draw = Draw(image, font, content, colors, position, size)
                result = draw.img
                file = save_name.format(name, i, suffix)
                result.save(file, format='JPEG', quality=100)
                break
            except Exception as e:
                print("异常", e, i, j)


class PhotoChangeColor(object):

    def __init__(self, image):
        """
        照片背景处理
        :param image:前景图
        :param mask: 遮罩
        :param colors: 背景图
        """
        self.image, self.mask = self.split_image(image)
        self.colors = [{"enc_color": 4427483, "color_name": "blue", "start_color": 4427483},
                       {"enc_color": 16777215, "color_name": "white", "start_color": 16777215},
                       {"enc_color": 16711680, "color_name": "red", "start_color": 16711680},
                       {"enc_color": 2118526, "color_name": "blue2", "start_color": 2118526},
                       {"enc_color": 10857149, "color_name": "grey", "start_color": 6580604},
                       {"enc_color": 13429244, "color_name": "blue3", "start_color": 6731250}]

    def split_image(self, image):
        r, g, b, l = image.split()

        return Image.merge('RGB', (r, g, b)), l

    def composite(self):
        data = []
        for back in self.background:
            img = Image.composite(self.image, back, self.mask)
            data.append(img)
        return data

    @property
    def background(self):
        for color in self.colors:
            back = self._generate_gradient_bgr(self.image.size, color['start_color'], color['enc_color'])
            yield back

    @staticmethod
    def rgb2hex(rgbcolor):
        r, g, b = rgbcolor
        return (r << 16) + (g << 8) + b

    def _generate_gradient_bgr(self, size, begin_color, end_color):
        begin_color = int(hex(begin_color), base=16)
        end_color = int(hex(end_color), base=16)
        begin = (
            (begin_color >> 16 & 0xFF),
            (begin_color >> 8 & 0xFF),
            (begin_color >> 0 & 0xFF),
        )
        end = (
            (end_color >> 16 & 0xFF),
            (end_color >> 8 & 0xFF),
            (end_color >> 0 & 0xFF),
        )
        if begin == end:
            return Image.new('RGB', size, begin)
        image = Image.new('RGB', size)
        from PIL import ImageDraw
        draw = ImageDraw.Draw(image)
        w, h = size
        for y in range(h):
            color = self._interpret(begin, end, float(y) / h)
            draw.line([0, y, w, y], color)
        return image

    def _interpret(self, begin, end, t):
        r = self._interpret_channel(begin[0], end[0], t)
        g = self._interpret_channel(begin[1], end[1], t)
        b = self._interpret_channel(begin[2], end[2], t)
        return r, g, b

    @staticmethod
    def _interpret_channel(begin, end, t):
        return int(round(begin * (1 - t) + end * t))


if __name__ == "__main__":
    files = [os.path.join(ORIGINAL_ROOT, k) for k in os.listdir(ORIGINAL_ROOT)]
    font_model = Font()
    for i, file in enumerate(files):
        print(i)
        run(file)

相关文章

网友评论

      本文标题:一个生成随机水印的脚本

      本文链接:https://www.haomeiwen.com/subject/gqpquctx.html