| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- import os.path
- from PIL import Image, ImageDraw, ImageEnhance, ImageFile
- # import cv2
- import numpy as np
- from blend_modes import multiply
- ImageFile.LOAD_TRUNCATED_IMAGES = True
- class PictureProcessing(object):
- def __init__(self, *args, **kwargs):
- self.im: Image
- if args:
- p = args[0]
- if isinstance(p, str):
- if p == "RGB" or p == "RGBA":
- size = int(args[1][0]), int(args[1][1])
- self.im = Image.new(args[0], size, args[2])
- else:
- # 设计优先取值处理
- _path = args[0]
- file_path, file = os.path.split(_path)
- custom_path = "{}/custom/{}".format(file_path, file)
- if os.path.exists(custom_path):
- _path = custom_path
- self.im = Image.open(_path)
- elif "im" in kwargs:
- self.im = kwargs["im"]
- else:
- self.im = Image.new(mode="RGB", size=(1600, 500), color=(255, 255, 255))
- def getbbox(self):
- return self.im.getbbox()
- def expand_bbox(self, bbox, value=100):
- x1, y1, x2, y2 = bbox[0] - value, bbox[1] - value, bbox[2] + value, bbox[3] + value
- if x1 < 0:
- x1 = 0
- if y1 < 0:
- y1 = 0
- bbox = (x1, y1, x2, y2)
- return [int(x) for x in bbox]
- def show(self):
- self.im.show()
- def get_size(self):
- return self.im.size
- def get_im(self):
- self.im: Image
- return self.im
- def to_color_2(self, target, blend): # 正片叠底
- return np.array(np.multiply(target / 256, blend / 256) * 256, dtype=np.uint8)
- def get_overlay_pic(self, top_img, color=None):
- # 正片叠底
- top_img: PictureProcessing
- image_white = Image.new("RGB", (self.get_size()), color)
- backdrop_prepped = np.asfarray(image_white.convert('RGBA'))
- source_prepped = np.asfarray(self.im.convert('RGBA'))
- blended_np = multiply(backdrop_prepped, source_prepped, 1)
- img = Image.fromarray(np.uint8(blended_np)).convert('RGB')
- img.paste(top_img.im, (0, 0), top_img.im)
- return PictureProcessing(im=img)
- def to_overlay_pic_advance(self, mode="pixel", top_img="", base="nw", value=(0, 0), percentage=(0, 0),
- margins=(0, 0, 0, 0), opacity=100, top_png_img=None):
- # opacity 透明度
- # 正片叠底
- _p = PictureProcessing("RGBA", (self.width, self.height), (255, 255, 255, 255))
- top_img = _p.paste_img(mode=mode,
- top_img=top_img,
- base=base,
- value=value,
- percentage=percentage,
- margins=margins)
- backdrop_prepped = np.asfarray(self.im.convert('RGBA'))
- source_prepped = np.asfarray(top_img.im.convert('RGBA'))
- blended_np = multiply(backdrop_prepped, source_prepped, opacity / 100)
- im = Image.fromarray(np.uint8(blended_np)).convert('RGB')
- pp = PictureProcessing(im=im)
- if top_png_img:
- pp = pp.paste_img(mode=mode,
- top_img=top_png_img,
- base=base,
- value=value,
- percentage=percentage,
- margins=margins)
- return pp
- def __to_resize(self, width=None, height=None):
- _im_x, _im_y = self.get_size()
- if width and height:
- if _im_x >= _im_y:
- high = None
- else:
- width = None
- if width:
- re_x = int(width)
- re_y = int(_im_y * re_x / _im_x)
- else:
- re_y = int(height)
- re_x = int(_im_x * re_y / _im_y)
- img = self.get_im().resize((re_x, re_y))
- return img
- # 锐化图片
- def sharpen_image(self, factor=1.0):
- # 创建一个ImageEnhance对象
- enhancer = ImageEnhance.Sharpness(self.im)
- # 应用增强,值为0.0给出模糊图像,1.0给出原始图像,大于1.0给出锐化效果
- # 调整这个值来增加或减少锐化的程度
- sharp_img = enhancer.enhance(factor)
- return PictureProcessing(im=sharp_img)
- @property
- def width(self):
- return self.im.width
- @property
- def height(self):
- return self.im.height
- @property
- def size(self):
- return self.im.size
- def resize(self, mode="pixel", base="width", value=0, base_im=None, percentage=0, base_by_box=None):
- """
- plugins_mode # relative相对(宽度、高度、其他参考图),或 pixel绝对像素
- base # pixel,width,height,by_im 基于长边、基于短边 (基于短边时,则缩放到指定尺寸)by_im确保能塞进参考图内
- value # 固定值,如果为百分比,则为0
- percentage #百分比
- base_im # 参考基于其他图 PictureProcessing 格式
- """
- # 在箱子内
- if base_by_box:
- mode = "relative"
- base = "by_im"
- base_by_box = (int(base_by_box[0]), int(base_by_box[1]))
- base_im = Image.new("RGB", base_by_box, (255, 255, 255))
- # 绝对值
- if mode == "pixel":
- if base == "width":
- img = self.__to_resize(width=value)
- if base == "high":
- img = self.__to_resize(height=value)
- # 相对值
- if mode == "relative":
- if base == "width":
- img = self.__to_resize(
- width=self.width * percentage if not base_im else int(base_im.width * percentage))
- if base == "height":
- img = self.__to_resize(
- width=self.height * percentage if not base_im else int(base_im.height * percentage))
- # by_im确保能塞进参考图内
- if base == "by_im":
- percentage = 1 if not percentage else percentage
- box_width, box_height = int(base_im.width * percentage), int(base_im.height * percentage)
- width, height = self.width, self.height
- if box_width / box_height < width / height:
- scale = box_width / width
- else:
- scale = box_height / height
- img = self.get_im().resize((int(width * scale), int(height * scale)))
- return PictureProcessing(im=img)
- def paste_img(self, mode="pixel", top_img="", base="nw", value=(0, 0), percentage=(0, 0), margins=(0, 0, 0, 0)):
- top_img: PictureProcessing
- """
- {
- "command": "paste_img",
- "im": 需要粘贴的图片
- "pos": {"plugins_mode": "relative", # pixel
- "base": "center", # nw,nc,ne,ec ... 各个方向参考点
- "value": (100, 100),
- "percentage": (0.5, 0.5),
- },
- "margins": (0, 0, 0, 0), # 上下左右边距
- }
- """
- value = (int(value[0]), int(value[1]))
- # 处理默认值
- base = "nw" if not base else base
- percentage = (0, 0) if not percentage else percentage
- if margins:
- top, down, left, right = margins
- else:
- top, down, left, right = 0, 0, 0, 0
- if percentage != (0, 0): # percentage 不按占比模式
- if base in ("nw", "wn", "wc", "cw", "nc", "cn", "center"):
- value = (int(self.width), int(self.height))
- if base in ("sw", "ws", "sc", "cs", "center"):
- value = (int(self.width), -1 * int(self.height))
- if base in ("ec", "ce"):
- value = (int(self.width), int(self.height))
- if mode == "pixel":
- # 基于右边,上下居中
- if base == "ec" or base == "ce":
- p_x = int(self.width - (top_img.width + value[0]))
- p_y = int((self.height - top_img.height) / 2) + value[1]
- # 基于顶部,左右居中
- if base == "nc" or base == "cn":
- # 顶部对齐
- deviation_x, deviation_y = int((self.width - top_img.width) / 2), int(
- (self.height - top_img.height) / 2)
- p_x = deviation_x + value[0] + left
- p_y = value[1]
- # 基于右上角
- if base == "en" or base == "ne":
- p_x = int(self.width - (top_img.width + value[0])) + left
- p_y = value[1]
- # 基于左上角
- if base == "nw" or base == "wn":
- deviation_x, deviation_y = 0, 0
- p_x, p_y = value
- # 基于底部,左右居中
- if base == "cs" or base == "sc":
- deviation_x, deviation_y = int((self.width - top_img.width) / 2), int(
- (self.height - top_img.height) / 2)
- p_y = self.height - (top_img.height + value[1] + down)
- p_x = deviation_x + value[0] + left
- # 上下左右居中
- if base == "center" or base == "cc":
- deviation_x, deviation_y = int((self.width - top_img.width) / 2), int(
- (self.height - top_img.height) / 2)
- p_x = deviation_x + value[0] + left
- p_y = deviation_y + value[1] + top
- # 基于左下角
- if base == "sw" or base == "ws":
- # deviation_x, deviation_y = 0, int((img.height - img_1.height))
- p_x = value[0] + left
- p_y = self.height - (top_img.height + value[1] + down)
- # 基于左边,上下居中
- if base == "wc" or base == "cw":
- p_x = value[0] + left
- p_y = int((self.height - top_img.height) / 2) + value[1] + top
- # 基于右下角
- if base == "es" or base == "se":
- p_x = int(self.width - (top_img.width + value[0])) + left
- p_y = self.height - (top_img.height + value[1] + down) + top
- img = PictureProcessing(im=self.im).im
- # top_img.get_im().show()
- top_img_im = top_img.im
- try:
- img.paste(top_img_im, box=(p_x, p_y), mask=top_img_im.convert("RGBA"))
- except:
- img.paste(top_img_im, box=(p_x, p_y), mask=top_img_im)
- return PictureProcessing(im=img)
- def paste_img_invert(self, mode="pixel", top_img="", base="nw", value=(0, 0), percentage=(0, 0),
- margins=(0, 0, 0, 0)):
- top_img: PictureProcessing
- bg_img = top_img.im
- top_img = PictureProcessing(im=self.im)
- self.im = bg_img
- return self.paste_img(mode=mode, top_img=top_img, base=base, value=value, percentage=percentage,
- margins=margins)
- # 水平分布处理,一行N个
- def horizontal_distribution(self, pp_list, bg_width=1200, margins=(0, 0, 0, 0), line_spacing=0, number_per_row=3, ):
- """
- pp_list
- line_spacing:行间距
- number_per_row:每行个数
- margins: (0, 0, 0, 0), # 上下左右边距
- """
- bg_width = int(bg_width)
- total_row = len(pp_list) // number_per_row
- if len(pp_list) % number_per_row > 0:
- total_row = total_row + 1
- # print("total_row", total_row)
- every_height = pp_list[0].height
- bg_pp = PictureProcessing("RGBA", (
- bg_width, margins[0] + every_height * total_row + line_spacing * (total_row - 1) + margins[1]),
- (255, 255, 255, 0))
- row_num = 0
- y = margins[0]
- for i in range(0, len(pp_list), number_per_row): # 切片获取每N个元素
- row_list = pp_list[i:i + number_per_row] # 输出每行的元素
- if row_list:
- row_num += 1
- # 计算每个元素间距
- t_w = bg_pp.width - margins[2] - margins[3]
- al_w = sum([x.width for x in row_list])
- space = int((t_w - al_w) / (len(row_list) + 1))
- x = margins[2]
- # 粘贴每个元素
- for pp in row_list:
- x = x + space
- bg_pp = bg_pp.paste_img(mode="pixel", top_img=pp, base="", value=(x, y))
- x = x + pp.width
- if row_num != total_row:
- y += (every_height + line_spacing)
- return bg_pp
- def get_text_image_advanced(self, value=(0, 0), font="", text="", anchor=None, align="left", spacing=0,
- fill=(0, 0, 0),
- return_mode="image", margins=(0, 0, 0, 0),max_len_one_line=0):
- """
- {
- "command": "add_text",
- "pos": {"plugins_mode": "relative", # pixel
- "base": "center", # nw,nc,ne,ec ... 各个方向参考点
- "value": (100, 100),
- "percentage": 0, },
- "font": "",
- "text": "",
- "anchor": "", # mm 为居中 ma 为左右居中,上面居顶 rs 右边;从哪边开始输入
- "align": "对齐方式",left center right
- "direction": "文本的方向",
- "max_len_one_line": "单行长度",
- "spacing": 10,
- "fill": "文字颜色",
- }
- margins 边距像素,上下左右;只有返回min_image_high 有效
- """
- # =====
- """
- return_mode:image返回图片,min_image_high返回最小高度尺寸但宽度不变,min_image返回最小高度与宽度
- """
- if not text:
- return self
- # pp = PictureProcessing("RGBA", self.size, (0, 0, 0, 0))
- pp = PictureProcessing("RGBA", self.size, (255, 255, 255, 0))
- draw_1 = ImageDraw.Draw(pp.im)
- # 定义字体,你需要有一个.ttf字体文件
- spacing = 4 if not spacing else spacing
- align = "left" if not align else align # left, center 或 right
- if max_len_one_line > 0:
- text = text[:max_len_one_line]
- _, _, text_width, text_height = draw_1.textbbox((0, 0), text, font=font)
- value = (int(value[0]), int(value[1]))
- draw_1.multiline_text((value[0], value[1] + margins[0]), text,
- fill=fill,
- font=font,
- anchor=anchor,
- spacing=spacing,
- align=align,
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- stroke_fill=None,
- embedded_color=False)
- if return_mode == "image":
- pp = pp.paste_img_invert(mode="pixel", top_img=self, base="nw", value=(0, 0))
- pass
- if return_mode == "min_image_high":
- # pp.show()
- bbox = pp.getbbox()
- bbox = (0, 0, pp.width, bbox[3] + margins[1])
- pp = pp.paste_img_invert(mode="pixel", top_img=self, base="nw", value=(0, 0))
- pp = pp.crop(bbox)
- if return_mode == "min_image":
- bbox = pp.getbbox()
- pp = pp.crop(bbox)
- return pp
- def add_text(self, mode="", base="", value="", percentage="", font="", text="", anchor="", align="", direction="",
- max_len_one_line="", spacing="", fill=""):
- """
- {
- "command": "add_text",
- "pos": {"plugins_mode": "relative", # pixel
- "base": "center", # nw,nc,ne,ec ... 各个方向参考点
- "value": (100, 100),
- "percentage": 0, },
- "font": "",
- "text": "",
- "anchor": "", # mm 为居中 ma 为左右居中,上面居顶 rs 右边;从哪边开始输入
- "align": "对齐方式",left center right
- "direction": "文本的方向",
- "max_len_one_line": "单行长度",
- "spacing": 10,
- "fill": "文字颜色",
- }
- """
- pp = PictureProcessing(im=self.im)
- draw_1 = ImageDraw.Draw(pp.im)
- # 定义字体,你需要有一个.ttf字体文件
- spacing = 4 if not spacing else spacing
- anchor = None if not anchor else anchor
- align = "left" if not align else align # left, center 或 right
- _, _, text_width, text_height = draw_1.textbbox((0, 0), text, font=font)
- xy = (0, 0)
- if mode == "pixel":
- xy = value
- draw_1.multiline_text(xy, text,
- fill=fill,
- font=font,
- anchor=anchor,
- spacing=spacing,
- align=align,
- direction=None,
- features=None,
- language=None,
- stroke_width=0,
- stroke_fill=None,
- embedded_color=False)
- return pp
- def crop_img(self, mode="pixel", base="nw", value=(100, 100, 10, 10), color_fill=(255, 255, 255),
- margins=(0, 0, 0, 0)):
- """
- {
- "command": "crop_img",
- "img": {"im": "im"},
- "pos": {"plugins_mode": "relative", # pixel
- "base": "center", # nw,nc,ne,ec ... 各个方向参考点
- "value": (100, 100, 10, 10),# 顶点,以及图片大小
- },
- "color_fill": (255, 255, 255)
- }
- """
- pp = PictureProcessing(im=self.im)
- base = "nw" if not base else base
- if margins:
- top, down, left, right = margins
- else:
- top, down, left, right = 0, 0, 0, 0
- out_img_size = (value[2], value[3])
- # 默认填充色
- if not color_fill:
- color_fill = (0, 0, 0)
- if mode == "pixel":
- # 左上脚
- if base == "nw" or "wn":
- box = value
- # 左下角
- if base == "sw" or base == "ws":
- # deviation_x, deviation_y = 0, int((img.height - img_1.height))
- box = (value[0], pp.height - (value[1] + value[3]), value[2], value[3])
- # print(box)
- # 右下角
- if base == "se" or base == "es":
- box = (pp.width - (value[0] + value[2]), pp.height - (value[1] + value[3]), value[2], value[3])
- # print(box)
- # 居中
- if base == "cc":
- # print("11-value", value)
- x = int((pp.width + value[0] - value[2]) / 2)
- y = int((pp.height + value[1] - value[3]) / 2)
- box = (x,
- y,
- value[2],
- value[3])
- # print("11-box", box)
- box = [box[0], box[1], box[0] + box[2], box[1] + box[3]]
- # print("12-box", box)
- # print("ww-hhh", pp.width, pp.height)
- out_img = pp.im.crop(box=box)
- # print("out_img", out_img.width, out_img.height)
- # print("-----")
- if box[0] < 0:
- out_img.paste(Image.new("RGB", (-1 * box[0], out_img.height), color_fill), (0, 0))
- if box[1] < 0:
- out_img.paste(Image.new("RGB", (out_img.width, -1 * box[1]), color_fill), (0, 0))
- if box[2] > pp.width:
- # print(box[2] - img.width, img.height)
- i = Image.new("RGB", (box[2] - pp.width, out_img.height), color_fill)
- out_img.paste(i, (pp.width - box[0], 0))
- if box[3] > pp.height:
- out_img.paste(Image.new("RGB", (out_img.width, box[3] - pp.height), color_fill),
- (0, pp.height - box[1]))
- return PictureProcessing(im=out_img)
- def crop(self, bbox=(0, 0, 0, 0), mode=None):
- bbox = (int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3]))
- if mode == "min":
- bbox = self.getbbox()
- return PictureProcessing(im=self.im.crop(bbox))
- def rotate(self, doge):
- return PictureProcessing(im=self.im.rotate(doge))
- def rotate_advance(self, doge, is_crop=True):
- max_px = max(self.width, self.height)
- max_px = max_px * 2
- bg = PictureProcessing("RGBA", (max_px, max_px), (255, 255, 255, 0))
- bg = bg.paste_img(top_img=self, base="cc")
- bg = bg.rotate(doge)
- if is_crop:
- bg = bg.crop(mode="min")
- return bg
- def radius(self, mode="pixel", circular_pos=(1, 1, 1, 1), value=30, percentage=""):
- """
- {"command": "radius", # radius
- "plugins_mode": "relative", # pixel 相对(短边),或绝对像素
- "circular_pos": (0, 1, 0, 1), # 从左上角顺时针,记录圆角数量
- "value": 649, # 固定值,如果为百分比,则为0
- "percentage": 0, } # 百分比
- """
- # 单图圆角处理
- pp = PictureProcessing(im=self.im)
- radii = value
- if radii > pp.width / 2:
- radii = int(pp.width / 2)
- if radii > pp.height / 2:
- radii = int(pp.height / 2)
- # 画圆(用于分离4个角)
- circle = Image.new('L', (radii * 2, radii * 2), 0) # 创建一个黑色背景的画布
- draw = ImageDraw.Draw(circle)
- draw.ellipse((0, 0, radii * 2, radii * 2), fill=255) # 画白色圆形
- # 原图
- img = pp.im.convert("RGBA")
- w, h = img.size
- # 画4个角(将整圆分离为4个部分)
- alpha = Image.new('L', img.size, 255)
- _pos = circular_pos
- if not _pos:
- _pos = (1, 1, 1, 1)
- for index, i in enumerate(_pos):
- if index == 0 and i == 1:
- alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0)) # 左上角
- if index == 1 and i == 1:
- alpha.paste(circle.crop((radii, 0, radii * 2, radii)), (w - radii, 0)) # 右上角
- if index == 2 and i == 1:
- alpha.paste(circle.crop((radii, radii, radii * 2, radii * 2)), (w - radii, h - radii)) # 右下角
- if index == 3 and i == 1:
- alpha.paste(circle.crop((0, radii, radii, radii * 2)), (0, h - radii)) # 左下角
- # alpha.show()
- img.putalpha(alpha) # 白色区域透明可见,黑色区域不可见
- return PictureProcessing(im=img)
- # 图片翻转
- def transpose(self, mode="left_right"):
- img = self.get_im()
- img = img.transpose(Image.FLIP_LEFT_RIGHT)
- return PictureProcessing(im=img)
- def convert(self, mode):
- return PictureProcessing(im=self.im.convert(mode))
- def save_as_rgb(self, path):
- self.im = self.im.convert("RGB")
- self.im.save(path, format="JPEG")
- def save_as_png(self, path):
- self.im = self.im.convert("RGBA")
- self.im.save(path)
|