Source code for SDF.data_model.image_data
import base64
from io import BytesIO
from xml.etree.ElementTree import Element
from PIL import Image
from SDF.data_model._helper_functions import pop_element_attribute, pop_element_text, element_is_empty
from SDF.data_model.abstract import Data
[docs]class ImageData(Data):
"""Wraps a PIL Image. Represents an SDF <data> element of type 'img'."""
def __init__(self, data: Image.Image):
if not isinstance(data, Image.Image):
raise TypeError(f"Expected a PIL Image, got {type(data)}")
self.__image = data
@property
def data(self) -> Image.Image:
return self.__image
@property
def type_for_xml(self) -> str:
return "img"
[docs] def to_xml_element(self) -> Element:
element = Element("data", attrib={"encoding": "base64", "type": "image/png"})
element.text = ImageData.image_to_base64(self.data)
return element
[docs] @classmethod
def from_xml_element(cls, element: Element) -> "ImageData":
if element.tag != "data":
raise ValueError(f"Expected a <data> element, got {element.tag}")
# pop valid, but irrelevant attributes
for key in ("dtype", "encoding", "type"):
if key in element.attrib:
pop_element_attribute(element, key)
base64_str = pop_element_text(element).strip()
if not element_is_empty(element):
raise ValueError("Element is not empty")
return cls(ImageData.base64_to_image(base64_str))
def __repr__(self):
return f"{self.__class__.__name__}({self.data!r})"
[docs] @staticmethod
def image_to_base64(image: Image.Image) -> str:
"""Return base64-encoded PNG representation of `image`"""
png_buffer = BytesIO()
image.save(png_buffer, format="png")
return base64.encodebytes(png_buffer.getvalue()).decode("ascii")
[docs] @staticmethod
def base64_to_image(base64_str: str) -> Image.Image:
"""Read base64-encoded PNG image"""
img_buffer = BytesIO()
img_buffer.write(base64.decodebytes(base64_str.strip().encode("ascii")))
return Image.open(img_buffer, formats=["PNG"])
def __eq__(self, other):
if isinstance(other, ImageData) and self.data.size == other.data.size:
# compare resolutions (fast), then compare pixel values (slow)
return (self.data.size == other.data.size and
self.image_to_base64(self.__image) == self.image_to_base64(other.__image))
return False