py-img-processor 1.2.0__tar.gz → 1.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of py-img-processor might be problematic. Click here for more details.

Files changed (31) hide show
  1. {py_img_processor-1.2.0/py_img_processor.egg-info → py_img_processor-1.2.3}/PKG-INFO +2 -2
  2. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/__init__.py +1 -1
  3. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/__init__.py +11 -11
  4. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/alpha.py +2 -2
  5. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/base.py +58 -36
  6. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/blur.py +2 -2
  7. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/circle.py +2 -2
  8. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/crop.py +10 -10
  9. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/gray.py +1 -1
  10. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/merge.py +36 -23
  11. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/resize.py +19 -15
  12. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/rotate.py +2 -2
  13. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/parsers/watermark.py +41 -23
  14. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/processor.py +21 -22
  15. {py_img_processor-1.2.0 → py_img_processor-1.2.3/py_img_processor.egg-info}/PKG-INFO +2 -2
  16. py_img_processor-1.2.3/py_img_processor.egg-info/requires.txt +2 -0
  17. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/setup.py +1 -1
  18. py_img_processor-1.2.0/py_img_processor.egg-info/requires.txt +0 -2
  19. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/LICENSE +0 -0
  20. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/MANIFEST.in +0 -0
  21. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/README.md +0 -0
  22. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/enums.py +0 -0
  23. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/exceptions.py +0 -0
  24. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/main.py +0 -0
  25. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/imgprocessor/utils.py +0 -0
  26. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/py_img_processor.egg-info/SOURCES.txt +0 -0
  27. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/py_img_processor.egg-info/dependency_links.txt +0 -0
  28. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/py_img_processor.egg-info/entry_points.txt +0 -0
  29. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/py_img_processor.egg-info/not-zip-safe +0 -0
  30. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/py_img_processor.egg-info/top_level.txt +0 -0
  31. {py_img_processor-1.2.0 → py_img_processor-1.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-img-processor
3
- Version: 1.2.0
3
+ Version: 1.2.3
4
4
  Summary: Image editor using Python and Pillow.
5
5
  Home-page: https://github.com/SkylerHu/py-img-processor.git
6
6
  Author: SkylerHu
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: py-enum>=1.1.1
24
+ Requires-Dist: py-enum>=2.1.1
25
25
  Requires-Dist: Pillow>=8
26
26
 
27
27
  # py-img-processor
@@ -5,7 +5,7 @@ import importlib
5
5
 
6
6
 
7
7
  __all__ = ["settings", "VERSION"]
8
- __version__ = "1.2.0"
8
+ __version__ = "1.2.3"
9
9
 
10
10
 
11
11
  VERSION = __version__
@@ -17,16 +17,16 @@ from .watermark import WatermarkParser
17
17
  from .merge import MergeParser
18
18
 
19
19
 
20
- _ACTION_PARASER_MAP: dict[str, BaseParser] = {
21
- enums.OpAction.RESIZE: ResizeParser, # type: ignore
22
- enums.OpAction.CROP: CropParser, # type: ignore
23
- enums.OpAction.CIRCLE: CircleParser, # type: ignore
24
- enums.OpAction.BLUR: BlurParser, # type: ignore
25
- enums.OpAction.ROTATE: RotateParser, # type: ignore
26
- enums.OpAction.ALPHA: AlphaParser, # type: ignore
27
- enums.OpAction.GRAY: GrayParser, # type: ignore
28
- enums.OpAction.WATERMARK: WatermarkParser, # type: ignore
29
- enums.OpAction.MERGE: MergeParser, # type: ignore
20
+ _ACTION_PARASER_MAP: dict[str, typing.Any] = {
21
+ enums.OpAction.RESIZE.value: ResizeParser,
22
+ enums.OpAction.CROP.value: CropParser,
23
+ enums.OpAction.CIRCLE.value: CircleParser,
24
+ enums.OpAction.BLUR.value: BlurParser,
25
+ enums.OpAction.ROTATE.value: RotateParser,
26
+ enums.OpAction.ALPHA.value: AlphaParser,
27
+ enums.OpAction.GRAY.value: GrayParser,
28
+ enums.OpAction.WATERMARK.value: WatermarkParser,
29
+ enums.OpAction.MERGE.value: MergeParser,
30
30
  }
31
31
 
32
32
 
@@ -77,7 +77,7 @@ class ProcessParams(object):
77
77
  key, param_str = info
78
78
  if not key:
79
79
  raise ParamParseException(f"参数必须指定操作类型 [{item}]不符合参数要求")
80
- if key in [enums.OpAction.FORMAT, enums.OpAction.QUALITY, enums.OpAction.INTERLACE]:
80
+ if key in [enums.OpAction.FORMAT.value, enums.OpAction.QUALITY.value, enums.OpAction.INTERLACE.value]:
81
81
  save_args.append(f"{key}_{param_str}")
82
82
  else:
83
83
  action_cls = _ACTION_PARASER_MAP.get(key)
@@ -10,10 +10,10 @@ from .base import BaseParser, pre_processing
10
10
 
11
11
  class AlphaParser(BaseParser):
12
12
 
13
- KEY = enums.OpAction.ALPHA
13
+ KEY = enums.OpAction.ALPHA.value
14
14
  ARGS = {
15
15
  # 不透明度, 为100时,完全不透明,即原图; 为0时,完全透明
16
- "value": {"type": enums.ArgType.INTEGER, "default": 100, "min": 0, "max": 100},
16
+ "value": {"type": enums.ArgType.INTEGER.value, "default": 100, "min": 0, "max": 100},
17
17
  }
18
18
 
19
19
  def __init__(
@@ -7,9 +7,11 @@ import re
7
7
  import tempfile
8
8
  import urllib.parse
9
9
  from urllib.request import urlretrieve
10
+ from contextlib import contextmanager
10
11
 
11
12
  from PIL import Image, ImageOps
12
13
 
14
+ from py_enum import ChoiceEnum
13
15
  from imgprocessor import settings, enums, utils
14
16
  from imgprocessor.exceptions import ParamValidateException, ParamParseException, ProcessLimitException
15
17
 
@@ -67,21 +69,23 @@ class BaseParser(object):
67
69
  else:
68
70
  value = kwargs.get(key)
69
71
  try:
70
- if _type == enums.ArgType.INTEGER:
72
+ if _type == enums.ArgType.INTEGER.value:
71
73
  value = cls._validate_number(value, **config)
72
- elif _type == enums.ArgType.FLOAT:
74
+ elif _type == enums.ArgType.FLOAT.value:
73
75
  value = cls._validate_number(value, use_float=True, **config)
74
- elif _type == enums.ArgType.STRING:
76
+ elif _type == enums.ArgType.STRING.value:
75
77
  value = cls._validate_str(value, enable_base64=enable_base64, **config)
76
- elif _type == enums.ArgType.URI:
78
+ elif _type == enums.ArgType.URI.value:
77
79
  value = cls._validate_uri(value, enable_base64=enable_base64, **config)
78
- elif _type == enums.ArgType.ACTION:
80
+ elif _type == enums.ArgType.ACTION.value:
79
81
  if value and isinstance(value, str):
80
82
  value = cls._validate_str(value, enable_base64=enable_base64, **config)
81
83
 
82
84
  choices = config.get("choices")
85
+ if isinstance(choices, ChoiceEnum):
86
+ choices = choices.values
83
87
  if choices and value not in choices:
84
- raise ParamValidateException(f"{key}枚举值只能是其中之一 {choices.values}")
88
+ raise ParamValidateException(f"{key}枚举值只能是其中之一 {choices}")
85
89
  except ParamValidateException as e:
86
90
  raise ParamValidateException(f"参数 {key}={value} 不符合要求:{e}")
87
91
  data[key] = value
@@ -241,23 +245,23 @@ def compute_by_geography(
241
245
  src_w: int, src_h: int, x: int, y: int, w: int, h: int, g: typing.Optional[str], pf: str
242
246
  ) -> tuple[int, int]:
243
247
  """计算 大小(w,h)的图像相对于(src_w, src_h)图像的原点(x,y)位置"""
244
- if g == enums.Geography.NW:
248
+ if g == enums.Geography.NW.value:
245
249
  x, y = 0, 0
246
- elif g == enums.Geography.NORTH:
250
+ elif g == enums.Geography.NORTH.value:
247
251
  x, y = int(src_w / 2 - w / 2), 0
248
- elif g == enums.Geography.NE:
252
+ elif g == enums.Geography.NE.value:
249
253
  x, y = src_w - w, 0
250
- elif g == enums.Geography.WEST:
254
+ elif g == enums.Geography.WEST.value:
251
255
  x, y = 0, int(src_h / 2 - h / 2)
252
- elif g == enums.Geography.CENTER:
256
+ elif g == enums.Geography.CENTER.value:
253
257
  x, y = int(src_w / 2 - w / 2), int(src_h / 2 - h / 2)
254
- elif g == enums.Geography.EAST:
258
+ elif g == enums.Geography.EAST.value:
255
259
  x, y = src_w - w, int(src_h / 2 - h / 2)
256
- elif g == enums.Geography.SW:
260
+ elif g == enums.Geography.SW.value:
257
261
  x, y = 0, src_h - h
258
- elif g == enums.Geography.SOUTH:
262
+ elif g == enums.Geography.SOUTH.value:
259
263
  x, y = int(src_w / 2 - w / 2), src_h - h
260
- elif g == enums.Geography.SE:
264
+ elif g == enums.Geography.SE.value:
261
265
  x, y = src_w - w, src_h - h
262
266
  elif pf:
263
267
  if "x" in pf:
@@ -302,8 +306,8 @@ def compute_splice_two_im(
302
306
  h1: int,
303
307
  w2: int,
304
308
  h2: int,
305
- align: int = enums.PositionAlign.VERTIAL_CENTER, # type: ignore
306
- order: int = enums.PositionOrder.BEFORE, # type: ignore
309
+ align: int = enums.PositionAlign.VERTIAL_CENTER.value,
310
+ order: int = enums.PositionOrder.BEFORE.value,
307
311
  interval: int = 0,
308
312
  ) -> tuple:
309
313
  """拼接2个图像,计算整体大小和元素原点位置;数值单位都是像素
@@ -325,33 +329,37 @@ def compute_splice_two_im(
325
329
  第2个元素的原点位置x2
326
330
  第2个元素的原点位置y2
327
331
  """
328
- if align in [enums.PositionAlign.TOP, enums.PositionAlign.HORIZONTAL_CENTER, enums.PositionAlign.BOTTOM]:
332
+ if align in [
333
+ enums.PositionAlign.TOP.value,
334
+ enums.PositionAlign.HORIZONTAL_CENTER.value,
335
+ enums.PositionAlign.BOTTOM.value,
336
+ ]:
329
337
  # 水平顺序
330
338
  # 计算整体占位大小w,h
331
339
  w, h = w1 + w2 + interval, max(h1, h2)
332
340
 
333
- if align == enums.PositionAlign.TOP:
341
+ if align == enums.PositionAlign.TOP.value:
334
342
  y1, y2 = 0, 0
335
- elif align == enums.PositionAlign.BOTTOM:
343
+ elif align == enums.PositionAlign.BOTTOM.value:
336
344
  y1, y2 = h - h1, h - h2
337
345
  else:
338
346
  y1, y2 = int((h - h1) / 2), int((h - h2) / 2)
339
347
 
340
- if order == enums.PositionOrder.BEFORE:
348
+ if order == enums.PositionOrder.BEFORE.value:
341
349
  x1, x2 = 0, w1 + interval
342
350
  else:
343
351
  x1, x2 = w2 + interval, 0
344
352
  else:
345
353
  # 垂直
346
354
  w, h = max(w1, w2), h1 + h2 + interval
347
- if align == enums.PositionAlign.LEFT:
355
+ if align == enums.PositionAlign.LEFT.value:
348
356
  x1, x2 = 0, 0
349
- elif align == enums.PositionAlign.RIGHT:
357
+ elif align == enums.PositionAlign.RIGHT.value:
350
358
  x1, x2 = w - w1, w - w2
351
359
  else:
352
360
  x1, x2 = int((w - w1) / 2), int((w - w2) / 2)
353
361
 
354
- if order == enums.PositionOrder.BEFORE:
362
+ if order == enums.PositionOrder.BEFORE.value:
355
363
  y1, y2 = 0, h1 + interval
356
364
  else:
357
365
  y1, y2 = h2 + interval, 0
@@ -369,11 +377,22 @@ def validate_ori_im(ori_im: Image) -> None:
369
377
  raise ProcessLimitException(f"图像总像素不可超过{settings.PROCESSOR_MAX_PIXEL}像素,输入图像({src_w}, {src_h})")
370
378
 
371
379
 
372
- def trans_uri_to_im(uri: str) -> Image:
380
+ def copy_full_img(ori_im: Image) -> Image:
381
+ out_im = ori_im.copy()
382
+ # 复制格式信息
383
+ out_im.format = ori_im.format
384
+ # 复制info中的元数据(包括ICC配置文件等)
385
+ out_im.info = ori_im.info.copy()
386
+ return out_im
387
+
388
+
389
+ @contextmanager
390
+ def trans_uri_to_im(uri: str, use_copy: bool = False) -> typing.Generator:
373
391
  """将输入资源转换成Image对象
374
392
 
375
393
  Args:
376
394
  uri: 文件路径 或者 可下载的链接地址
395
+ use_copy: 是否复制图像,使其不依赖打开的文件
377
396
 
378
397
  Raises:
379
398
  ProcessLimitException: 处理图像大小/像素限制
@@ -392,27 +411,30 @@ def trans_uri_to_im(uri: str) -> Image:
392
411
  if size > settings.PROCESSOR_MAX_FILE_SIZE * 1024 * 1024:
393
412
  raise ProcessLimitException(f"图像文件大小不得超过{settings.PROCESSOR_MAX_FILE_SIZE}MB")
394
413
 
395
- ori_im = Image.open(fp)
396
- validate_ori_im(ori_im)
397
- # 解决临时文件close后im对象不能正常使用得问题
398
- ori_im = ori_im.copy()
414
+ with Image.open(fp) as uri_im:
415
+ validate_ori_im(uri_im)
416
+ # 解决临时文件close后im对象不能正常使用得问题
417
+ ori_im = copy_full_img(uri_im)
418
+ yield ori_im
399
419
  else:
400
420
  size = os.path.getsize(uri)
401
421
  if size > settings.PROCESSOR_MAX_FILE_SIZE * 1024 * 1024:
402
422
  raise ProcessLimitException(f"图像文件大小不得超过{settings.PROCESSOR_MAX_FILE_SIZE}MB")
403
- ori_im = Image.open(uri)
404
- validate_ori_im(ori_im)
405
-
406
- return ori_im
423
+ with Image.open(uri) as uri_im:
424
+ validate_ori_im(uri_im)
425
+ ori_im = uri_im
426
+ if use_copy:
427
+ ori_im = copy_full_img(ori_im)
428
+ yield ori_im
407
429
 
408
430
 
409
431
  class ImgSaveParser(BaseParser):
410
432
  KEY = ""
411
433
  ARGS = {
412
- "format": {"type": enums.ArgType.STRING, "default": None},
413
- "quality": {"type": enums.ArgType.INTEGER, "default": None, "min": 1, "max": 100},
434
+ "format": {"type": enums.ArgType.STRING.value, "default": None},
435
+ "quality": {"type": enums.ArgType.INTEGER.value, "default": None, "min": 1, "max": 100},
414
436
  # 1 表示将原图设置成渐进显示
415
- "interlace": {"type": enums.ArgType.INTEGER, "default": 0, "choices": [0, 1]},
437
+ "interlace": {"type": enums.ArgType.INTEGER.value, "default": 0, "choices": [0, 1]},
416
438
  }
417
439
 
418
440
  def __init__(
@@ -10,10 +10,10 @@ from .base import BaseParser, pre_processing
10
10
 
11
11
  class BlurParser(BaseParser):
12
12
 
13
- KEY = enums.OpAction.BLUR
13
+ KEY = enums.OpAction.BLUR.value
14
14
  ARGS = {
15
15
  # 模糊半径,值越大,图片越模糊
16
- "r": {"type": enums.ArgType.INTEGER, "required": True, "min": 1, "max": 50},
16
+ "r": {"type": enums.ArgType.INTEGER.value, "required": True, "min": 1, "max": 50},
17
17
  }
18
18
 
19
19
  def __init__(
@@ -10,9 +10,9 @@ from .base import BaseParser, pre_processing
10
10
 
11
11
  class CircleParser(BaseParser):
12
12
 
13
- KEY = enums.OpAction.CIRCLE
13
+ KEY = enums.OpAction.CIRCLE.value
14
14
  ARGS = {
15
- "r": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
15
+ "r": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
16
16
  }
17
17
 
18
18
  def __init__(
@@ -9,20 +9,20 @@ from .base import BaseParser, pre_processing, compute_by_geography, compute_by_r
9
9
 
10
10
  class CropParser(BaseParser):
11
11
 
12
- KEY = enums.OpAction.CROP
12
+ KEY = enums.OpAction.CROP.value
13
13
  ARGS = {
14
- "w": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
15
- "h": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
16
- "ratio": {"type": enums.ArgType.STRING, "regex": r"^\d+:\d+$"},
17
- "x": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
18
- "y": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
19
- "g": {"type": enums.ArgType.STRING, "choices": enums.Geography},
14
+ "w": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
15
+ "h": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
16
+ "ratio": {"type": enums.ArgType.STRING.value, "regex": r"^\d+:\d+$"},
17
+ "x": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
18
+ "y": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
19
+ "g": {"type": enums.ArgType.STRING.value, "choices": enums.Geography},
20
20
  # percent field, eg: xywh
21
- "pf": {"type": enums.ArgType.STRING, "default": ""},
21
+ "pf": {"type": enums.ArgType.STRING.value, "default": ""},
22
22
  # padding right
23
- "padr": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
23
+ "padr": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
24
24
  # padding bottom
25
- "padb": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
25
+ "padb": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
26
26
  # 左和上通过x,y控制
27
27
  }
28
28
 
@@ -10,7 +10,7 @@ from .base import BaseParser
10
10
 
11
11
  class GrayParser(BaseParser):
12
12
 
13
- KEY = enums.OpAction.GRAY
13
+ KEY = enums.OpAction.GRAY.value
14
14
  ARGS = {}
15
15
 
16
16
  def __init__(
@@ -6,33 +6,43 @@ from PIL import Image
6
6
 
7
7
  from imgprocessor import enums, settings
8
8
  from imgprocessor.exceptions import ParamValidateException
9
- from .base import BaseParser, pre_processing, compute_by_geography, compute_splice_two_im, trans_uri_to_im
9
+ from .base import (
10
+ BaseParser,
11
+ pre_processing,
12
+ compute_by_geography,
13
+ compute_splice_two_im,
14
+ trans_uri_to_im,
15
+ )
10
16
 
11
17
 
12
18
  class MergeParser(BaseParser):
13
19
 
14
- KEY = enums.OpAction.MERGE
20
+ KEY = enums.OpAction.MERGE.value
15
21
  ARGS = {
16
22
  # 要处理的图片
17
- "image": {"type": enums.ArgType.URI, "required": True, "base64_encode": True},
23
+ "image": {"type": enums.ArgType.URI.value, "required": True, "base64_encode": True},
18
24
  # 对image的处理参数
19
- "actions": {"type": enums.ArgType.ACTION, "base64_encode": True},
20
- # 使用输入图像的大小作为参照进行缩放
21
- "p": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": 1000},
25
+ "actions": {"type": enums.ArgType.ACTION.value, "base64_encode": True},
22
26
  # 是否将imgae当做背景放在输入图像之下; 定义输入图像和image参数的拼接顺序
23
- "bg": {"type": enums.ArgType.INTEGER, "default": 0, "choices": [0, 1]},
27
+ "bg": {"type": enums.ArgType.INTEGER.value, "default": 0, "choices": [0, 1]},
28
+ # 使用输入图像的大小作为参照进行缩放,bg=1按照image缩放输入图像
29
+ "p": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": 1000},
24
30
  # 对齐方式
25
- "order": {"type": enums.ArgType.INTEGER, "choices": enums.PositionOrder},
26
- "align": {"type": enums.ArgType.INTEGER, "default": enums.PositionAlign.BOTTOM, "choices": enums.PositionAlign},
27
- "interval": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 1000},
31
+ "order": {"type": enums.ArgType.INTEGER.value, "choices": enums.PositionOrder},
32
+ "align": {
33
+ "type": enums.ArgType.INTEGER.value,
34
+ "default": enums.PositionAlign.BOTTOM.value,
35
+ "choices": enums.PositionAlign,
36
+ },
37
+ "interval": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 1000},
28
38
  # 粘贴的位置
29
- "g": {"type": enums.ArgType.STRING, "choices": enums.Geography},
30
- "x": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
31
- "y": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
32
- "pf": {"type": enums.ArgType.STRING, "default": ""},
39
+ "g": {"type": enums.ArgType.STRING.value, "choices": enums.Geography},
40
+ "x": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
41
+ "y": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
42
+ "pf": {"type": enums.ArgType.STRING.value, "default": ""},
33
43
  # 拼接后大小包含2个图像,空白区域使用color颜色填充
34
44
  "color": {
35
- "type": enums.ArgType.STRING,
45
+ "type": enums.ArgType.STRING.value,
36
46
  "default": "0000", # 为了保证透明背景
37
47
  "regex": r"^([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3,4})$",
38
48
  },
@@ -80,7 +90,7 @@ class MergeParser(BaseParser):
80
90
  raise ParamValidateException(f"merage操作中actions参数校验异常,其中 {e}")
81
91
 
82
92
  def compute(self, src_w: int, src_h: int, w2: int, h2: int) -> tuple:
83
- if self.order in enums.PositionOrder: # type: ignore
93
+ if self.order in enums.PositionOrder:
84
94
  order = typing.cast(int, self.order)
85
95
  w, h, x1, y1, x2, y2 = compute_splice_two_im(
86
96
  src_w,
@@ -107,23 +117,26 @@ class MergeParser(BaseParser):
107
117
 
108
118
  def do_action(self, im: Image) -> Image:
109
119
  im = pre_processing(im, use_alpha=True)
110
- src_w, src_h = im.size
111
120
 
112
121
  # 处理要合并的图像
113
- im2 = trans_uri_to_im(self.image)
114
- im2 = pre_processing(im2, use_alpha=True)
122
+ with trans_uri_to_im(self.image, use_copy=True) as _im2:
123
+ im2 = pre_processing(_im2, use_alpha=True)
124
+
115
125
  if self.actions:
116
126
  from imgprocessor.processor import ProcessorCtr
117
127
 
118
128
  im2 = ProcessorCtr.handle_img_actions(im2, self.actions)
119
- if self.p:
120
- w2, h2 = round(src_w * self.p / 100), round(src_h * self.p / 100)
121
- im2 = im2.resize((w2, h2), resample=Image.LANCZOS)
122
129
 
130
+ # 调整拼接顺序
123
131
  if self.bg:
124
- # 调整拼接顺序
125
132
  im, im2 = im2, im
126
133
 
134
+ # 缩放图像
135
+ if self.p:
136
+ src_w, src_h = im.size
137
+ w2, h2 = round(src_w * self.p / 100), round(src_h * self.p / 100)
138
+ im2 = im2.resize((w2, h2), resample=Image.LANCZOS)
139
+
127
140
  src_w, src_h = im.size
128
141
  w2, h2 = im2.size
129
142
 
@@ -10,25 +10,29 @@ from .base import BaseParser, pre_processing
10
10
 
11
11
  class ResizeParser(BaseParser):
12
12
 
13
- KEY = enums.OpAction.RESIZE
13
+ KEY = enums.OpAction.RESIZE.value
14
14
  ARGS = {
15
- "m": {"type": enums.ArgType.STRING, "default": enums.ResizeMode.LFIT, "choices": enums.ResizeMode},
16
- "w": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
17
- "h": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
18
- "l": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
19
- "s": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
20
- "limit": {"type": enums.ArgType.INTEGER, "default": 1, "choices": [0, 1]},
15
+ "m": {
16
+ "type": enums.ArgType.STRING.value,
17
+ "default": enums.ResizeMode.LFIT.value,
18
+ "choices": enums.ResizeMode,
19
+ },
20
+ "w": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
21
+ "h": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
22
+ "l": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
23
+ "s": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
24
+ "limit": {"type": enums.ArgType.INTEGER.value, "default": 1, "choices": [0, 1]},
21
25
  "color": {
22
- "type": enums.ArgType.STRING,
26
+ "type": enums.ArgType.STRING.value,
23
27
  "default": "FFFFFF", # 默认白色
24
28
  "regex": r"^([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3,4})$",
25
29
  },
26
- "p": {"type": enums.ArgType.INTEGER, "default": 0, "min": 1, "max": 1000},
30
+ "p": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 1, "max": 1000},
27
31
  }
28
32
 
29
33
  def __init__(
30
34
  self,
31
- m: str = enums.ResizeMode.LFIT, # type: ignore
35
+ m: str = enums.ResizeMode.LFIT.value,
32
36
  w: int = 0,
33
37
  h: int = 0,
34
38
  l: int = 0, # noqa: E741
@@ -50,13 +54,13 @@ class ResizeParser(BaseParser):
50
54
  def compute(self, src_w: int, src_h: int) -> tuple:
51
55
  """计算出`Image.resize`需要的参数"""
52
56
  if self.w or self.h:
53
- if self.m in [enums.ResizeMode.FIXED, enums.ResizeMode.PAD, enums.ResizeMode.FIT]:
57
+ if self.m in [enums.ResizeMode.FIXED.value, enums.ResizeMode.PAD.value, enums.ResizeMode.FIT.value]:
54
58
  # 有可能改变原图宽高比
55
59
  if not (self.w and self.h):
56
60
  raise ParamValidateException(f"当m={self.m}的模式下,参数w和h都必不可少且不能为0")
57
61
  # w,h按指定的即可,无需计算
58
62
  w, h = self.w, self.h
59
- elif self.m == enums.ResizeMode.MFIT:
63
+ elif self.m == enums.ResizeMode.MFIT.value:
60
64
  # 低版本Pillow未实现 ImageOps.cover 方法,自行处理
61
65
  # 等比缩放
62
66
  if self.w and self.h:
@@ -70,7 +74,7 @@ class ResizeParser(BaseParser):
70
74
  else:
71
75
  w, h = round(self.h * src_w / src_h), self.h
72
76
  else:
73
- # 默认 enums.ResizeMode.LFIT
77
+ # 默认 enums.ResizeMode.LFIT.value
74
78
  # 等比缩放
75
79
  if self.w and self.h:
76
80
  # 指定w与h的矩形内的最大图像
@@ -114,9 +118,9 @@ class ResizeParser(BaseParser):
114
118
  if size == im.size:
115
119
  # 大小没有变化直接返回
116
120
  return im
117
- if self.m == enums.ResizeMode.PAD:
121
+ if self.m == enums.ResizeMode.PAD.value:
118
122
  out = ImageOps.pad(im, size, color=f"#{self.color}")
119
- elif self.m == enums.ResizeMode.FIT:
123
+ elif self.m == enums.ResizeMode.FIT.value:
120
124
  out = ImageOps.fit(im, size)
121
125
  else:
122
126
  out = im.resize(size, resample=Image.LANCZOS)
@@ -10,10 +10,10 @@ from .base import BaseParser, pre_processing
10
10
 
11
11
  class RotateParser(BaseParser):
12
12
 
13
- KEY = enums.OpAction.ROTATE
13
+ KEY = enums.OpAction.ROTATE.value
14
14
  ARGS = {
15
15
  # 顺时针旋转的度数
16
- "value": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 360},
16
+ "value": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 360},
17
17
  }
18
18
 
19
19
  def __init__(
@@ -6,45 +6,63 @@ from PIL import Image, ImageFont, ImageDraw
6
6
 
7
7
  from imgprocessor import enums, settings, utils
8
8
  from imgprocessor.exceptions import ParamValidateException
9
- from .base import BaseParser, pre_processing, compute_splice_two_im, compute_by_geography, trans_uri_to_im
9
+ from .base import (
10
+ BaseParser,
11
+ pre_processing,
12
+ compute_splice_two_im,
13
+ compute_by_geography,
14
+ trans_uri_to_im,
15
+ )
10
16
 
11
17
 
12
18
  class WatermarkParser(BaseParser):
13
19
 
14
- KEY = enums.OpAction.WATERMARK
20
+ KEY = enums.OpAction.WATERMARK.value
15
21
  ARGS = {
16
22
  # 水印本身的不透明度,100表示完全不透明
17
- "t": {"type": enums.ArgType.INTEGER, "default": 100, "min": 0, "max": 100},
18
- "g": {"type": enums.ArgType.STRING, "choices": enums.Geography},
19
- "x": {"type": enums.ArgType.INTEGER, "default": 10, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
20
- "y": {"type": enums.ArgType.INTEGER, "default": 10, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
23
+ "t": {"type": enums.ArgType.INTEGER.value, "default": 100, "min": 0, "max": 100},
24
+ "g": {"type": enums.ArgType.STRING.value, "choices": enums.Geography},
25
+ "x": {"type": enums.ArgType.INTEGER.value, "default": 10, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
26
+ "y": {"type": enums.ArgType.INTEGER.value, "default": 10, "min": 0, "max": settings.PROCESSOR_MAX_W_H},
21
27
  # percent field, eg: xy
22
- "pf": {"type": enums.ArgType.STRING, "default": ""},
28
+ "pf": {"type": enums.ArgType.STRING.value, "default": ""},
23
29
  # 是否将图片水印或文字水印铺满原图; 值为1开启
24
- "fill": {"type": enums.ArgType.INTEGER, "default": 0, "choices": [0, 1]},
25
- "padx": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 4096},
26
- "pady": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 4096},
30
+ "fill": {"type": enums.ArgType.INTEGER.value, "default": 0, "choices": [0, 1]},
31
+ "padx": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 4096},
32
+ "pady": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 4096},
27
33
  # 图片水印路径
28
- "image": {"type": enums.ArgType.URI, "base64_encode": True},
34
+ "image": {"type": enums.ArgType.URI.value, "base64_encode": True},
29
35
  # 水印的原始设计参照尺寸,会根据原图大小缩放水印
30
- "design": {"type": enums.ArgType.INTEGER, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
36
+ "design": {"type": enums.ArgType.INTEGER.value, "min": 1, "max": settings.PROCESSOR_MAX_W_H},
31
37
  # 文字
32
- "text": {"type": enums.ArgType.STRING, "base64_encode": True, "max_length": 64},
33
- "font": {"type": enums.ArgType.STRING, "base64_encode": True},
38
+ "text": {"type": enums.ArgType.STRING.value, "base64_encode": True, "max_length": 64},
39
+ "font": {"type": enums.ArgType.STRING.value, "base64_encode": True},
34
40
  # 文字默认黑色
35
- "color": {"type": enums.ArgType.STRING, "default": "000000", "regex": "^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$"},
36
- "size": {"type": enums.ArgType.INTEGER, "default": 40, "min": 1, "max": 1000},
41
+ "color": {
42
+ "type": enums.ArgType.STRING.value,
43
+ "default": "000000",
44
+ "regex": "^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$",
45
+ },
46
+ "size": {"type": enums.ArgType.INTEGER.value, "default": 40, "min": 1, "max": 1000},
37
47
  # 文字水印的阴影透明度, 0表示没有阴影
38
- "shadow": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 100},
48
+ "shadow": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 100},
39
49
  # 顺时针旋转角度
40
- "rotate": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 360},
50
+ "rotate": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 360},
41
51
  # 图文混合水印参数
42
52
  # 文字和图片水印的前后顺序; 0表示图片水印在前;1表示文字水印在前
43
- "order": {"type": enums.ArgType.INTEGER, "default": enums.PositionOrder.BEFORE, "choices": enums.PositionOrder},
53
+ "order": {
54
+ "type": enums.ArgType.INTEGER.value,
55
+ "default": enums.PositionOrder.BEFORE.value,
56
+ "choices": enums.PositionOrder,
57
+ },
44
58
  # 文字水印和图片水印的对齐方式; 0表示文字水印和图片水印上对齐; 1表示文字水印和图片水印中对齐; 2: 表示文字水印和图片水印下对齐
45
- "align": {"type": enums.ArgType.INTEGER, "default": enums.PositionAlign.BOTTOM, "choices": enums.PositionAlign},
59
+ "align": {
60
+ "type": enums.ArgType.INTEGER.value,
61
+ "default": enums.PositionAlign.BOTTOM.value,
62
+ "choices": enums.PositionAlign,
63
+ },
46
64
  # 文字水印和图片水印间的间距
47
- "interval": {"type": enums.ArgType.INTEGER, "default": 0, "min": 0, "max": 1000},
65
+ "interval": {"type": enums.ArgType.INTEGER.value, "default": 0, "min": 0, "max": 1000},
48
66
  }
49
67
 
50
68
  def __init__(
@@ -100,8 +118,8 @@ class WatermarkParser(BaseParser):
100
118
  w1, h1, w2, h2 = 0, 0, 0, 0
101
119
  icon = None
102
120
  if self.image:
103
- icon = trans_uri_to_im(self.image)
104
- icon = pre_processing(icon, use_alpha=True)
121
+ with trans_uri_to_im(self.image, use_copy=True) as _icon:
122
+ icon = pre_processing(_icon, use_alpha=True)
105
123
  if not self.text:
106
124
  # 没有文字,直接返回
107
125
  return icon
@@ -27,12 +27,12 @@ class ProcessorCtr(object):
27
27
 
28
28
  def run(self) -> typing.Optional[typing.ByteString]:
29
29
  # 初始化输入
30
- ori_im = trans_uri_to_im(self.input_uri)
31
- # 处理图像
32
- im = self.handle_img_actions(ori_im, self.params.actions)
33
- # 输出、保存
34
- kwargs = self.params.save_parser.compute(ori_im, im)
35
- return self.save_img_to_file(im, out_path=self.out_path, **kwargs)
30
+ with trans_uri_to_im(self.input_uri) as ori_im:
31
+ # 处理图像
32
+ im = self.handle_img_actions(ori_im, self.params.actions)
33
+ # 输出、保存
34
+ kwargs = self.params.save_parser.compute(ori_im, im)
35
+ return self.save_img_to_file(im, out_path=self.out_path, **kwargs)
36
36
 
37
37
  @classmethod
38
38
  def handle_img_actions(cls, ori_im: Image, actions: list[BaseParser]) -> Image:
@@ -51,13 +51,12 @@ class ProcessorCtr(object):
51
51
  **kwargs: typing.Any,
52
52
  ) -> typing.Optional[typing.ByteString]:
53
53
  fmt = kwargs.get("format") or im.format
54
- kwargs["format"] = fmt
55
54
 
56
- if fmt.upper() == enums.ImageFormat.JPEG and im.mode == "RGBA":
55
+ if fmt and fmt.upper() == enums.ImageFormat.JPEG.value and im.mode == "RGBA":
57
56
  im = im.convert("RGB")
58
57
 
59
58
  if not kwargs.get("quality"):
60
- if fmt.upper() == enums.ImageFormat.JPEG and im.format == enums.ImageFormat.JPEG:
59
+ if fmt and fmt.upper() == enums.ImageFormat.JPEG.value and im.format == enums.ImageFormat.JPEG.value:
61
60
  kwargs["quality"] = "keep"
62
61
  else:
63
62
  kwargs["quality"] = settings.PROCESSOR_DEFAULT_QUALITY
@@ -107,20 +106,20 @@ def extract_main_color(img_path: str, delta_h: float = 0.3) -> str:
107
106
  颜色值,eg: FFFFFF
108
107
  """
109
108
  r, g, b = 0, 0, 0
110
- im = Image.open(img_path)
111
- if im.mode != "RGB":
112
- im = im.convert("RGB")
113
- # 转换成HSV即 色相(Hue)、饱和度(Saturation)、明度(alue),取值范围[0,1]
114
- # 取H计算平均色相
115
- all_h = [colorsys.rgb_to_hsv(*im.getpixel((x, y)))[0] for x in range(im.size[0]) for y in range(im.size[1])]
116
- avg_h = sum(all_h) / (im.size[0] * im.size[1])
117
- # 取与平均色相相近的像素色值rgb用于计算,像素值取值范围[0,255]
118
- beyond = list(
119
- filter(
120
- lambda x: abs(colorsys.rgb_to_hsv(*x)[0] - avg_h) < delta_h,
121
- [im.getpixel((x, y)) for x in range(im.size[0]) for y in range(im.size[1])],
109
+ with Image.open(img_path) as im:
110
+ if im.mode != "RGB":
111
+ im = im.convert("RGB")
112
+ # 转换成HSV即 色相(Hue)、饱和度(Saturation)、明度(alue),取值范围[0,1]
113
+ # 取H计算平均色相
114
+ all_h = [colorsys.rgb_to_hsv(*im.getpixel((x, y)))[0] for x in range(im.size[0]) for y in range(im.size[1])]
115
+ avg_h = sum(all_h) / (im.size[0] * im.size[1])
116
+ # 取与平均色相相近的像素色值rgb用于计算,像素值取值范围[0,255]
117
+ beyond = list(
118
+ filter(
119
+ lambda x: abs(colorsys.rgb_to_hsv(*x)[0] - avg_h) < delta_h,
120
+ [im.getpixel((x, y)) for x in range(im.size[0]) for y in range(im.size[1])],
121
+ )
122
122
  )
123
- )
124
123
  if len(beyond):
125
124
  r = int(sum(e[0] for e in beyond) / len(beyond))
126
125
  g = int(sum(e[1] for e in beyond) / len(beyond))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-img-processor
3
- Version: 1.2.0
3
+ Version: 1.2.3
4
4
  Summary: Image editor using Python and Pillow.
5
5
  Home-page: https://github.com/SkylerHu/py-img-processor.git
6
6
  Author: SkylerHu
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: py-enum>=1.1.1
24
+ Requires-Dist: py-enum>=2.1.1
25
25
  Requires-Dist: Pillow>=8
26
26
 
27
27
  # py-img-processor
@@ -0,0 +1,2 @@
1
+ py-enum>=2.1.1
2
+ Pillow>=8
@@ -39,7 +39,7 @@ setup(
39
39
  ]
40
40
  },
41
41
  install_requires=[
42
- "py-enum>=1.1.1",
42
+ "py-enum>=2.1.1",
43
43
  "Pillow>=8",
44
44
  ],
45
45
  python_requires=">=3.9",
@@ -1,2 +0,0 @@
1
- py-enum>=1.1.1
2
- Pillow>=8