pic_deal.py 22 KB

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