pic_deal.py 23 KB

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