123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- #!/usr/bin/python
- # -*- coding: utf-8 -*-
- import argparse
- import os
- import sys
- import math
- import textwrap
- from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageChops, ImageOps
- def add_mark(imagePath, mark, args):
- '''
- 添加水印,然后保存图片
- '''
- im = Image.open(imagePath)
- im = ImageOps.exif_transpose(im)
- image = mark(im)
- name = os.path.basename(imagePath)
- if image:
- if not os.path.exists(args.out):
- os.mkdir(args.out)
- new_name = os.path.join(args.out, name)
- if os.path.splitext(new_name)[1] != '.png':
- image = image.convert('RGB')
- image.save(new_name, quality=args.quality)
- print(name + " Success.")
- else:
- print(name + " Failed.")
- def set_opacity(im, opacity):
- '''
- 设置水印透明度
- '''
- assert opacity >= 0 and opacity <= 1
- alpha = im.split()[3]
- alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
- im.putalpha(alpha)
- return im
- def crop_image(im):
- '''裁剪图片边缘空白'''
- bg = Image.new(mode='RGBA', size=im.size)
- diff = ImageChops.difference(im, bg)
- del bg
- bbox = diff.getbbox()
- if bbox:
- return im.crop(bbox)
- return im
- def gen_mark(args):
- '''
- 生成mark图片,返回添加水印的函数
- '''
- # 字体宽度、高度
- is_height_crop_float = '.' in args.font_height_crop # not good but work
- width = len(args.mark) * args.size
- if is_height_crop_float:
- height = round(args.size * float(args.font_height_crop))
- else:
- height = int(args.font_height_crop)
- # 创建水印图片(宽度、高度)
- mark = Image.new(mode='RGBA', size=(width, height))
- # 生成文字
- draw_table = ImageDraw.Draw(im=mark)
- draw_table.text(xy=(0, 0),
- text=args.mark,
- fill=args.color,
- font=ImageFont.truetype(args.font_family,
- size=args.size))
- del draw_table
- # 裁剪空白
- mark = crop_image(mark)
- # 透明度
- set_opacity(mark, args.opacity)
- def mark_im(im):
- ''' 在im图片上添加水印 im为打开的原图'''
- # 计算斜边长度
- c = int(math.sqrt(im.size[0] * im.size[0] + im.size[1] * im.size[1]))
- # 以斜边长度为宽高创建大图(旋转后大图才足以覆盖原图)
- mark2 = Image.new(mode='RGBA', size=(c, c))
- # 在大图上生成水印文字,此处mark为上面生成的水印图片
- y, idx = 0, 0
- while y < c:
- # 制造x坐标错位
- x = -int((mark.size[0] + args.space) * 0.5 * idx)
- idx = (idx + 1) % 2
- while x < c:
- # 在该位置粘贴mark水印图片
- mark2.paste(mark, (x, y))
- x = x + mark.size[0] + args.space
- y = y + mark.size[1] + args.space
- # 将大图旋转一定角度
- mark2 = mark2.rotate(args.angle)
- # 在原图上添加大图水印
- if im.mode != 'RGBA':
- im = im.convert('RGBA')
- im.paste(mark2, # 大图
- (int((im.size[0] - c) / 2), int((im.size[1] - c) / 2)), # 坐标
- mask=mark2.split()[3])
- del mark2
- return im
- return mark_im
- def main():
- parse = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
- parse.add_argument("-f", "--file", type=str,
- help="image file path or directory")
- parse.add_argument("-m", "--mark", type=str, help="watermark content")
- parse.add_argument("-o", "--out", default="./output",
- help="image output directory, default is ./output")
- parse.add_argument("-c", "--color", default="#808080", type=str,
- help="text color like '#000000', default is #808080")
- parse.add_argument("-s", "--space", default=100, type=int,
- help="space between watermarks, default is 75")
- parse.add_argument("-a", "--angle", default=30, type=int,
- help="rotate angle of watermarks, default is 30")
- parse.add_argument("--font-family", default="SimHei.ttf", type=str,
- help=textwrap.dedent('''\
- font family of text, default is './font/SimHei.ttf'
- using font in system just by font file name
- for example 'PingFang.ttc', which is default installed on macOS
- '''))
- parse.add_argument("--font-height-crop", default="2.2", type=str,
- help=textwrap.dedent('''\
- change watermark font height crop
- float will be parsed to factor; int will be parsed to value
- default is '2.2', meaning 2.2 times font size
- this useful with CJK font, because line height may be higher than size
- '''))
- parse.add_argument("--size", default=50, type=int,
- help="font size of text, default is 50")
- parse.add_argument("--opacity", default=0.15, type=float,
- help="opacity of watermarks, default is 0.15")
- parse.add_argument("--quality", default=80, type=int,
- help="quality of output images, default is 80")
- args = parse.parse_args()
- if isinstance(args.mark, str) and sys.version_info[0] < 3:
- args.mark = args.mark.decode("utf-8")
- mark = gen_mark(args)
- if os.path.isdir(args.file):
- names = os.listdir(args.file)
- for name in names:
- image_file = os.path.join(args.file, name)
- add_mark(image_file, mark, args)
- else:
- add_mark(args.file, mark, args)
- if __name__ == '__main__':
- main()
|