about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pptx/parts/image.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/pptx/parts/image.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/parts/image.py')
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/parts/image.py275
1 files changed, 275 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/parts/image.py b/.venv/lib/python3.12/site-packages/pptx/parts/image.py
new file mode 100644
index 00000000..9be5d02d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/parts/image.py
@@ -0,0 +1,275 @@
+"""ImagePart and related objects."""
+
+from __future__ import annotations
+
+import hashlib
+import io
+import os
+from typing import IO, TYPE_CHECKING, Any, cast
+
+from PIL import Image as PIL_Image
+
+from pptx.opc.package import Part
+from pptx.opc.spec import image_content_types
+from pptx.util import Emu, lazyproperty
+
+if TYPE_CHECKING:
+    from pptx.opc.packuri import PackURI
+    from pptx.package import Package
+    from pptx.util import Length
+
+
+class ImagePart(Part):
+    """An image part.
+
+    An image part generally has a partname matching the regex `ppt/media/image[1-9][0-9]*.*`.
+    """
+
+    def __init__(
+        self,
+        partname: PackURI,
+        content_type: str,
+        package: Package,
+        blob: bytes,
+        filename: str | None = None,
+    ):
+        super(ImagePart, self).__init__(partname, content_type, package, blob)
+        self._blob = blob
+        self._filename = filename
+
+    @classmethod
+    def new(cls, package: Package, image: Image) -> ImagePart:
+        """Return new |ImagePart| instance containing `image`.
+
+        `image` is an |Image| object.
+        """
+        return cls(
+            package.next_image_partname(image.ext),
+            image.content_type,
+            package,
+            image.blob,
+            image.filename,
+        )
+
+    @property
+    def desc(self) -> str:
+        """The filename associated with this image.
+
+        Either the filename of the original image or a generic name of the form `image.ext` where
+        `ext` is appropriate to the image file format, e.g. `'jpg'`. An image created using a path
+        will have that filename; one created with a file-like object will have a generic name.
+        """
+        # -- return generic filename if original filename is unknown --
+        if self._filename is None:
+            return f"image.{self.ext}"
+        return self._filename
+
+    @property
+    def ext(self) -> str:
+        """File-name extension for this image e.g. `'png'`."""
+        return self.partname.ext
+
+    @property
+    def image(self) -> Image:
+        """An |Image| object containing the image in this image part.
+
+        Note this is a `pptx.image.Image` object, not a PIL Image.
+        """
+        return Image(self._blob, self.desc)
+
+    def scale(self, scaled_cx: int | None, scaled_cy: int | None) -> tuple[int, int]:
+        """Return scaled image dimensions in EMU based on the combination of parameters supplied.
+
+        If `scaled_cx` and `scaled_cy` are both |None|, the native image size is returned. If
+        neither `scaled_cx` nor `scaled_cy` is |None|, their values are returned unchanged. If a
+        value is provided for either `scaled_cx` or `scaled_cy` and the other is |None|, the
+        missing value is calculated such that the image's aspect ratio is preserved.
+        """
+        image_cx, image_cy = self._native_size
+
+        if scaled_cx and scaled_cy:
+            return scaled_cx, scaled_cy
+
+        if scaled_cx and not scaled_cy:
+            scaling_factor = float(scaled_cx) / float(image_cx)
+            scaled_cy = int(round(image_cy * scaling_factor))
+            return scaled_cx, scaled_cy
+
+        if not scaled_cx and scaled_cy:
+            scaling_factor = float(scaled_cy) / float(image_cy)
+            scaled_cx = int(round(image_cx * scaling_factor))
+            return scaled_cx, scaled_cy
+
+        # -- only remaining case is both `scaled_cx` and `scaled_cy` are `None` --
+        return image_cx, image_cy
+
+    @lazyproperty
+    def sha1(self) -> str:
+        """The 40-character SHA1 hash digest for the image binary of this image part.
+
+        like: `"1be010ea47803b00e140b852765cdf84f491da47"`.
+        """
+        return hashlib.sha1(self._blob).hexdigest()
+
+    @property
+    def _dpi(self) -> tuple[int, int]:
+        """(horz_dpi, vert_dpi) pair representing the dots-per-inch resolution of this image."""
+        image = Image.from_blob(self._blob)
+        return image.dpi
+
+    @property
+    def _native_size(self) -> tuple[Length, Length]:
+        """A (width, height) 2-tuple representing the native dimensions of the image in EMU.
+
+        Calculated based on the image DPI value, if present, assuming 72 dpi as a default.
+        """
+        EMU_PER_INCH = 914400
+        horz_dpi, vert_dpi = self._dpi
+        width_px, height_px = self._px_size
+
+        width = EMU_PER_INCH * width_px / horz_dpi
+        height = EMU_PER_INCH * height_px / vert_dpi
+
+        return Emu(int(width)), Emu(int(height))
+
+    @property
+    def _px_size(self) -> tuple[int, int]:
+        """A (width, height) 2-tuple representing the dimensions of this image in pixels."""
+        image = Image.from_blob(self._blob)
+        return image.size
+
+
+class Image(object):
+    """Immutable value object representing an image such as a JPEG, PNG, or GIF."""
+
+    def __init__(self, blob: bytes, filename: str | None):
+        super(Image, self).__init__()
+        self._blob = blob
+        self._filename = filename
+
+    @classmethod
+    def from_blob(cls, blob: bytes, filename: str | None = None) -> Image:
+        """Return a new |Image| object loaded from the image binary in `blob`."""
+        return cls(blob, filename)
+
+    @classmethod
+    def from_file(cls, image_file: str | IO[bytes]) -> Image:
+        """Return a new |Image| object loaded from `image_file`.
+
+        `image_file` can be either a path (str) or a file-like object.
+        """
+        if isinstance(image_file, str):
+            # treat image_file as a path
+            with open(image_file, "rb") as f:
+                blob = f.read()
+            filename = os.path.basename(image_file)
+        else:
+            # assume image_file is a file-like object
+            # ---reposition file cursor if it has one---
+            if callable(getattr(image_file, "seek")):
+                image_file.seek(0)
+            blob = image_file.read()
+            filename = None
+
+        return cls.from_blob(blob, filename)
+
+    @property
+    def blob(self) -> bytes:
+        """The binary image bytestream of this image."""
+        return self._blob
+
+    @lazyproperty
+    def content_type(self) -> str:
+        """MIME-type of this image, e.g. `"image/jpeg"`."""
+        return image_content_types[self.ext]
+
+    @lazyproperty
+    def dpi(self) -> tuple[int, int]:
+        """A (horz_dpi, vert_dpi) 2-tuple specifying the dots-per-inch resolution of this image.
+
+        A default value of (72, 72) is used if the dpi is not specified in the image file.
+        """
+
+        def int_dpi(dpi: Any):
+            """Return an integer dots-per-inch value corresponding to `dpi`.
+
+            If `dpi` is |None|, a non-numeric type, less than 1 or greater than 2048, 72 is
+            returned.
+            """
+            try:
+                int_dpi = int(round(float(dpi)))
+                if int_dpi < 1 or int_dpi > 2048:
+                    int_dpi = 72
+            except (TypeError, ValueError):
+                int_dpi = 72
+            return int_dpi
+
+        def normalize_pil_dpi(pil_dpi: tuple[int, int] | None):
+            """Return a (horz_dpi, vert_dpi) 2-tuple corresponding to `pil_dpi`.
+
+            The value for the 'dpi' key in the `info` dict of a PIL image. If the 'dpi' key is not
+            present or contains an invalid value, `(72, 72)` is returned.
+            """
+            if isinstance(pil_dpi, tuple):
+                return (int_dpi(pil_dpi[0]), int_dpi(pil_dpi[1]))
+            return (72, 72)
+
+        return normalize_pil_dpi(self._pil_props[2])
+
+    @lazyproperty
+    def ext(self) -> str:
+        """Canonical file extension for this image e.g. `'png'`.
+
+        The returned extension is all lowercase and is the canonical extension for the content type
+        of this image, regardless of what extension may have been used in its filename, if any.
+        """
+        ext_map = {
+            "BMP": "bmp",
+            "GIF": "gif",
+            "JPEG": "jpg",
+            "PNG": "png",
+            "TIFF": "tiff",
+            "WMF": "wmf",
+        }
+        format = self._format
+        if format not in ext_map:
+            tmpl = "unsupported image format, expected one of: %s, got '%s'"
+            raise ValueError(tmpl % (ext_map.keys(), format))
+        return ext_map[format]
+
+    @property
+    def filename(self) -> str | None:
+        """Filename from path used to load this image, if loaded from the filesystem.
+
+        |None| if no filename was used in loading, such as when loaded from an in-memory stream.
+        """
+        return self._filename
+
+    @lazyproperty
+    def sha1(self) -> str:
+        """SHA1 hash digest of the image blob."""
+        return hashlib.sha1(self._blob).hexdigest()
+
+    @lazyproperty
+    def size(self) -> tuple[int, int]:
+        """A (width, height) 2-tuple specifying the dimensions of this image in pixels."""
+        return self._pil_props[1]
+
+    @property
+    def _format(self) -> str | None:
+        """The PIL Image format of this image, e.g. 'PNG'."""
+        return self._pil_props[0]
+
+    @lazyproperty
+    def _pil_props(self) -> tuple[str | None, tuple[int, int], tuple[int, int] | None]:
+        """tuple of image properties extracted from this image using Pillow."""
+        stream = io.BytesIO(self._blob)
+        pil_image = PIL_Image.open(stream)  # pyright: ignore[reportUnknownMemberType]
+        format = pil_image.format
+        width_px, height_px = pil_image.size
+        dpi = cast(
+            "tuple[int, int] | None",
+            pil_image.info.get("dpi"),  # pyright: ignore[reportUnknownMemberType]
+        )
+        stream.close()
+        return (format, (width_px, height_px), dpi)