pic_deal.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. import os.path
  2. from PIL import Image, ImageDraw, ImageEnhance, ImageFile
  3. # import cv2
  4. import numpy as np
  5. from blend_modes import multiply
  6. ImageFile.LOAD_TRUNCATED_IMAGES = True
  7. class PictureProcessing(object):
  8. def __init__(self, *args, **kwargs):
  9. self.im: Image
  10. if args:
  11. p = args[0]
  12. if isinstance(p, str):
  13. if p == "RGB" or p == "RGBA":
  14. size = int(args[1][0]), int(args[1][1])
  15. self.im = Image.new(args[0], size, args[2])
  16. else:
  17. # 设计优先取值处理
  18. _path = args[0]
  19. file_path, file = os.path.split(_path)
  20. custom_path = "{}/custom/{}".format(file_path, file)
  21. if os.path.exists(custom_path):
  22. _path = custom_path
  23. self.im = Image.open(_path)
  24. elif "im" in kwargs:
  25. self.im = kwargs["im"]
  26. else:
  27. self.im = Image.new(mode="RGB", size=(1600, 500), color=(255, 255, 255))
  28. def getbbox(self):
  29. return self.im.getbbox()
  30. def expand_bbox(self, bbox, value=100):
  31. x1, y1, x2, y2 = bbox[0] - value, bbox[1] - value, bbox[2] + value, bbox[3] + value
  32. if x1 < 0:
  33. x1 = 0
  34. if y1 < 0:
  35. y1 = 0
  36. bbox = (x1, y1, x2, y2)
  37. return [int(x) for x in bbox]
  38. def show(self):
  39. self.im.show()
  40. def get_size(self):
  41. return self.im.size
  42. def get_im(self):
  43. self.im: Image
  44. return self.im
  45. def to_color_2(self, target, blend): # 正片叠底
  46. return np.array(np.multiply(target / 256, blend / 256) * 256, dtype=np.uint8)
  47. def get_overlay_pic(self, top_img, color=None):
  48. # 正片叠底
  49. top_img: PictureProcessing
  50. image_white = Image.new("RGB", (self.get_size()), color)
  51. backdrop_prepped = np.asfarray(image_white.convert('RGBA'))
  52. source_prepped = np.asfarray(self.im.convert('RGBA'))
  53. blended_np = multiply(backdrop_prepped, source_prepped, 1)
  54. img = Image.fromarray(np.uint8(blended_np)).convert('RGB')
  55. img.paste(top_img.im, (0, 0), top_img.im)
  56. return PictureProcessing(im=img)
  57. def to_overlay_pic_advance(self, mode="pixel", top_img="", base="nw", value=(0, 0), percentage=(0, 0),
  58. margins=(0, 0, 0, 0), opacity=100, top_png_img=None):
  59. # opacity 透明度
  60. # 正片叠底
  61. _p = PictureProcessing("RGBA", (self.width, self.height), (255, 255, 255, 255))
  62. top_img = _p.paste_img(mode=mode,
  63. top_img=top_img,
  64. base=base,
  65. value=value,
  66. percentage=percentage,
  67. margins=margins)
  68. backdrop_prepped = np.asfarray(self.im.convert('RGBA'))
  69. source_prepped = np.asfarray(top_img.im.convert('RGBA'))
  70. blended_np = multiply(backdrop_prepped, source_prepped, opacity / 100)
  71. im = Image.fromarray(np.uint8(blended_np)).convert('RGB')
  72. pp = PictureProcessing(im=im)
  73. if top_png_img:
  74. pp = pp.paste_img(mode=mode,
  75. top_img=top_png_img,
  76. base=base,
  77. value=value,
  78. percentage=percentage,
  79. margins=margins)
  80. return pp
  81. def __to_resize(self, width=None, height=None):
  82. _im_x, _im_y = self.get_size()
  83. if width and height:
  84. if _im_x >= _im_y:
  85. high = None
  86. else:
  87. width = None
  88. if width:
  89. re_x = int(width)
  90. re_y = int(_im_y * re_x / _im_x)
  91. else:
  92. re_y = int(height)
  93. re_x = int(_im_x * re_y / _im_y)
  94. img = self.get_im().resize((re_x, re_y))
  95. return img
  96. # 锐化图片
  97. def sharpen_image(self, factor=1.0):
  98. # 创建一个ImageEnhance对象
  99. enhancer = ImageEnhance.Sharpness(self.im)
  100. # 应用增强,值为0.0给出模糊图像,1.0给出原始图像,大于1.0给出锐化效果
  101. # 调整这个值来增加或减少锐化的程度
  102. sharp_img = enhancer.enhance(factor)
  103. return PictureProcessing(im=sharp_img)
  104. @property
  105. def width(self):
  106. return self.im.width
  107. @property
  108. def height(self):
  109. return self.im.height
  110. @property
  111. def size(self):
  112. return self.im.size
  113. def resize(self, mode="pixel", base="width", value=0, base_im=None, percentage=0, base_by_box=None):
  114. """
  115. plugins_mode # relative相对(宽度、高度、其他参考图),或 pixel绝对像素
  116. base # pixel,width,height,by_im 基于长边、基于短边 (基于短边时,则缩放到指定尺寸)by_im确保能塞进参考图内
  117. value # 固定值,如果为百分比,则为0
  118. percentage #百分比
  119. base_im # 参考基于其他图 PictureProcessing 格式
  120. """
  121. # 在箱子内
  122. if base_by_box:
  123. mode = "relative"
  124. base = "by_im"
  125. base_by_box = (int(base_by_box[0]), int(base_by_box[1]))
  126. base_im = Image.new("RGB", base_by_box, (255, 255, 255))
  127. # 绝对值
  128. if mode == "pixel":
  129. if base == "width":
  130. img = self.__to_resize(width=value)
  131. if base == "high":
  132. img = self.__to_resize(height=value)
  133. # 相对值
  134. if mode == "relative":
  135. if base == "width":
  136. img = self.__to_resize(
  137. width=self.width * percentage if not base_im else int(base_im.width * percentage))
  138. if base == "height":
  139. img = self.__to_resize(
  140. width=self.height * percentage if not base_im else int(base_im.height * percentage))
  141. # by_im确保能塞进参考图内
  142. if base == "by_im":
  143. percentage = 1 if not percentage else percentage
  144. box_width, box_height = int(base_im.width * percentage), int(base_im.height * percentage)
  145. width, height = self.width, self.height
  146. if box_width / box_height < width / height:
  147. scale = box_width / width
  148. else:
  149. scale = box_height / height
  150. img = self.get_im().resize((int(width * scale), int(height * scale)))
  151. return PictureProcessing(im=img)
  152. def paste_img(self, mode="pixel", top_img="", base="nw", value=(0, 0), percentage=(0, 0), margins=(0, 0, 0, 0)):
  153. top_img: PictureProcessing
  154. """
  155. {
  156. "command": "paste_img",
  157. "im": 需要粘贴的图片
  158. "pos": {"plugins_mode": "relative", # pixel
  159. "base": "center", # nw,nc,ne,ec ... 各个方向参考点
  160. "value": (100, 100),
  161. "percentage": (0.5, 0.5),
  162. },
  163. "margins": (0, 0, 0, 0), # 上下左右边距
  164. }
  165. """
  166. value = (int(value[0]), int(value[1]))
  167. # 处理默认值
  168. base = "nw" if not base else base
  169. percentage = (0, 0) if not percentage else percentage
  170. if margins:
  171. top, down, left, right = margins
  172. else:
  173. top, down, left, right = 0, 0, 0, 0
  174. if percentage != (0, 0): # percentage 不按占比模式
  175. if base in ("nw", "wn", "wc", "cw", "nc", "cn", "center"):
  176. value = (int(self.width), int(self.height))
  177. if base in ("sw", "ws", "sc", "cs", "center"):
  178. value = (int(self.width), -1 * int(self.height))
  179. if base in ("ec", "ce"):
  180. value = (int(self.width), int(self.height))
  181. if mode == "pixel":
  182. # 基于右边,上下居中
  183. if base == "ec" or base == "ce":
  184. p_x = int(self.width - (top_img.width + value[0]))
  185. p_y = int((self.height - top_img.height) / 2) + value[1]
  186. # 基于顶部,左右居中
  187. if base == "nc" or base == "cn":
  188. # 顶部对齐
  189. deviation_x, deviation_y = int((self.width - top_img.width) / 2), int(
  190. (self.height - top_img.height) / 2)
  191. p_x = deviation_x + value[0] + left
  192. p_y = value[1]
  193. # 基于右上角
  194. if base == "en" or base == "ne":
  195. p_x = int(self.width - (top_img.width + value[0])) + left
  196. p_y = value[1]
  197. # 基于左上角
  198. if base == "nw" or base == "wn":
  199. deviation_x, deviation_y = 0, 0
  200. p_x, p_y = value
  201. # 基于底部,左右居中
  202. if base == "cs" or base == "sc":
  203. deviation_x, deviation_y = int((self.width - top_img.width) / 2), int(
  204. (self.height - top_img.height) / 2)
  205. p_y = self.height - (top_img.height + value[1] + down)
  206. p_x = deviation_x + value[0] + left
  207. # 上下左右居中
  208. if base == "center" or base == "cc":
  209. deviation_x, deviation_y = int((self.width - top_img.width) / 2), int(
  210. (self.height - top_img.height) / 2)
  211. p_x = deviation_x + value[0] + left
  212. p_y = deviation_y + value[1] + top
  213. # 基于左下角
  214. if base == "sw" or base == "ws":
  215. # deviation_x, deviation_y = 0, int((img.height - img_1.height))
  216. p_x = value[0] + left
  217. p_y = self.height - (top_img.height + value[1] + down)
  218. # 基于左边,上下居中
  219. if base == "wc" or base == "cw":
  220. p_x = value[0] + left
  221. p_y = int((self.height - top_img.height) / 2) + value[1] + top
  222. # 基于右下角
  223. if base == "es" or base == "se":
  224. p_x = int(self.width - (top_img.width + value[0])) + left
  225. p_y = self.height - (top_img.height + value[1] + down) + top
  226. img = PictureProcessing(im=self.im).im
  227. # top_img.get_im().show()
  228. top_img_im = top_img.im
  229. try:
  230. img.paste(top_img_im, box=(p_x, p_y), mask=top_img_im.convert("RGBA"))
  231. except:
  232. img.paste(top_img_im, box=(p_x, p_y), mask=top_img_im)
  233. return PictureProcessing(im=img)
  234. def paste_img_invert(self, mode="pixel", top_img="", base="nw", value=(0, 0), percentage=(0, 0),
  235. margins=(0, 0, 0, 0)):
  236. top_img: PictureProcessing
  237. bg_img = top_img.im
  238. top_img = PictureProcessing(im=self.im)
  239. self.im = bg_img
  240. return self.paste_img(mode=mode, top_img=top_img, base=base, value=value, percentage=percentage,
  241. margins=margins)
  242. # 水平分布处理,一行N个
  243. def horizontal_distribution(self, pp_list, bg_width=1200, margins=(0, 0, 0, 0), line_spacing=0, number_per_row=3, ):
  244. """
  245. pp_list
  246. line_spacing:行间距
  247. number_per_row:每行个数
  248. margins: (0, 0, 0, 0), # 上下左右边距
  249. """
  250. bg_width = int(bg_width)
  251. total_row = len(pp_list) // number_per_row
  252. if len(pp_list) % number_per_row > 0:
  253. total_row = total_row + 1
  254. # print("total_row", total_row)
  255. every_height = pp_list[0].height
  256. bg_pp = PictureProcessing("RGBA", (
  257. bg_width, margins[0] + every_height * total_row + line_spacing * (total_row - 1) + margins[1]),
  258. (255, 255, 255, 0))
  259. row_num = 0
  260. y = margins[0]
  261. for i in range(0, len(pp_list), number_per_row): # 切片获取每N个元素
  262. row_list = pp_list[i:i + number_per_row] # 输出每行的元素
  263. if row_list:
  264. row_num += 1
  265. # 计算每个元素间距
  266. t_w = bg_pp.width - margins[2] - margins[3]
  267. al_w = sum([x.width for x in row_list])
  268. space = int((t_w - al_w) / (len(row_list) + 1))
  269. x = margins[2]
  270. # 粘贴每个元素
  271. for pp in row_list:
  272. x = x + space
  273. bg_pp = bg_pp.paste_img(mode="pixel", top_img=pp, base="", value=(x, y))
  274. x = x + pp.width
  275. if row_num != total_row:
  276. y += (every_height + line_spacing)
  277. return bg_pp
  278. def get_text_image_advanced(self, value=(0, 0), font="", text="", anchor=None, align="left", spacing=0,
  279. fill=(0, 0, 0),
  280. return_mode="image", margins=(0, 0, 0, 0),max_len_one_line=0):
  281. """
  282. {
  283. "command": "add_text",
  284. "pos": {"plugins_mode": "relative", # pixel
  285. "base": "center", # nw,nc,ne,ec ... 各个方向参考点
  286. "value": (100, 100),
  287. "percentage": 0, },
  288. "font": "",
  289. "text": "",
  290. "anchor": "", # mm 为居中 ma 为左右居中,上面居顶 rs 右边;从哪边开始输入
  291. "align": "对齐方式",left center right
  292. "direction": "文本的方向",
  293. "max_len_one_line": "单行长度",
  294. "spacing": 10,
  295. "fill": "文字颜色",
  296. }
  297. margins 边距像素,上下左右;只有返回min_image_high 有效
  298. """
  299. # =====
  300. """
  301. return_mode:image返回图片,min_image_high返回最小高度尺寸但宽度不变,min_image返回最小高度与宽度
  302. """
  303. if not text:
  304. return self
  305. # pp = PictureProcessing("RGBA", self.size, (0, 0, 0, 0))
  306. pp = PictureProcessing("RGBA", self.size, (255, 255, 255, 0))
  307. draw_1 = ImageDraw.Draw(pp.im)
  308. # 定义字体,你需要有一个.ttf字体文件
  309. spacing = 4 if not spacing else spacing
  310. align = "left" if not align else align # left, center 或 right
  311. if max_len_one_line > 0:
  312. text = text[:max_len_one_line]
  313. _, _, text_width, text_height = draw_1.textbbox((0, 0), text, font=font)
  314. value = (int(value[0]), int(value[1]))
  315. draw_1.multiline_text((value[0], value[1] + margins[0]), text,
  316. fill=fill,
  317. font=font,
  318. anchor=anchor,
  319. spacing=spacing,
  320. align=align,
  321. direction=None,
  322. features=None,
  323. language=None,
  324. stroke_width=0,
  325. stroke_fill=None,
  326. embedded_color=False)
  327. if return_mode == "image":
  328. pp = pp.paste_img_invert(mode="pixel", top_img=self, base="nw", value=(0, 0))
  329. pass
  330. if return_mode == "min_image_high":
  331. # pp.show()
  332. bbox = pp.getbbox()
  333. bbox = (0, 0, pp.width, bbox[3] + margins[1])
  334. pp = pp.paste_img_invert(mode="pixel", top_img=self, base="nw", value=(0, 0))
  335. pp = pp.crop(bbox)
  336. if return_mode == "min_image":
  337. bbox = pp.getbbox()
  338. pp = pp.crop(bbox)
  339. return pp
  340. def add_text(self, mode="", base="", value="", percentage="", font="", text="", anchor="", align="", direction="",
  341. max_len_one_line="", spacing="", fill=""):
  342. """
  343. {
  344. "command": "add_text",
  345. "pos": {"plugins_mode": "relative", # pixel
  346. "base": "center", # nw,nc,ne,ec ... 各个方向参考点
  347. "value": (100, 100),
  348. "percentage": 0, },
  349. "font": "",
  350. "text": "",
  351. "anchor": "", # mm 为居中 ma 为左右居中,上面居顶 rs 右边;从哪边开始输入
  352. "align": "对齐方式",left center right
  353. "direction": "文本的方向",
  354. "max_len_one_line": "单行长度",
  355. "spacing": 10,
  356. "fill": "文字颜色",
  357. }
  358. """
  359. pp = PictureProcessing(im=self.im)
  360. draw_1 = ImageDraw.Draw(pp.im)
  361. # 定义字体,你需要有一个.ttf字体文件
  362. spacing = 4 if not spacing else spacing
  363. anchor = None if not anchor else anchor
  364. align = "left" if not align else align # left, center 或 right
  365. _, _, text_width, text_height = draw_1.textbbox((0, 0), text, font=font)
  366. xy = (0, 0)
  367. if mode == "pixel":
  368. xy = value
  369. draw_1.multiline_text(xy, text,
  370. fill=fill,
  371. font=font,
  372. anchor=anchor,
  373. spacing=spacing,
  374. align=align,
  375. direction=None,
  376. features=None,
  377. language=None,
  378. stroke_width=0,
  379. stroke_fill=None,
  380. embedded_color=False)
  381. return pp
  382. def crop_img(self, mode="pixel", base="nw", value=(100, 100, 10, 10), color_fill=(255, 255, 255),
  383. margins=(0, 0, 0, 0)):
  384. """
  385. {
  386. "command": "crop_img",
  387. "img": {"im": "im"},
  388. "pos": {"plugins_mode": "relative", # pixel
  389. "base": "center", # nw,nc,ne,ec ... 各个方向参考点
  390. "value": (100, 100, 10, 10),# 顶点,以及图片大小
  391. },
  392. "color_fill": (255, 255, 255)
  393. }
  394. """
  395. pp = PictureProcessing(im=self.im)
  396. base = "nw" if not base else base
  397. if margins:
  398. top, down, left, right = margins
  399. else:
  400. top, down, left, right = 0, 0, 0, 0
  401. out_img_size = (value[2], value[3])
  402. # 默认填充色
  403. if not color_fill:
  404. color_fill = (0, 0, 0)
  405. if mode == "pixel":
  406. # 左上脚
  407. if base == "nw" or "wn":
  408. box = value
  409. # 左下角
  410. if base == "sw" or base == "ws":
  411. # deviation_x, deviation_y = 0, int((img.height - img_1.height))
  412. box = (value[0], pp.height - (value[1] + value[3]), value[2], value[3])
  413. # print(box)
  414. # 右下角
  415. if base == "se" or base == "es":
  416. box = (pp.width - (value[0] + value[2]), pp.height - (value[1] + value[3]), value[2], value[3])
  417. # print(box)
  418. # 居中
  419. if base == "cc":
  420. # print("11-value", value)
  421. x = int((pp.width + value[0] - value[2]) / 2)
  422. y = int((pp.height + value[1] - value[3]) / 2)
  423. box = (x,
  424. y,
  425. value[2],
  426. value[3])
  427. # print("11-box", box)
  428. box = [box[0], box[1], box[0] + box[2], box[1] + box[3]]
  429. # print("12-box", box)
  430. # print("ww-hhh", pp.width, pp.height)
  431. out_img = pp.im.crop(box=box)
  432. # print("out_img", out_img.width, out_img.height)
  433. # print("-----")
  434. if box[0] < 0:
  435. out_img.paste(Image.new("RGB", (-1 * box[0], out_img.height), color_fill), (0, 0))
  436. if box[1] < 0:
  437. out_img.paste(Image.new("RGB", (out_img.width, -1 * box[1]), color_fill), (0, 0))
  438. if box[2] > pp.width:
  439. # print(box[2] - img.width, img.height)
  440. i = Image.new("RGB", (box[2] - pp.width, out_img.height), color_fill)
  441. out_img.paste(i, (pp.width - box[0], 0))
  442. if box[3] > pp.height:
  443. out_img.paste(Image.new("RGB", (out_img.width, box[3] - pp.height), color_fill),
  444. (0, pp.height - box[1]))
  445. return PictureProcessing(im=out_img)
  446. def crop(self, bbox=(0, 0, 0, 0), mode=None):
  447. bbox = (int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3]))
  448. if mode == "min":
  449. bbox = self.getbbox()
  450. return PictureProcessing(im=self.im.crop(bbox))
  451. def rotate(self, doge):
  452. return PictureProcessing(im=self.im.rotate(doge))
  453. def rotate_advance(self, doge, is_crop=True):
  454. max_px = max(self.width, self.height)
  455. max_px = max_px * 2
  456. bg = PictureProcessing("RGBA", (max_px, max_px), (255, 255, 255, 0))
  457. bg = bg.paste_img(top_img=self, base="cc")
  458. bg = bg.rotate(doge)
  459. if is_crop:
  460. bg = bg.crop(mode="min")
  461. return bg
  462. def radius(self, mode="pixel", circular_pos=(1, 1, 1, 1), value=30, percentage=""):
  463. """
  464. {"command": "radius", # radius
  465. "plugins_mode": "relative", # pixel 相对(短边),或绝对像素
  466. "circular_pos": (0, 1, 0, 1), # 从左上角顺时针,记录圆角数量
  467. "value": 649, # 固定值,如果为百分比,则为0
  468. "percentage": 0, } # 百分比
  469. """
  470. # 单图圆角处理
  471. pp = PictureProcessing(im=self.im)
  472. radii = value
  473. if radii > pp.width / 2:
  474. radii = int(pp.width / 2)
  475. if radii > pp.height / 2:
  476. radii = int(pp.height / 2)
  477. # 画圆(用于分离4个角)
  478. circle = Image.new('L', (radii * 2, radii * 2), 0) # 创建一个黑色背景的画布
  479. draw = ImageDraw.Draw(circle)
  480. draw.ellipse((0, 0, radii * 2, radii * 2), fill=255) # 画白色圆形
  481. # 原图
  482. img = pp.im.convert("RGBA")
  483. w, h = img.size
  484. # 画4个角(将整圆分离为4个部分)
  485. alpha = Image.new('L', img.size, 255)
  486. _pos = circular_pos
  487. if not _pos:
  488. _pos = (1, 1, 1, 1)
  489. for index, i in enumerate(_pos):
  490. if index == 0 and i == 1:
  491. alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0)) # 左上角
  492. if index == 1 and i == 1:
  493. alpha.paste(circle.crop((radii, 0, radii * 2, radii)), (w - radii, 0)) # 右上角
  494. if index == 2 and i == 1:
  495. alpha.paste(circle.crop((radii, radii, radii * 2, radii * 2)), (w - radii, h - radii)) # 右下角
  496. if index == 3 and i == 1:
  497. alpha.paste(circle.crop((0, radii, radii, radii * 2)), (0, h - radii)) # 左下角
  498. # alpha.show()
  499. img.putalpha(alpha) # 白色区域透明可见,黑色区域不可见
  500. return PictureProcessing(im=img)
  501. # 图片翻转
  502. def transpose(self, mode="left_right"):
  503. img = self.get_im()
  504. img = img.transpose(Image.FLIP_LEFT_RIGHT)
  505. return PictureProcessing(im=img)
  506. def convert(self, mode):
  507. return PictureProcessing(im=self.im.convert(mode))
  508. def save_as_rgb(self, path):
  509. self.im = self.im.convert("RGB")
  510. self.im.save(path, format="JPEG")
  511. def save_as_png(self, path):
  512. self.im = self.im.convert("RGBA")
  513. self.im.save(path)