一个需求是对图片加上一个随机水印,另外,如果图片是透明背景色则加上一个随机背景后再加水印.
水印中的内容由代码中的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)
网友评论