imprinted 0.7.0__py3-none-any.whl

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.
imprinted/__init__.py ADDED
@@ -0,0 +1,59 @@
1
+ """Imprinted: hide a short payload in a picture, read it back from the file or a photo of a print.
2
+
3
+ import imprinted
4
+ imprinted.encode("photo.png", "hello", "photo_imprinted.png")
5
+ imprinted.decode("phone_shot.jpg") # -> "hello", or None
6
+
7
+ `imprinted.core` is the full codec (encode stats, every Imprinted found in a photo, the strength ladder).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+
13
+ import numpy as np
14
+ from PIL import Image, ImageOps
15
+
16
+ from . import core
17
+
18
+ __version__ = "0.7.0"
19
+ __all__ = ["encode", "decode", "core"]
20
+
21
+
22
+ def _load(image) -> np.ndarray:
23
+ """Path, PIL image or uint8 array (RGB, RGBA or grayscale) -> uint8 array, EXIF rotation applied."""
24
+ if isinstance(image, np.ndarray):
25
+ return image
26
+ if isinstance(image, (str, os.PathLike)):
27
+ image = Image.open(image)
28
+ image = ImageOps.exif_transpose(image)
29
+ if image.mode not in ("L", "RGB", "RGBA"):
30
+ image = image.convert("RGBA" if "A" in image.getbands() or "transparency" in image.info else "RGB")
31
+ return np.asarray(image)
32
+
33
+
34
+ def encode(image, payload: str | bytes, out=None, *, print_width_cm: float = 15.0,
35
+ strength: float | None = None) -> Image.Image:
36
+ """Hide payload (1..32 bytes; a str is UTF-8 encoded) in image (a path, PIL image or RGB uint8
37
+ array). Returns the encoded picture (same size and mode) and saves it to out when given; save
38
+ it lossless (PNG). print_width_cm: how wide it will be printed or shown, which sets the pattern
39
+ size. strength: None takes the strongest within the visibility budget (SSIM >= 0.955, mean
40
+ dL* <= 0.99). ValueError when the picture is too small or too clipped to carry the payload."""
41
+ rgb = _load(image)
42
+ if isinstance(payload, str):
43
+ payload = payload.encode()
44
+ b = core.subblock_for_print(rgb.shape[1], print_width_cm * 10)
45
+ enc, _ = core.encode(rgb, payload, delta=strength, subblock_px=b)
46
+ img = Image.fromarray(enc)
47
+ if out is not None:
48
+ img.save(out)
49
+ return img
50
+
51
+
52
+ def decode(image, raw: bool = False) -> str | bytes | None:
53
+ """First payload found in image (a file, screenshot or photo: path, PIL image or RGB uint8
54
+ array), or None. A str (UTF-8) unless raw, then bytes."""
55
+ found = core.decode(_load(image))["found"]
56
+ if not found:
57
+ return None
58
+ payload = found[0]["payload"]
59
+ return payload if raw else payload.decode("utf-8", "replace")