| | import base64 |
| | import io |
| |
|
| | from PIL import Image |
| |
|
| |
|
| | def encode_image(img: Image.Image, image_format: str | None = None) -> str: |
| | """Encode a PIL image to a data URL in the specified format. |
| | |
| | image_format examples: "PNG", "JPEG", "WEBP" |
| | If not provided, fall back to WEBP. |
| | """ |
| |
|
| | params = {} |
| |
|
| | if image_format is None: |
| | image_format = "WEBP" |
| |
|
| | if image_format == "WEBP": |
| | params["quality"] = 90 |
| |
|
| | buffer = io.BytesIO() |
| | try: |
| | img.save(buffer, format=image_format, **params) |
| | except Exception as e: |
| | raise ValueError(f"Failed to encode image as {image_format}: {e}") |
| |
|
| | raw = buffer.getvalue() |
| | b64 = base64.b64encode(raw).decode("ascii") |
| | mime = f"image/{image_format.lower()}" |
| | return f"data:{mime};base64,{b64}" |
| |
|
| |
|
| | def decode_image(data: str) -> Image.Image: |
| | """Decode a data URL or base64 string into a PIL.Image.Image. |
| | |
| | Supports any format that Pillow supports (png, jpg, webp, etc). |
| | """ |
| |
|
| | if not isinstance(data, str): |
| | raise TypeError("decode_image expects a string") |
| |
|
| | |
| | if data.startswith("data:"): |
| | try: |
| | _, _, b64 = data.partition(",") |
| | data = b64 |
| | except Exception: |
| | raise ValueError("Invalid data URL") |
| |
|
| | |
| | try: |
| | raw = base64.b64decode(data.encode("ascii")) |
| | except Exception as e: |
| | raise ValueError(f"Base64 decode error: {e}") |
| |
|
| | |
| | buffer = io.BytesIO(raw) |
| | try: |
| | img = Image.open(buffer) |
| | img.load() |
| | return img |
| | except Exception as e: |
| | raise ValueError(f"Failed to decode image: {e}") |
| |
|