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

一个生成随机水印的脚本

作者: 愤愤的有痣青年 | 来源:发表于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