"""Icon normalization — anything Pillow can decode becomes a 256x256 PNG. Intune wants exactly 256x256 PNG, but nothing a human picks (a photo from the web, a vendor logo, a screenshot) arrives that way. Rather than reject it, fit it into the box: aspect ratio preserved, transparent letterbox around it. """ from __future__ import annotations import io from PIL import Image, ImageOps, UnidentifiedImageError ICON_SIZE = (256, 256) class ImageError(ValueError): """Raised when the bytes are not a decodable raster image.""" def normalize_icon(data: bytes) -> bytes: """Return `data` as exactly-256x256 PNG bytes. A file that *already* is a 256x256 PNG is returned byte-identical — the icon store is content-addressed by SHA256, so re-encoding an unchanged upload would move it to a new path on every save for no reason. Raises `ImageError` if the bytes cannot be decoded as an image. """ try: img = Image.open(io.BytesIO(data)) img.load() except (UnidentifiedImageError, OSError, ValueError) as exc: raise ImageError("Not a readable image file.") from exc if img.format == "PNG" and img.size == ICON_SIZE: return data # `contain` scales down to fit inside the box without cropping; a smaller # source is left at its own size rather than blown up into mush. fitted = ImageOps.contain(img.convert("RGBA"), ICON_SIZE, Image.LANCZOS) canvas = Image.new("RGBA", ICON_SIZE, (0, 0, 0, 0)) canvas.paste( fitted, ((ICON_SIZE[0] - fitted.width) // 2, (ICON_SIZE[1] - fitted.height) // 2), ) out = io.BytesIO() canvas.save(out, format="PNG", optimize=True) return out.getvalue()