about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pillow_heif
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pillow_heif')
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/AvifImagePlugin.py5
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/HeifImagePlugin.py5
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/__init__.py30
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/_deffered_error.py11
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/_lib_info.py36
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/_version.py3
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/as_plugin.py283
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/constants.py203
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/heif.py648
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/misc.py501
-rw-r--r--.venv/lib/python3.12/site-packages/pillow_heif/options.py91
11 files changed, 1816 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/AvifImagePlugin.py b/.venv/lib/python3.12/site-packages/pillow_heif/AvifImagePlugin.py
new file mode 100644
index 00000000..70460329
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/AvifImagePlugin.py
@@ -0,0 +1,5 @@
+"""Import this file to auto register an AVIF plugin for Pillow."""
+
+from .as_plugin import register_avif_opener
+
+register_avif_opener()
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/HeifImagePlugin.py b/.venv/lib/python3.12/site-packages/pillow_heif/HeifImagePlugin.py
new file mode 100644
index 00000000..c9382728
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/HeifImagePlugin.py
@@ -0,0 +1,5 @@
+"""Import this file to auto register a HEIF plugin for Pillow."""
+
+from .as_plugin import register_heif_opener
+
+register_heif_opener()
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/__init__.py b/.venv/lib/python3.12/site-packages/pillow_heif/__init__.py
new file mode 100644
index 00000000..41f814af
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/__init__.py
@@ -0,0 +1,30 @@
+"""Provide all possible stuff that can be used."""
+
+from . import options
+from ._lib_info import libheif_info, libheif_version
+from ._version import __version__
+from .as_plugin import (
+    AvifImageFile,
+    HeifImageFile,
+    register_avif_opener,
+    register_heif_opener,
+)
+from .constants import (
+    HeifColorPrimaries,
+    HeifDepthRepresentationType,
+    HeifMatrixCoefficients,
+    HeifTransferCharacteristics,
+)
+from .heif import (
+    HeifAuxImage,
+    HeifDepthImage,
+    HeifFile,
+    HeifImage,
+    encode,
+    from_bytes,
+    from_pillow,
+    is_supported,
+    open_heif,
+    read_heif,
+)
+from .misc import get_file_mimetype, load_libheif_plugin, set_orientation
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/_deffered_error.py b/.venv/lib/python3.12/site-packages/pillow_heif/_deffered_error.py
new file mode 100644
index 00000000..0e0399ac
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/_deffered_error.py
@@ -0,0 +1,11 @@
+"""DeferredError class taken from PIL._util.py file."""
+
+
+class DeferredError:  # pylint: disable=too-few-public-methods
+    """Allows failing import for doc purposes, as C module will be not build during docs build."""
+
+    def __init__(self, ex):
+        self.ex = ex
+
+    def __getattr__(self, elt):
+        raise self.ex
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/_lib_info.py b/.venv/lib/python3.12/site-packages/pillow_heif/_lib_info.py
new file mode 100644
index 00000000..a2d0bc03
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/_lib_info.py
@@ -0,0 +1,36 @@
+"""Functions to get versions of underlying libraries."""
+
+try:
+    import _pillow_heif
+except ImportError as ex:
+    from ._deffered_error import DeferredError
+
+    _pillow_heif = DeferredError(ex)
+
+
+def libheif_version() -> str:
+    """Returns ``libheif`` version."""
+    return _pillow_heif.get_lib_info()["libheif"]
+
+
+def libheif_info() -> dict:
+    """Returns a dictionary with version information.
+
+    The keys `libheif`, `HEIF`, `AVIF`, `encoders`, `decoders` are always present, but values for all except
+    `libheif` can be empty.
+
+    {
+        'libheif': '1.15.2',
+        'HEIF': 'x265 HEVC encoder (3.4+31-6722fce1f)',
+        'AVIF': 'AOMedia Project AV1 Encoder 3.5.0',
+        'encoders': {
+            'encoder1_id': 'encoder1_full_name',
+            'encoder2_id': 'encoder2_full_name',
+        },
+        'decoders': {
+            'decoder1_id': 'decoder1_full_name',
+            'decoder2_id': 'decoder2_full_name',
+        },
+    }
+    """
+    return _pillow_heif.get_lib_info()
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/_version.py b/.venv/lib/python3.12/site-packages/pillow_heif/_version.py
new file mode 100644
index 00000000..04be03c5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/_version.py
@@ -0,0 +1,3 @@
+"""Version of pillow_heif/pi_heif."""
+
+__version__ = "0.21.0"
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/as_plugin.py b/.venv/lib/python3.12/site-packages/pillow_heif/as_plugin.py
new file mode 100644
index 00000000..324d6430
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/as_plugin.py
@@ -0,0 +1,283 @@
+"""Plugins for the Pillow library."""
+
+from __future__ import annotations
+
+from itertools import chain
+from typing import IO
+from warnings import warn
+
+from PIL import Image, ImageFile, ImageSequence
+from PIL import __version__ as pil_version
+
+from . import options
+from .constants import HeifCompressionFormat
+from .heif import HeifFile
+from .misc import (
+    CtxEncode,
+    _exif_from_pillow,
+    _get_bytes,
+    _get_orientation_for_encoder,
+    _get_primary_index,
+    _pil_to_supported_mode,
+    _xmp_from_pillow,
+    set_orientation,
+)
+
+try:
+    import _pillow_heif
+except ImportError as ex:
+    from ._deffered_error import DeferredError
+
+    _pillow_heif = DeferredError(ex)
+
+
+class _LibHeifImageFile(ImageFile.ImageFile):
+    """Base class with all functionality for ``HeifImageFile`` and ``AvifImageFile`` classes."""
+
+    _heif_file: HeifFile | None = None
+    _close_exclusive_fp_after_loading = True
+    _mode: str  # only for Pillow 10.1+
+
+    def __init__(self, *args, **kwargs):
+        self.__frame = 0
+        super().__init__(*args, **kwargs)
+
+    def _open(self):
+        try:
+            # when Pillow starts supporting 16-bit multichannel images change `convert_hdr_to_8bit` to False
+            _heif_file = HeifFile(self.fp, convert_hdr_to_8bit=True, hdr_to_16bit=True, remove_stride=False)
+        except (OSError, ValueError, SyntaxError, RuntimeError, EOFError) as exception:
+            raise SyntaxError(str(exception)) from None
+        self.custom_mimetype = _heif_file.mimetype
+        self._heif_file = _heif_file
+        self.__frame = _heif_file.primary_index
+        self._init_from_heif_file(self.__frame)
+        self.tile = []
+
+    def load(self):
+        if self._heif_file:
+            frame_heif = self._heif_file[self.tell()]
+            try:
+                data = frame_heif.data  # Size of Image can change during decoding
+                self._size = frame_heif.size  # noqa
+                self.load_prepare()
+                self.frombytes(data, "raw", (frame_heif.mode, frame_heif.stride))
+            except EOFError:
+                if not ImageFile.LOAD_TRUNCATED_IMAGES:
+                    raise
+                self.load_prepare()
+            # In any case, we close `fp`, since the input data bytes are held by the `HeifFile` class.
+            if self.fp and getattr(self, "_exclusive_fp", False) and hasattr(self.fp, "close"):
+                self.fp.close()
+            self.fp = None
+            if not self.is_animated:
+                self._heif_file = None
+        return super().load()
+
+    if pil_version[:4] in ("10.1", "10.2", "10.3"):
+
+        def getxmp(self) -> dict:
+            """Returns a dictionary containing the XMP tags. Requires ``defusedxml`` to be installed.
+
+            :returns: XMP tags in a dictionary.
+            """
+            if self.info.get("xmp", None):
+                xmp_data = self.info["xmp"].rsplit(b"\x00", 1)
+                if xmp_data[0]:
+                    return self._getxmp(xmp_data[0])  # pylint: disable=no-member
+            return {}
+
+    def seek(self, frame: int):
+        if not self._seek_check(frame):
+            return
+        self.__frame = frame
+        self._init_from_heif_file(frame)
+
+        if pil_version[:3] != "10.":
+            # Pillow 11.0+
+            # We need to create a new core image object on second and
+            # subsequent frames in the image. Image may be different size/mode.
+            # https://github.com/python-pillow/Pillow/issues/8439
+            self.im = Image.core.new(self._mode, self._size)  # pylint: disable=too-many-function-args
+
+        _exif = getattr(self, "_exif", None)  # Pillow 9.2+ do no reload exif between frames.
+        if _exif is not None and getattr(_exif, "_loaded", None):
+            _exif._loaded = False  # pylint: disable=protected-access
+
+    def tell(self) -> int:
+        return self.__frame
+
+    def verify(self) -> None:
+        pass
+
+    @property
+    def n_frames(self) -> int:
+        """Returns the number of available frames.
+
+        :returns: Frame number, starting with 0.
+        """
+        return len(self._heif_file) if self._heif_file else 1
+
+    @property
+    def is_animated(self) -> bool:
+        """Returns ``True`` if this image contains more than one frame, or ``False`` otherwise."""
+        return self.n_frames > 1
+
+    def _seek_check(self, frame: int):
+        if frame < 0 or frame >= self.n_frames:
+            raise EOFError("attempt to seek outside sequence")
+        return self.tell() != frame
+
+    def _init_from_heif_file(self, img_index: int) -> None:
+        if self._heif_file:
+            self._size = self._heif_file[img_index].size
+            self._mode = self._heif_file[img_index].mode
+            self.info = self._heif_file[img_index].info
+            self.info["original_orientation"] = set_orientation(self.info)
+
+
+class HeifImageFile(_LibHeifImageFile):
+    """Pillow plugin class type for a HEIF image format."""
+
+    format = "HEIF"  # noqa
+    format_description = "HEIF container"
+
+
+def _is_supported_heif(fp) -> bool:
+    magic = _get_bytes(fp, 12)
+    if magic[4:8] != b"ftyp":
+        return False
+    return magic[8:12] in (b"heic", b"heix", b"heim", b"heis", b"hevc", b"hevx", b"hevm", b"hevs", b"mif1", b"msf1")
+
+
+def _save_heif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
+    __save_one(im, fp, HeifCompressionFormat.HEVC)
+
+
+def _save_all_heif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
+    __save_all(im, fp, HeifCompressionFormat.HEVC)
+
+
+def register_heif_opener(**kwargs) -> None:
+    """Registers a Pillow plugin for HEIF format.
+
+    :param kwargs: dictionary with values to set in options. See: :ref:`options`.
+    """
+    __options_update(**kwargs)
+    Image.register_open(HeifImageFile.format, HeifImageFile, _is_supported_heif)
+    if _pillow_heif.get_lib_info()["HEIF"]:
+        Image.register_save(HeifImageFile.format, _save_heif)
+        Image.register_save_all(HeifImageFile.format, _save_all_heif)
+    extensions = [".heic", ".heics", ".heif", ".heifs", ".hif"]
+    Image.register_mime(HeifImageFile.format, "image/heif")
+    Image.register_extensions(HeifImageFile.format, extensions)
+
+
+class AvifImageFile(_LibHeifImageFile):
+    """Pillow plugin class type for an AVIF image format."""
+
+    format = "AVIF"  # noqa
+    format_description = "AVIF container"
+
+
+def _is_supported_avif(fp) -> bool:
+    magic = _get_bytes(fp, 12)
+    if magic[4:8] != b"ftyp":
+        return False
+    return magic[8:12] == b"avif"
+    # if magic[8:12] in (
+    #     b"avif",
+    #     b"avis",
+    # ):
+    #     return True
+    # return False
+
+
+def _save_avif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
+    __save_one(im, fp, HeifCompressionFormat.AV1)
+
+
+def _save_all_avif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
+    __save_all(im, fp, HeifCompressionFormat.AV1)
+
+
+def register_avif_opener(**kwargs) -> None:
+    """Registers a Pillow plugin for AVIF format.
+
+    :param kwargs: dictionary with values to set in options. See: :ref:`options`.
+    """
+    if not _pillow_heif.get_lib_info()["AVIF"]:
+        warn("This version of `pillow-heif` was built without AVIF support.", stacklevel=1)
+        return
+    __options_update(**kwargs)
+    Image.register_open(AvifImageFile.format, AvifImageFile, _is_supported_avif)
+    Image.register_save(AvifImageFile.format, _save_avif)
+    Image.register_save_all(AvifImageFile.format, _save_all_avif)
+    # extensions = [".avif", ".avifs"]
+    extensions = [".avif"]
+    Image.register_mime(AvifImageFile.format, "image/avif")
+    Image.register_extensions(AvifImageFile.format, extensions)
+
+
+def __options_update(**kwargs):
+    """Internal function to set options from `register_avif_opener` and `register_heif_opener` methods."""
+    for k, v in kwargs.items():
+        if k == "thumbnails":
+            options.THUMBNAILS = v
+        elif k == "depth_images":
+            options.DEPTH_IMAGES = v
+        elif k == "aux_images":
+            options.AUX_IMAGES = v
+        elif k == "quality":
+            options.QUALITY = v
+        elif k == "save_to_12bit":
+            options.SAVE_HDR_TO_12_BIT = v
+        elif k == "decode_threads":
+            options.DECODE_THREADS = v
+        elif k == "allow_incorrect_headers":
+            options.ALLOW_INCORRECT_HEADERS = v
+        elif k == "save_nclx_profile":
+            options.SAVE_NCLX_PROFILE = v
+        elif k == "preferred_encoder":
+            options.PREFERRED_ENCODER = v
+        elif k == "preferred_decoder":
+            options.PREFERRED_DECODER = v
+        else:
+            warn(f"Unknown option: {k}", stacklevel=1)
+
+
+def __save_one(im: Image.Image, fp: IO[bytes], compression_format: HeifCompressionFormat):
+    ctx_write = CtxEncode(compression_format, **im.encoderinfo)
+    _pil_encode_image(ctx_write, im, True, **im.encoderinfo)
+    ctx_write.save(fp)
+
+
+def __save_all(im: Image.Image, fp: IO[bytes], compression_format: HeifCompressionFormat):
+    ctx_write = CtxEncode(compression_format, **im.encoderinfo)
+    current_frame = im.tell() if hasattr(im, "tell") else None
+    append_images = im.encoderinfo.get("append_images", [])
+    primary_index = _get_primary_index(
+        chain(ImageSequence.Iterator(im), append_images), im.encoderinfo.get("primary_index", None)
+    )
+    for i, frame in enumerate(chain(ImageSequence.Iterator(im), append_images)):
+        _pil_encode_image(ctx_write, frame, i == primary_index, **im.encoderinfo)
+    if current_frame is not None and hasattr(im, "seek"):
+        im.seek(current_frame)
+    ctx_write.save(fp)
+
+
+def _pil_encode_image(ctx: CtxEncode, img: Image.Image, primary: bool, **kwargs) -> None:
+    if img.size[0] <= 0 or img.size[1] <= 0:
+        raise ValueError("Empty images are not supported.")
+    _info = img.info.copy()
+    _info["exif"] = _exif_from_pillow(img)
+    _info["xmp"] = _xmp_from_pillow(img)
+    _info.update(**kwargs)
+    _info["primary"] = primary
+    if img.mode == "YCbCr":
+        ctx.add_image_ycbcr(img, image_orientation=_get_orientation_for_encoder(_info), **_info)
+    else:
+        _img = _pil_to_supported_mode(img)
+        ctx.add_image(
+            _img.size, _img.mode, _img.tobytes(), image_orientation=_get_orientation_for_encoder(_info), **_info
+        )
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/constants.py b/.venv/lib/python3.12/site-packages/pillow_heif/constants.py
new file mode 100644
index 00000000..98e64b2e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/constants.py
@@ -0,0 +1,203 @@
+"""Enums from LibHeif that are used."""
+
+from enum import IntEnum
+
+
+class HeifChroma(IntEnum):
+    """Chroma subsampling definitions."""
+
+    UNDEFINED = 99
+    """Undefined chroma."""
+    MONOCHROME = 0
+    """Mono chroma."""
+    CHROMA_420 = 1
+    """``Cb`` and ``Cr`` are each subsampled at a factor of 2 both horizontally and vertically."""
+    CHROMA_422 = 2
+    """The two chroma components are sampled at half the horizontal sample rate of luma."""
+    CHROMA_444 = 3
+    """Each of the three Y'CbCr components has the same sample rate."""
+    INTERLEAVED_RGB = 10
+    """Simple interleaved RGB."""
+    INTERLEAVED_RGBA = 11
+    """Interleaved RGB with Alpha channel."""
+    INTERLEAVED_RRGGBB_BE = 12
+    """10 bit RGB BE."""
+    INTERLEAVED_RRGGBBAA_BE = 13
+    """10 bit RGB BE with Alpha channel."""
+    INTERLEAVED_RRGGBB_LE = 14
+    """10 bit RGB LE."""
+    INTERLEAVED_RRGGBBAA_LE = 15
+    """10 bit RGB LE with Alpha channel."""
+
+
+class HeifColorspace(IntEnum):
+    """Colorspace format of the image."""
+
+    UNDEFINED = 99
+    """Undefined colorspace."""
+    YCBCR = 0
+    """https://en.wikipedia.org/wiki/YCbCr"""
+    RGB = 1
+    """RGB colorspace."""
+    MONOCHROME = 2
+    """Monochrome colorspace."""
+
+
+class HeifCompressionFormat(IntEnum):
+    """Possible LibHeif compression formats."""
+
+    UNDEFINED = 0
+    """The compression format is not defined."""
+    HEVC = 1
+    """Equivalent to H.265."""
+    AVC = 2
+    """Equivalent to H.264. Defined in ISO/IEC 14496-10."""
+    JPEG = 3
+    """JPEG compression. Defined in ISO/IEC 10918-1."""
+    AV1 = 4
+    """AV1 compression, used for AVIF images."""
+    VVC = 5
+    """Equivalent to H.266. Defined in ISO/IEC 23090-3."""
+    EVC = 6
+    """Equivalent to H.266. Defined in ISO/IEC 23094-1."""
+    JPEG2000 = 7
+    """The compression format is JPEG200 ISO/IEC 15444-16:2021"""
+    UNCOMPRESSED = 8
+    """Defined in ISO/IEC 23001-17:2023 (Final Draft International Standard)."""
+    MASK = 9
+    """Mask image encoding. See ISO/IEC 23008-12:2022 Section 6.10.2"""
+
+
+class HeifColorPrimaries(IntEnum):
+    """Possible NCLX color_primaries values."""
+
+    ITU_R_BT_709_5 = 1
+    """g=0.3;0.6, b=0.15;0.06, r=0.64;0.33, w=0.3127,0.3290"""
+    UNSPECIFIED = 2
+    """No color primaries"""
+    ITU_R_BT_470_6_SYSTEM_M = 4
+    """Unknown"""
+    ITU_R_BT_470_6_SYSTEM_B_G = 5
+    """Unknown"""
+    ITU_R_BT_601_6 = 6
+    """Unknown"""
+    SMPTE_240M = 7
+    """Unknown"""
+    GENERIC_FILM = 8
+    """Unknown"""
+    ITU_R_BT_2020_2_AND_2100_0 = 9
+    """Unknown"""
+    SMPTE_ST_428_1 = 10
+    """Unknown"""
+    SMPTE_RP_431_2 = 11
+    """Unknown"""
+    SMPTE_EG_432_1 = 12
+    """Unknown"""
+    EBU_TECH_3213_E = 22
+    """Unknown"""
+
+
+class HeifTransferCharacteristics(IntEnum):
+    """Possible NCLX transfer_characteristics values."""
+
+    ITU_R_BT_709_5 = 1
+    """Unknown"""
+    UNSPECIFIED = 2
+    """No transfer characteristics"""
+    ITU_R_BT_470_6_SYSTEM_M = 4
+    """Unknown"""
+    ITU_R_BT_470_6_SYSTEM_B_G = 5
+    """Unknown"""
+    ITU_R_BT_601_6 = 6
+    """Unknown"""
+    SMPTE_240M = 7
+    """Unknown"""
+    LINEAR = 8
+    """Unknown"""
+    LOGARITHMIC_100 = 9
+    """Unknown"""
+    LOGARITHMIC_100_SQRT10 = 10
+    """Unknown"""
+    IEC_61966_2_4 = 11
+    """Unknown"""
+    ITU_R_BT_1361 = 12
+    """Unknown"""
+    IEC_61966_2_1 = 13
+    """Unknown"""
+    ITU_R_BT_2020_2_10BIT = 14
+    """Unknown"""
+    ITU_R_BT_2020_2_12BIT = 15
+    """Unknown"""
+    ITU_R_BT_2100_0_PQ = 16
+    """Unknown"""
+    SMPTE_ST_428_1 = 17
+    """Unknown"""
+    ITU_R_BT_2100_0_HLG = 18
+    """Unknown"""
+
+
+class HeifMatrixCoefficients(IntEnum):
+    """Possible NCLX matrix_coefficients values."""
+
+    RGB_GBR = 0
+    """Unknown"""
+    ITU_R_BT_709_5 = 1
+    """Unknown"""
+    UNSPECIFIED = 2
+    """Unknown"""
+    US_FCC_T47 = 4
+    """Unknown"""
+    ITU_R_BT_470_6_SYSTEM_B_G = 5
+    """Unknown"""
+    ITU_R_BT_601_6 = 6
+    """Unknown"""
+    SMPTE_240M = 7
+    """Unknown"""
+    YCGCO = 8
+    """Unknown"""
+    ITU_R_BT_2020_2_NON_CONSTANT_LUMINANCE = 9
+    """Unknown"""
+    ITU_R_BT_2020_2_CONSTANT_LUMINANCE = 10
+    """Unknown"""
+    SMPTE_ST_2085 = 11
+    """Unknown"""
+    CHROMATICITY_DERIVED_NON_CONSTANT_LUMINANCE = 12
+    """Unknown"""
+    CHROMATICITY_DERIVED_CONSTANT_LUMINANCE = 13
+    """Unknown"""
+    ICTCP = 14
+    """Unknown"""
+
+
+class HeifDepthRepresentationType(IntEnum):
+    """Possible values of the ``HeifDepthImage.info['metadata']['representation_type']``."""
+
+    UNIFORM_INVERSE_Z = 0
+    """Unknown"""
+    UNIFORM_DISPARITY = 1
+    """Unknown"""
+    UNIFORM_Z = 2
+    """Unknown"""
+    NON_UNIFORM_DISPARITY = 3
+    """Unknown"""
+
+
+class HeifChannel(IntEnum):
+    """Internal libheif values, used in ``CtxEncode``."""
+
+    CHANNEL_Y = 0
+    """Monochrome or YCbCR"""
+    CHANNEL_CB = 1
+    """Only for YCbCR"""
+    CHANNEL_CR = 2
+    """Only for YCbCR"""
+    CHANNEL_R = 3
+    """RGB or RGBA"""
+    CHANNEL_G = 4
+    """RGB or RGBA"""
+    CHANNEL_B = 5
+    """RGB or RGBA"""
+    CHANNEL_ALPHA = 6
+    """Monochrome or RGBA"""
+    CHANNEL_INTERLEAVED = 10
+    """RGB or RGBA"""
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/heif.py b/.venv/lib/python3.12/site-packages/pillow_heif/heif.py
new file mode 100644
index 00000000..ddecd1b4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/heif.py
@@ -0,0 +1,648 @@
+"""Functions and classes for heif images to read and write."""
+
+from __future__ import annotations
+
+from copy import copy, deepcopy
+from io import SEEK_SET
+from typing import Any
+
+from PIL import Image
+
+from . import options
+from .constants import HeifCompressionFormat
+from .misc import (
+    MODE_INFO,
+    CtxEncode,
+    MimCImage,
+    _exif_from_pillow,
+    _get_bytes,
+    _get_heif_meta,
+    _get_orientation_for_encoder,
+    _get_primary_index,
+    _pil_to_supported_mode,
+    _retrieve_exif,
+    _retrieve_xmp,
+    _rotate_pil,
+    _xmp_from_pillow,
+    get_file_mimetype,
+    save_colorspace_chroma,
+    set_orientation,
+)
+
+try:
+    import _pillow_heif
+except ImportError as ex:
+    from ._deffered_error import DeferredError
+
+    _pillow_heif = DeferredError(ex)
+
+
+class BaseImage:
+    """Base class for :py:class:`HeifImage`, :py:class:`HeifDepthImage` and :py:class:`HeifAuxImage`."""
+
+    size: tuple[int, int]
+    """Width and height of the image."""
+
+    mode: str
+    """A string which defines the type and depth of a pixel in the image:
+    `Pillow Modes <https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes>`_
+
+    For currently supported modes by Pillow-Heif see :ref:`image-modes`."""
+
+    def __init__(self, c_image):
+        self.size, self.mode = c_image.size_mode
+        self._c_image = c_image
+        self._data = None
+
+    @property
+    def data(self):
+        """Decodes image and returns image data.
+
+        :returns: ``bytes`` of the decoded image.
+        """
+        self.load()
+        return self._data
+
+    @property
+    def stride(self) -> int:
+        """Stride of the image.
+
+        .. note:: from `0.10.0` version this value always will have width * sizeof pixel in default usage mode.
+
+        :returns: An Int value indicating the image stride after decoding.
+        """
+        self.load()
+        return self._c_image.stride
+
+    @property
+    def __array_interface__(self):
+        """Numpy array interface support."""
+        self.load()
+        width = int(self.stride / MODE_INFO[self.mode][0])
+        if MODE_INFO[self.mode][1] <= 8:
+            typestr = "|u1"
+        else:
+            width = int(width / 2)
+            typestr = "<u2"
+        shape: tuple[Any, ...] = (self.size[1], width)
+        if MODE_INFO[self.mode][0] > 1:
+            shape += (MODE_INFO[self.mode][0],)
+        return {"shape": shape, "typestr": typestr, "version": 3, "data": self.data}
+
+    def to_pillow(self) -> Image.Image:
+        """Helper method to create :external:py:class:`~PIL.Image.Image` class.
+
+        :returns: :external:py:class:`~PIL.Image.Image` class created from an image.
+        """
+        self.load()
+        return Image.frombytes(
+            self.mode,  # noqa
+            self.size,
+            self.data,
+            "raw",
+            self.mode,
+            self.stride,
+        )
+
+    def load(self) -> None:
+        """Method to decode image.
+
+        .. note:: In normal cases, you should not call this method directly,
+            when reading `data` or `stride` property of image will be loaded automatically.
+        """
+        if not self._data:
+            self._data = self._c_image.data
+            self.size, _ = self._c_image.size_mode
+
+
+class HeifDepthImage(BaseImage):
+    """Class representing the depth image associated with the :py:class:`~pillow_heif.HeifImage` class."""
+
+    def __init__(self, c_image):
+        super().__init__(c_image)
+        _metadata: dict = c_image.metadata
+        self.info = {
+            "metadata": _metadata,
+        }
+        save_colorspace_chroma(c_image, self.info)
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} {self.size[0]}x{self.size[1]} {self.mode}>"
+
+    def to_pillow(self) -> Image.Image:
+        """Helper method to create :external:py:class:`~PIL.Image.Image` class.
+
+        :returns: :external:py:class:`~PIL.Image.Image` class created from an image.
+        """
+        image = super().to_pillow()
+        image.info = self.info.copy()
+        return image
+
+
+class HeifAuxImage(BaseImage):
+    """Class representing the auxiliary image associated with the :py:class:`~pillow_heif.HeifImage` class."""
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} {self.size[0]}x{self.size[1]} {self.mode}>"
+
+
+class HeifImage(BaseImage):
+    """One image in a :py:class:`~pillow_heif.HeifFile` container."""
+
+    def __init__(self, c_image):
+        super().__init__(c_image)
+        _metadata: list[dict] = c_image.metadata
+        _exif = _retrieve_exif(_metadata)
+        _xmp = _retrieve_xmp(_metadata)
+        _thumbnails: list[int | None] = [i for i in c_image.thumbnails if i is not None] if options.THUMBNAILS else []
+        _depth_images: list[HeifDepthImage | None] = (
+            [HeifDepthImage(i) for i in c_image.depth_image_list if i is not None] if options.DEPTH_IMAGES else []
+        )
+        self.info = {
+            "primary": bool(c_image.primary),
+            "bit_depth": int(c_image.bit_depth),
+            "exif": _exif,
+            "metadata": _metadata,
+            "thumbnails": _thumbnails,
+            "depth_images": _depth_images,
+        }
+        if options.AUX_IMAGES:
+            _ctx_aux_info = {}
+            for aux_id in c_image.aux_image_ids:
+                aux_type = c_image.get_aux_type(aux_id)
+                if aux_type not in _ctx_aux_info:
+                    _ctx_aux_info[aux_type] = []
+                _ctx_aux_info[aux_type].append(aux_id)
+            self.info["aux"] = _ctx_aux_info
+        _heif_meta = _get_heif_meta(c_image)
+        if _xmp:
+            self.info["xmp"] = _xmp
+        if _heif_meta:
+            self.info["heif"] = _heif_meta
+        save_colorspace_chroma(c_image, self.info)
+        _color_profile: dict[str, Any] = c_image.color_profile
+        if _color_profile:
+            if _color_profile["type"] in ("rICC", "prof"):
+                self.info["icc_profile"] = _color_profile["data"]
+                self.info["icc_profile_type"] = _color_profile["type"]
+            else:
+                self.info["nclx_profile"] = _color_profile["data"]
+
+    def __repr__(self):
+        _bytes = f"{len(self.data)} bytes" if self._data or isinstance(self._c_image, MimCImage) else "no"
+        return (
+            f"<{self.__class__.__name__} {self.size[0]}x{self.size[1]} {self.mode} "
+            f"with {_bytes} image data and {len(self.info.get('thumbnails', []))} thumbnails>"
+        )
+
+    @property
+    def has_alpha(self) -> bool:
+        """``True`` for images with the ``alpha`` channel, ``False`` otherwise."""
+        return self.mode.split(sep=";")[0][-1] in ("A", "a")
+
+    @property
+    def premultiplied_alpha(self) -> bool:
+        """``True`` for images with ``premultiplied alpha`` channel, ``False`` otherwise."""
+        return bool(self.mode.split(sep=";")[0][-1] == "a")
+
+    @premultiplied_alpha.setter
+    def premultiplied_alpha(self, value: bool):
+        if self.has_alpha:
+            self.mode = self.mode.replace("A" if value else "a", "a" if value else "A")
+
+    def to_pillow(self) -> Image.Image:
+        """Helper method to create :external:py:class:`~PIL.Image.Image` class.
+
+        :returns: :external:py:class:`~PIL.Image.Image` class created from an image.
+        """
+        image = super().to_pillow()
+        image.info = self.info.copy()
+        image.info["original_orientation"] = set_orientation(image.info)
+        return image
+
+    def get_aux_image(self, aux_id: int) -> HeifAuxImage:
+        """Method to retrieve the auxiliary image at the given ID.
+
+        :returns: a :py:class:`~pillow_heif.HeifAuxImage` class instance.
+        """
+        aux_image = self._c_image.get_aux_image(aux_id)
+        return HeifAuxImage(aux_image)
+
+
+class HeifFile:
+    """Representation of the :py:class:`~pillow_heif.HeifImage` classes container.
+
+    To create :py:class:`~pillow_heif.HeifFile` object, use the appropriate factory functions.
+
+    * :py:func:`~pillow_heif.open_heif`
+    * :py:func:`~pillow_heif.read_heif`
+    * :py:func:`~pillow_heif.from_pillow`
+    * :py:func:`~pillow_heif.from_bytes`
+
+    Exceptions that can be raised when working with methods:
+        `ValueError`, `EOFError`, `SyntaxError`, `RuntimeError`, `OSError`
+    """
+
+    def __init__(self, fp=None, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs):
+        if hasattr(fp, "seek"):
+            fp.seek(0, SEEK_SET)
+
+        if fp is None:
+            images = []
+            mimetype = ""
+        else:
+            fp_bytes = _get_bytes(fp)
+            mimetype = get_file_mimetype(fp_bytes)
+            if mimetype.find("avif") != -1:
+                preferred_decoder = options.PREFERRED_DECODER.get("AVIF", "")
+            elif mimetype.find("heic") != -1 or mimetype.find("heif") != -1:
+                preferred_decoder = options.PREFERRED_DECODER.get("HEIF", "")
+            else:
+                preferred_decoder = ""
+            images = _pillow_heif.load_file(
+                fp_bytes,
+                options.DECODE_THREADS,
+                convert_hdr_to_8bit,
+                bgr_mode,
+                kwargs.get("remove_stride", True),
+                kwargs.get("hdr_to_16bit", True),
+                kwargs.get("reload_size", options.ALLOW_INCORRECT_HEADERS),
+                preferred_decoder,
+            )
+        self.mimetype = mimetype
+        self._images: list[HeifImage] = [HeifImage(i) for i in images if i is not None]
+        self.primary_index = 0
+        for index, _ in enumerate(self._images):
+            if _.info.get("primary", False):
+                self.primary_index = index
+
+    @property
+    def size(self):
+        """:attr:`~pillow_heif.HeifImage.size` property of the primary :class:`~pillow_heif.HeifImage`.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].size
+
+    @property
+    def mode(self):
+        """:attr:`~pillow_heif.HeifImage.mode` property of the primary :class:`~pillow_heif.HeifImage`.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].mode
+
+    @property
+    def has_alpha(self):
+        """:attr:`~pillow_heif.HeifImage.has_alpha` property of the primary :class:`~pillow_heif.HeifImage`.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].has_alpha
+
+    @property
+    def premultiplied_alpha(self):
+        """:attr:`~pillow_heif.HeifImage.premultiplied_alpha` property of the primary :class:`~pillow_heif.HeifImage`.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].premultiplied_alpha
+
+    @premultiplied_alpha.setter
+    def premultiplied_alpha(self, value: bool):
+        self._images[self.primary_index].premultiplied_alpha = value
+
+    @property
+    def data(self):
+        """:attr:`~pillow_heif.HeifImage.data` property of the primary :class:`~pillow_heif.HeifImage`.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].data
+
+    @property
+    def stride(self):
+        """:attr:`~pillow_heif.HeifImage.stride` property of the primary :class:`~pillow_heif.HeifImage`.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].stride
+
+    @property
+    def info(self):
+        """`info`` dict of the primary :class:`~pillow_heif.HeifImage` in the container.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].info
+
+    def to_pillow(self) -> Image.Image:
+        """Helper method to create Pillow :external:py:class:`~PIL.Image.Image`.
+
+        :returns: :external:py:class:`~PIL.Image.Image` class created from the primary image.
+        """
+        return self._images[self.primary_index].to_pillow()
+
+    def save(self, fp, **kwargs) -> None:
+        """Saves image(s) under the given fp.
+
+        Keyword options can be used to provide additional instructions to the writer.
+        If a writer does not recognize an option, it is silently ignored.
+
+        Supported options:
+            ``save_all`` - boolean. Should all images from ``HeiFile`` be saved?
+            (default = ``True``)
+
+            ``append_images`` - do the same as in Pillow. Accepts the list of ``HeifImage``
+
+            .. note:: Appended images always will have ``info["primary"]=False``
+
+            ``quality`` - see :py:attr:`~pillow_heif.options.QUALITY`
+
+            ``enc_params`` - dictionary with key:value to pass to :ref:`x265 <hevc-encoder>` encoder.
+
+            ``exif`` - override primary image's EXIF with specified.
+            Accepts ``None``, ``bytes`` or ``PIL.Image.Exif`` class.
+
+            ``xmp`` - override primary image's XMP with specified. Accepts ``None`` or ``bytes``.
+
+            ``primary_index`` - ignore ``info["primary"]`` and set `PrimaryImage` by index.
+
+            ``chroma`` - custom subsampling value. Possible values: ``444``, ``422`` or ``420`` (``x265`` default).
+
+            ``subsampling`` - synonym for *chroma*. Format is string, compatible with Pillow: ``x:x:x``, e.g. '4:4:4'.
+
+            ``format`` - string with encoder format name. Possible values: ``HEIF`` (default) or ``AVIF``.
+
+            ``save_nclx_profile`` - boolean, see :py:attr:`~pillow_heif.options.SAVE_NCLX_PROFILE`
+
+            ``matrix_coefficients`` - int, nclx profile: color conversion matrix coefficients, default=6 (see h.273)
+
+            ``color_primaries`` - int, nclx profile: color primaries (see h.273)
+
+            ``transfer_characteristic`` - int, nclx profile: transfer characteristics (see h.273)
+
+            ``full_range_flag`` - nclx profile: full range flag, default: 1
+
+        :param fp: A filename (string), pathlib.Path object or an object with `write` method.
+        """
+        _encode_images(self._images, fp, **kwargs)
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} with {len(self)} images: {[str(i) for i in self]}>"
+
+    def __len__(self):
+        return len(self._images)
+
+    def __iter__(self):
+        yield from self._images
+
+    def __getitem__(self, index):
+        if index < 0 or index >= len(self._images):
+            raise IndexError(f"invalid image index: {index}")
+        return self._images[index]
+
+    def __delitem__(self, key):
+        if key < 0 or key >= len(self._images):
+            raise IndexError(f"invalid image index: {key}")
+        del self._images[key]
+
+    def add_frombytes(self, mode: str, size: tuple[int, int], data, **kwargs):
+        """Adds image from bytes to container.
+
+        .. note:: Supports ``stride`` value if needed.
+
+        :param mode: see :ref:`image-modes`.
+        :param size: tuple with ``width`` and ``height`` of image.
+        :param data: bytes object with raw image data.
+
+        :returns: :py:class:`~pillow_heif.HeifImage` added object.
+        """
+        added_image = HeifImage(MimCImage(mode, size, data, **kwargs))
+        self._images.append(added_image)
+        return added_image
+
+    def add_from_heif(self, image: HeifImage) -> HeifImage:
+        """Add image to the container.
+
+        :param image: :py:class:`~pillow_heif.HeifImage` class to add from.
+
+        :returns: :py:class:`~pillow_heif.HeifImage` added object.
+        """
+        image.load()
+        added_image = self.add_frombytes(
+            image.mode,
+            image.size,
+            image.data,
+            stride=image.stride,
+        )
+        added_image.info = deepcopy(image.info)
+        added_image.info.pop("primary", None)
+        return added_image
+
+    def add_from_pillow(self, image: Image.Image) -> HeifImage:
+        """Add image to the container.
+
+        :param image: Pillow :external:py:class:`~PIL.Image.Image` class to add from.
+
+        :returns: :py:class:`~pillow_heif.HeifImage` added object.
+        """
+        if image.size[0] <= 0 or image.size[1] <= 0:
+            raise ValueError("Empty images are not supported.")
+        _info = image.info.copy()
+        _info["exif"] = _exif_from_pillow(image)
+        _xmp = _xmp_from_pillow(image)
+        if _xmp:
+            _info["xmp"] = _xmp
+        original_orientation = set_orientation(_info)
+        _img = _pil_to_supported_mode(image)
+        if original_orientation is not None and original_orientation != 1:
+            _img = _rotate_pil(_img, original_orientation)
+        _img.load()
+        added_image = self.add_frombytes(
+            _img.mode,
+            _img.size,
+            _img.tobytes(),
+        )
+        for key in ["bit_depth", "thumbnails", "icc_profile", "icc_profile_type"]:
+            if key in image.info:
+                added_image.info[key] = image.info[key]
+        for key in ["nclx_profile", "metadata"]:
+            if key in image.info:
+                added_image.info[key] = deepcopy(image.info[key])
+        added_image.info["exif"] = _exif_from_pillow(image)
+        _xmp = _xmp_from_pillow(image)
+        if _xmp:
+            added_image.info["xmp"] = _xmp
+        return added_image
+
+    @property
+    def __array_interface__(self):
+        """Returns the primary image as a numpy array."""
+        return self._images[self.primary_index].__array_interface__
+
+    def __getstate__(self):
+        im_desc = []
+        for im in self._images:
+            im_data = bytes(im.data)
+            im_desc.append([im.mode, im.size, im_data, im.info])
+        return [self.primary_index, self.mimetype, im_desc]
+
+    def __setstate__(self, state):
+        self.__init__()
+        self.primary_index, self.mimetype, images = state
+        for im_desc in images:
+            im_mode, im_size, im_data, im_info = im_desc
+            added_image = self.add_frombytes(im_mode, im_size, im_data)
+            added_image.info = im_info
+
+    def __copy(self):
+        _im_copy = HeifFile()
+        _im_copy._images = copy(self._images)  # pylint: disable=protected-access
+        _im_copy.mimetype = self.mimetype
+        _im_copy.primary_index = self.primary_index
+        return _im_copy
+
+    def get_aux_image(self, aux_id):
+        """`get_aux_image`` method of the primary :class:`~pillow_heif.HeifImage` in the container.
+
+        :exception IndexError: If there are no images.
+        """
+        return self._images[self.primary_index].get_aux_image(aux_id)
+
+    __copy__ = __copy
+
+
+def is_supported(fp) -> bool:
+    """Checks if the given `fp` object contains a supported file type.
+
+    :param fp: A filename (string), pathlib.Path object or a file object.
+        The file object must implement ``file.read``, ``file.seek``, and ``file.tell`` methods,
+        and be opened in binary mode.
+
+    :returns: A boolean indicating if the object can be opened.
+    """
+    __data = _get_bytes(fp, 12)
+    if __data[4:8] != b"ftyp":
+        return False
+    return get_file_mimetype(__data) != ""
+
+
+def open_heif(fp, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs) -> HeifFile:
+    """Opens the given HEIF(AVIF) image file.
+
+    :param fp: See parameter ``fp`` in :func:`is_supported`
+    :param convert_hdr_to_8bit: Boolean indicating should 10 bit or 12 bit images
+        be converted to 8-bit images during decoding. Otherwise, they will open in 16-bit mode.
+        ``Does not affect "monochrome" or "depth images".``
+    :param bgr_mode: Boolean indicating should be `RGB(A)` images be opened in `BGR(A)` mode.
+    :param kwargs: **hdr_to_16bit** a boolean value indicating that 10/12-bit image data
+        should be converted to 16-bit mode during decoding. `Has lower priority than convert_hdr_to_8bit`!
+        Default = **True**
+
+    :returns: :py:class:`~pillow_heif.HeifFile` object.
+    :exception ValueError: invalid input data.
+    :exception EOFError: corrupted image data.
+    :exception SyntaxError: unsupported feature.
+    :exception RuntimeError: some other error.
+    :exception OSError: out of memory.
+    """
+    return HeifFile(fp, convert_hdr_to_8bit, bgr_mode, **kwargs)
+
+
+def read_heif(fp, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs) -> HeifFile:
+    """Opens the given HEIF(AVIF) image file and decodes all images.
+
+    .. note:: In most cases it is better to call :py:meth:`~pillow_heif.open_heif`, and
+        let images decoded automatically only when needed.
+
+    :param fp: See parameter ``fp`` in :func:`is_supported`
+    :param convert_hdr_to_8bit: Boolean indicating should 10 bit or 12 bit images
+        be converted to 8-bit images during decoding. Otherwise, they will open in 16-bit mode.
+        ``Does not affect "monochrome" or "depth images".``
+    :param bgr_mode: Boolean indicating should be `RGB(A)` images be opened in `BGR(A)` mode.
+    :param kwargs: **hdr_to_16bit** a boolean value indicating that 10/12-bit image data
+        should be converted to 16-bit mode during decoding. `Has lower priority than convert_hdr_to_8bit`!
+        Default = **True**
+
+    :returns: :py:class:`~pillow_heif.HeifFile` object.
+    :exception ValueError: invalid input data.
+    :exception EOFError: corrupted image data.
+    :exception SyntaxError: unsupported feature.
+    :exception RuntimeError: some other error.
+    :exception OSError: out of memory.
+    """
+    ret = HeifFile(fp, convert_hdr_to_8bit, bgr_mode, reload_size=True, **kwargs)
+    for img in ret:
+        img.load()
+    return ret
+
+
+def encode(mode: str, size: tuple[int, int], data, fp, **kwargs) -> None:
+    """Encodes data in a ``fp``.
+
+    :param mode: `BGR(A);16`, `RGB(A);16`, LA;16`, `L;16`, `I;16L`, `BGR(A)`, `RGB(A)`, `LA`, `L`
+    :param size: tuple with ``width`` and ``height`` of an image.
+    :param data: bytes object with raw image data.
+    :param fp: A filename (string), pathlib.Path object or an object with ``write`` method.
+    """
+    _encode_images([HeifImage(MimCImage(mode, size, data, **kwargs))], fp, **kwargs)
+
+
+def _encode_images(images: list[HeifImage], fp, **kwargs) -> None:
+    compression = kwargs.get("format", "HEIF")
+    compression_format = HeifCompressionFormat.AV1 if compression == "AVIF" else HeifCompressionFormat.HEVC
+    if not _pillow_heif.get_lib_info()[compression]:
+        raise RuntimeError(f"No {compression} encoder found.")
+    images_to_save: list[HeifImage] = images + kwargs.get("append_images", [])
+    if not kwargs.get("save_all", True):
+        images_to_save = images_to_save[:1]
+    if not images_to_save:
+        raise ValueError("Cannot write file with no images as HEIF.")
+    primary_index = _get_primary_index(images_to_save, kwargs.get("primary_index"))
+    ctx_write = CtxEncode(compression_format, **kwargs)
+    for i, img in enumerate(images_to_save):
+        img.load()
+        _info = img.info.copy()
+        _info["primary"] = False
+        if i == primary_index:
+            _info.update(**kwargs)
+            _info["primary"] = True
+        _info.pop("stride", 0)
+        ctx_write.add_image(
+            img.size,
+            img.mode,
+            img.data,
+            image_orientation=_get_orientation_for_encoder(_info),
+            **_info,
+            stride=img.stride,
+        )
+    ctx_write.save(fp)
+
+
+def from_pillow(pil_image: Image.Image) -> HeifFile:
+    """Creates :py:class:`~pillow_heif.HeifFile` from a Pillow Image.
+
+    :param pil_image: Pillow :external:py:class:`~PIL.Image.Image` class.
+
+    :returns: New :py:class:`~pillow_heif.HeifFile` object.
+    """
+    _ = HeifFile()
+    _.add_from_pillow(pil_image)
+    return _
+
+
+def from_bytes(mode: str, size: tuple[int, int], data, **kwargs) -> HeifFile:
+    """Creates :py:class:`~pillow_heif.HeifFile` from bytes.
+
+    .. note:: Supports ``stride`` value if needed.
+
+    :param mode: see :ref:`image-modes`.
+    :param size: tuple with ``width`` and ``height`` of an image.
+    :param data: bytes object with raw image data.
+
+    :returns: New :py:class:`~pillow_heif.HeifFile` object.
+    """
+    _ = HeifFile()
+    _.add_frombytes(mode, size, data, **kwargs)
+    return _
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/misc.py b/.venv/lib/python3.12/site-packages/pillow_heif/misc.py
new file mode 100644
index 00000000..08c01e73
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/misc.py
@@ -0,0 +1,501 @@
+"""Different miscellaneous helper functions.
+
+Mostly for internal use, so prototypes can change between versions.
+"""
+
+from __future__ import annotations
+
+import builtins
+import re
+from dataclasses import dataclass
+from enum import IntEnum
+from math import ceil
+from pathlib import Path
+from struct import pack, unpack
+
+from PIL import Image
+
+from . import options
+from .constants import HeifChannel, HeifChroma, HeifColorspace, HeifCompressionFormat
+
+try:
+    import _pillow_heif
+except ImportError as ex:
+    from ._deffered_error import DeferredError
+
+    _pillow_heif = DeferredError(ex)
+
+
+MODE_INFO = {
+    # name -> [channels, bits per pixel channel, colorspace, chroma]
+    "BGRA;16": (4, 16, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "BGRa;16": (4, 16, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "BGR;16": (3, 16, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBB_LE),
+    "RGBA;16": (4, 16, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "RGBa;16": (4, 16, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "RGB;16": (3, 16, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBB_LE),
+    "LA;16": (2, 16, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "La;16": (2, 16, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "L;16": (1, 16, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "I;16": (1, 16, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "I;16L": (1, 16, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "BGRA;12": (4, 12, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "BGRa;12": (4, 12, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "BGR;12": (3, 12, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBB_LE),
+    "RGBA;12": (4, 12, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "RGBa;12": (4, 12, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "RGB;12": (3, 12, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBB_LE),
+    "LA;12": (2, 12, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "La;12": (2, 12, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "L;12": (1, 12, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "I;12": (1, 12, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "I;12L": (1, 12, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "BGRA;10": (4, 10, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "BGRa;10": (4, 10, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "BGR;10": (3, 10, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBB_LE),
+    "RGBA;10": (4, 10, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "RGBa;10": (4, 10, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBBAA_LE),
+    "RGB;10": (3, 10, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RRGGBB_LE),
+    "LA;10": (2, 10, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "La;10": (2, 10, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "L;10": (1, 10, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "I;10": (1, 10, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "I;10L": (1, 10, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "RGBA": (4, 8, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RGBA),
+    "RGBa": (4, 8, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RGBA),
+    "RGB": (3, 8, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RGB),
+    "BGRA": (4, 8, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RGBA),
+    "BGRa": (4, 8, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RGBA),
+    "BGR": (3, 8, HeifColorspace.RGB, HeifChroma.INTERLEAVED_RGB),
+    "LA": (2, 8, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "La": (2, 8, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "L": (1, 8, HeifColorspace.MONOCHROME, HeifChroma.MONOCHROME),
+    "YCbCr": (3, 8, HeifColorspace.YCBCR, HeifChroma.CHROMA_444),
+}
+
+SUBSAMPLING_CHROMA_MAP = {
+    "4:4:4": 444,
+    "4:2:2": 422,
+    "4:2:0": 420,
+}
+
+LIBHEIF_CHROMA_MAP = {
+    1: 420,
+    2: 422,
+    3: 444,
+}
+
+
+def save_colorspace_chroma(c_image, info: dict) -> None:
+    """Converts `chroma` value from `c_image` to useful values and stores them in ``info`` dict."""
+    # Saving of `colorspace` was removed, as currently is not clear where to use that value.
+    chroma = LIBHEIF_CHROMA_MAP.get(c_image.chroma, None)
+    if chroma is not None:
+        info["chroma"] = chroma
+
+
+def set_orientation(info: dict) -> int | None:
+    """Reset orientation in ``EXIF`` to ``1`` if any orientation present.
+
+    Removes ``XMP`` orientation tag if it is present.
+    In Pillow plugin mode, it is called automatically for images.
+    When ``pillow_heif`` used in ``standalone`` mode, if you wish, you can call it manually.
+
+    .. note:: If there is no orientation tag, this function will not add it and do nothing.
+
+        If both XMP and EXIF orientation tags are present, EXIF orientation tag will be returned,
+        but both tags will be removed.
+
+    :param info: `info` dictionary from :external:py:class:`~PIL.Image.Image` or :py:class:`~pillow_heif.HeifImage`.
+    :returns: Original orientation or None if it is absent.
+    """
+    return _get_orientation(info, True)
+
+
+def _get_orientation_for_encoder(info: dict) -> int:
+    image_orientation = _get_orientation(info, False)
+    return 1 if image_orientation is None else image_orientation
+
+
+def _get_orientation_xmp(info: dict, exif_orientation: int | None, reset: bool = False) -> int | None:
+    xmp_orientation = 1
+    if info.get("xmp"):
+        xmp_data = info["xmp"].rsplit(b"\x00", 1)
+        if xmp_data[0]:
+            decoded_xmp_data = None
+            for encoding in ("utf-8", "latin1"):
+                try:
+                    decoded_xmp_data = xmp_data[0].decode(encoding)
+                    break
+                except Exception:  # noqa # pylint: disable=broad-except
+                    pass
+            if decoded_xmp_data:
+                match = re.search(r'tiff:Orientation(="|>)([0-9])', decoded_xmp_data)
+                if match:
+                    xmp_orientation = int(match[2])
+                    if reset:
+                        decoded_xmp_data = re.sub(r'tiff:Orientation="([0-9])"', "", decoded_xmp_data)
+                        decoded_xmp_data = re.sub(r"<tiff:Orientation>([0-9])</tiff:Orientation>", "", decoded_xmp_data)
+                # should encode in "utf-8" anyway, as `defusedxml` do not work with `latin1` encoding.
+                if encoding != "utf-8" or xmp_orientation != 1:
+                    info["xmp"] = b"".join([decoded_xmp_data.encode("utf-8"), b"\x00" if len(xmp_data) > 1 else b""])
+    return xmp_orientation if exif_orientation is None and xmp_orientation != 1 else None
+
+
+def _get_orientation(info: dict, reset: bool = False) -> int | None:
+    original_orientation = None
+    if info.get("exif"):
+        try:
+            tif_tag = info["exif"]
+            skipped_exif00 = False
+            if tif_tag.startswith(b"Exif\x00\x00"):
+                skipped_exif00 = True
+                tif_tag = tif_tag[6:]
+            endian_mark = "<" if tif_tag[0:2] == b"\x49\x49" else ">"
+            pointer = unpack(endian_mark + "L", tif_tag[4:8])[0]
+            tag_count = unpack(endian_mark + "H", tif_tag[pointer : pointer + 2])[0]
+            offset = pointer + 2
+            for tag_n in range(tag_count):
+                pointer = offset + 12 * tag_n
+                if unpack(endian_mark + "H", tif_tag[pointer : pointer + 2])[0] != 274:
+                    continue
+                value = tif_tag[pointer + 8 : pointer + 12]
+                _original_orientation = unpack(endian_mark + "H", value[0:2])[0]
+                if _original_orientation != 1:
+                    original_orientation = _original_orientation
+                    if not reset:
+                        break
+                    p_value = pointer + 8
+                    if skipped_exif00:
+                        p_value += 6
+                    new_orientation = pack(endian_mark + "H", 1)
+                    info["exif"] = info["exif"][:p_value] + new_orientation + info["exif"][p_value + 2 :]
+                    break
+        except Exception:  # noqa # pylint: disable=broad-except
+            pass
+    xmp_orientation = _get_orientation_xmp(info, original_orientation, reset=reset)
+    return xmp_orientation or original_orientation
+
+
+def get_file_mimetype(fp) -> str:
+    """Gets the MIME type of the HEIF(or AVIF) object.
+
+    :param fp: A filename (string), pathlib.Path object, file object or bytes.
+        The file object must implement ``file.read``, ``file.seek`` and ``file.tell`` methods,
+        and be opened in binary mode.
+    :returns: "image/heic", "image/heif", "image/heic-sequence", "image/heif-sequence",
+        "image/avif", "image/avif-sequence" or "".
+    """
+    heif_brand = _get_bytes(fp, 12)[8:]
+    if heif_brand:
+        if heif_brand == b"avif":
+            return "image/avif"
+        if heif_brand == b"avis":
+            return "image/avif-sequence"
+        if heif_brand in (b"heic", b"heix", b"heim", b"heis"):
+            return "image/heic"
+        if heif_brand in (b"hevc", b"hevx", b"hevm", b"hevs"):
+            return "image/heic-sequence"
+        if heif_brand == b"mif1":
+            return "image/heif"
+        if heif_brand == b"msf1":
+            return "image/heif-sequence"
+    return ""
+
+
+def _get_bytes(fp, length=None) -> bytes:
+    if isinstance(fp, (str, Path)):
+        with builtins.open(fp, "rb") as file:
+            return file.read(length or -1)
+    if hasattr(fp, "read"):
+        offset = fp.tell() if hasattr(fp, "tell") else None
+        result = fp.read(length or -1)
+        if offset is not None and hasattr(fp, "seek"):
+            fp.seek(offset)
+        return result
+    return bytes(fp)[:length]
+
+
+def _retrieve_exif(metadata: list[dict]) -> bytes | None:
+    _result = None
+    _purge = []
+    for i, md_block in enumerate(metadata):
+        if md_block["type"] == "Exif":
+            _purge.append(i)
+            skip_size = int.from_bytes(md_block["data"][:4], byteorder="big", signed=False)
+            skip_size += 4  # skip 4 bytes with offset
+            if len(md_block["data"]) - skip_size <= 4:  # bad EXIF data, skip first 4 bytes
+                skip_size = 4
+            elif skip_size >= 6 and md_block["data"][skip_size - 6 : skip_size] == b"Exif\x00\x00":
+                skip_size -= 6
+            _data = md_block["data"][skip_size:]
+            if not _result and _data:
+                _result = _data
+    for i in reversed(_purge):
+        del metadata[i]
+    return _result
+
+
+def _retrieve_xmp(metadata: list[dict]) -> bytes | None:
+    _result = None
+    _purge = []
+    for i, md_block in enumerate(metadata):
+        if md_block["type"] == "mime":
+            _purge.append(i)
+            if not _result:
+                _result = md_block["data"]
+    for i in reversed(_purge):
+        del metadata[i]
+    return _result
+
+
+def _exif_from_pillow(img: Image.Image) -> bytes | None:
+    if "exif" in img.info:
+        return img.info["exif"]
+    if hasattr(img, "getexif"):  # noqa
+        exif = img.getexif()
+        if exif:
+            return exif.tobytes()
+    return None
+
+
+def _xmp_from_pillow(img: Image.Image) -> bytes | None:
+    _xmp = None
+    if "xmp" in img.info:
+        _xmp = img.info["xmp"]
+    elif "XML:com.adobe.xmp" in img.info:  # PNG
+        _xmp = img.info["XML:com.adobe.xmp"]
+    elif hasattr(img, "tag_v2"):  # TIFF
+        if 700 in img.tag_v2:
+            _xmp = img.tag_v2[700]
+    elif hasattr(img, "applist"):  # JPEG
+        for segment, content in img.applist:
+            if segment == "APP1":
+                marker, xmp_tags = content.rsplit(b"\x00", 1)
+                if marker == b"http://ns.adobe.com/xap/1.0/":
+                    _xmp = xmp_tags
+                    break
+    if isinstance(_xmp, str):
+        _xmp = _xmp.encode("utf-8")
+    return _xmp
+
+
+def _pil_to_supported_mode(img: Image.Image) -> Image.Image:
+    # We support "YCbCr" for encoding in Pillow plugin mode and do not call this function.
+    if img.mode == "P":
+        mode = "RGBA" if img.info.get("transparency", None) is not None else "RGB"
+        img = img.convert(mode=mode)
+    elif img.mode == "I":
+        img = img.convert(mode="I;16L")
+    elif img.mode == "1":
+        img = img.convert(mode="L")
+    elif img.mode == "CMYK":
+        img = img.convert(mode="RGBA")
+    elif img.mode == "YCbCr":
+        img = img.convert(mode="RGB")
+    return img
+
+
+class Transpose(IntEnum):
+    """Temporary workaround till we support old Pillows, remove this when a minimum Pillow version will have this."""
+
+    FLIP_LEFT_RIGHT = 0
+    FLIP_TOP_BOTTOM = 1
+    ROTATE_90 = 2
+    ROTATE_180 = 3
+    ROTATE_270 = 4
+    TRANSPOSE = 5
+    TRANSVERSE = 6
+
+
+def _rotate_pil(img: Image.Image, orientation: int) -> Image.Image:
+    # Probably need create issue in Pillow to add support
+    # for info["xmp"] or `getxmp()` for ImageOps.exif_transpose and remove this func.
+    method = {
+        2: Transpose.FLIP_LEFT_RIGHT,
+        3: Transpose.ROTATE_180,
+        4: Transpose.FLIP_TOP_BOTTOM,
+        5: Transpose.TRANSPOSE,
+        6: Transpose.ROTATE_270,
+        7: Transpose.TRANSVERSE,
+        8: Transpose.ROTATE_90,
+    }.get(orientation)
+    if method is not None:
+        return img.transpose(method)
+    return img
+
+
+def _get_primary_index(some_iterator, primary_index: int | None) -> int:
+    primary_attrs = [_.info.get("primary", False) for _ in some_iterator]
+    if primary_index is None:
+        primary_index = 0
+        for i, v in enumerate(primary_attrs):
+            if v:
+                primary_index = i
+    elif primary_index == -1 or primary_index >= len(primary_attrs):
+        primary_index = len(primary_attrs) - 1
+    return primary_index
+
+
+def __get_camera_intrinsic_matrix(values: tuple | None):
+    return (
+        {
+            "focal_length_x": values[0],
+            "focal_length_y": values[1],
+            "principal_point_x": values[2],
+            "principal_point_y": values[3],
+            "skew": values[4],
+        }
+        if values
+        else None
+    )
+
+
+def _get_heif_meta(c_image) -> dict:
+    r = {}
+    _camera_intrinsic_matrix = __get_camera_intrinsic_matrix(c_image.camera_intrinsic_matrix)
+    if _camera_intrinsic_matrix:
+        r["camera_intrinsic_matrix"] = _camera_intrinsic_matrix
+    _camera_extrinsic_matrix_rot = c_image.camera_extrinsic_matrix_rot
+    if _camera_extrinsic_matrix_rot:
+        r["camera_extrinsic_matrix_rot"] = _camera_extrinsic_matrix_rot
+    return r
+
+
+class CtxEncode:
+    """Encoder bindings from python to python C module."""
+
+    def __init__(self, compression_format: HeifCompressionFormat, **kwargs):
+        quality = kwargs.get("quality", options.QUALITY)
+        self.ctx_write = _pillow_heif.CtxWrite(
+            compression_format,
+            -2 if quality is None else quality,
+            options.PREFERRED_ENCODER.get("HEIF" if compression_format == HeifCompressionFormat.HEVC else "AVIF", ""),
+        )
+        enc_params = kwargs.get("enc_params", {})
+        chroma = None
+        if "subsampling" in kwargs:
+            chroma = SUBSAMPLING_CHROMA_MAP.get(kwargs["subsampling"], None)
+        if chroma is None:
+            chroma = kwargs.get("chroma")
+        if chroma:
+            enc_params["chroma"] = chroma
+        for key, value in enc_params.items():
+            _value = value if isinstance(value, str) else str(value)
+            self.ctx_write.set_parameter(key, _value)
+
+    def add_image(self, size: tuple[int, int], mode: str, data, **kwargs) -> None:
+        """Adds image to the encoder."""
+        if size[0] <= 0 or size[1] <= 0:
+            raise ValueError("Empty images are not supported.")
+        bit_depth_in = MODE_INFO[mode][1]
+        bit_depth_out = 8 if bit_depth_in == 8 else kwargs.get("bit_depth", 16)
+        if bit_depth_out == 16:
+            bit_depth_out = 12 if options.SAVE_HDR_TO_12_BIT else 10
+        premultiplied_alpha = int(mode.split(sep=";")[0][-1] == "a")
+        # creating image
+        im_out = self.ctx_write.create_image(size, MODE_INFO[mode][2], MODE_INFO[mode][3], premultiplied_alpha)
+        # image data
+        if MODE_INFO[mode][0] == 1:
+            im_out.add_plane_l(size, bit_depth_out, bit_depth_in, data, kwargs.get("stride", 0), HeifChannel.CHANNEL_Y)
+        elif MODE_INFO[mode][0] == 2:
+            im_out.add_plane_la(size, bit_depth_out, bit_depth_in, data, kwargs.get("stride", 0))
+        else:
+            im_out.add_plane(size, bit_depth_out, bit_depth_in, data, mode.find("BGR") != -1, kwargs.get("stride", 0))
+        self._finish_add_image(im_out, size, **kwargs)
+
+    def add_image_ycbcr(self, img: Image.Image, **kwargs) -> None:
+        """Adds image in `YCbCR` mode to the encoder."""
+        # creating image
+        im_out = self.ctx_write.create_image(img.size, MODE_INFO[img.mode][2], MODE_INFO[img.mode][3], 0)
+        # image data
+        for i in (HeifChannel.CHANNEL_Y, HeifChannel.CHANNEL_CB, HeifChannel.CHANNEL_CR):
+            im_out.add_plane_l(img.size, 8, 8, bytes(img.getdata(i)), kwargs.get("stride", 0), i)
+        self._finish_add_image(im_out, img.size, **kwargs)
+
+    def _finish_add_image(self, im_out, size: tuple[int, int], **kwargs):
+        # set ICC color profile
+        __icc_profile = kwargs.get("icc_profile")
+        if __icc_profile is not None:
+            im_out.set_icc_profile(kwargs.get("icc_profile_type", "prof"), __icc_profile)
+        # set NCLX color profile
+        if kwargs.get("nclx_profile"):
+            im_out.set_nclx_profile(
+                *[
+                    kwargs["nclx_profile"][i]
+                    for i in ("color_primaries", "transfer_characteristics", "matrix_coefficients", "full_range_flag")
+                ]
+            )
+        # encode
+        image_orientation = kwargs.get("image_orientation", 1)
+        im_out.encode(
+            self.ctx_write,
+            kwargs.get("primary", False),
+            kwargs.get("save_nclx_profile", options.SAVE_NCLX_PROFILE),
+            kwargs.get("color_primaries", -1),
+            kwargs.get("transfer_characteristics", -1),
+            kwargs.get("matrix_coefficients", -1),
+            kwargs.get("full_range_flag", -1),
+            image_orientation,
+        )
+        # adding metadata
+        exif = kwargs.get("exif")
+        if exif is not None:
+            if isinstance(exif, Image.Exif):
+                exif = exif.tobytes()
+            im_out.set_exif(self.ctx_write, exif)
+        xmp = kwargs.get("xmp")
+        if xmp is not None:
+            im_out.set_xmp(self.ctx_write, xmp)
+        for metadata in kwargs.get("metadata", []):
+            im_out.set_metadata(self.ctx_write, metadata["type"], metadata["content_type"], metadata["data"])
+        # adding thumbnails
+        for thumb_box in kwargs.get("thumbnails", []):
+            if max(size) > thumb_box > 3:
+                im_out.encode_thumbnail(self.ctx_write, thumb_box, image_orientation)
+
+    def save(self, fp) -> None:
+        """Ask encoder to produce output based on previously added images."""
+        data = self.ctx_write.finalize()
+        if isinstance(fp, (str, Path)):
+            Path(fp).write_bytes(data)
+        elif hasattr(fp, "write"):
+            fp.write(data)
+        else:
+            raise TypeError("`fp` must be a path to file or an object with `write` method.")
+
+
+@dataclass
+class MimCImage:
+    """Mimicry of the HeifImage class."""
+
+    def __init__(self, mode: str, size: tuple[int, int], data: bytes, **kwargs):
+        self.mode = mode
+        self.size = size
+        self.stride: int = kwargs.get("stride", size[0] * MODE_INFO[mode][0] * ceil(MODE_INFO[mode][1] / 8))
+        self.data = data
+        self.metadata: list[dict] = []
+        self.color_profile = None
+        self.thumbnails: list[int] = []
+        self.depth_image_list: list = []
+        self.aux_image_ids: list[int] = []
+        self.primary = False
+        self.chroma = HeifChroma.UNDEFINED.value
+        self.colorspace = HeifColorspace.UNDEFINED.value
+        self.camera_intrinsic_matrix = None
+        self.camera_extrinsic_matrix_rot = None
+
+    @property
+    def size_mode(self):
+        """Mimicry of c_image property."""
+        return self.size, self.mode
+
+    @property
+    def bit_depth(self) -> int:
+        """Return bit-depth based on image mode."""
+        return MODE_INFO[self.mode][1]
+
+
+def load_libheif_plugin(plugin_path: str | Path) -> None:
+    """Load specified LibHeif plugin."""
+    _pillow_heif.load_plugin(plugin_path)
diff --git a/.venv/lib/python3.12/site-packages/pillow_heif/options.py b/.venv/lib/python3.12/site-packages/pillow_heif/options.py
new file mode 100644
index 00000000..0bbd90b5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pillow_heif/options.py
@@ -0,0 +1,91 @@
+"""Options to change pillow_heif's runtime behavior."""
+
+DECODE_THREADS = 4
+"""Maximum number of threads to use for decoding images(when it is possible)
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(decode_threads=8)`"""
+
+
+THUMBNAILS = True
+"""Option to enable/disable thumbnail support
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(thumbnails=False)`"""
+
+
+DEPTH_IMAGES = True
+"""Option to enable/disable depth image support
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(depth_images=False)`"""
+
+
+AUX_IMAGES = True
+"""Option to enable/disable auxiliary image support
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(aux_images=False)`"""
+
+
+QUALITY = None
+"""Default encoding quality
+
+.. note:: Quality specified during calling ``save`` has higher priority then this.
+
+Possible values: None, -1, range(0-100).
+Set -1 for lossless quality or from 0 to 100, where 0 is lowest and 100 is highest.
+
+.. note:: Also for lossless encoding you should specify ``chroma=444`` during save.
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(quality=-1)`"""
+
+
+SAVE_HDR_TO_12_BIT = False
+"""Should 16 bit images be saved to 12 bit instead of 10 bit``
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(save_to_12bit=True)`"""
+
+
+ALLOW_INCORRECT_HEADERS = False
+"""Can or not the ``size`` of image in header differ from decoded one.
+
+.. note:: If enabled, ``Image.size`` can change after loading for images where it is invalid in header.
+
+To learn more read: `here <https://github.com/strukturag/libheif/issues/784>`_
+
+When use pillow_heif as a plugin you can set it with: `register_*_opener(allow_incorrect_headers=True)`"""
+
+
+SAVE_NCLX_PROFILE = True
+"""Should be ``nclx`` profile saved or not.
+
+Default for all previous versions(pillow_heif<0.14.0) was NOT TO save `nclx` profile,
+due to an old bug in Apple software refusing to open images with `nclx` profiles.
+Apple has already fixed this and there is no longer a need to not save the default profile.
+
+.. note:: `save_nclx_profile` specified during calling ``save`` has higher priority than this.
+
+When use pillow_heif as a plugin you can unset it with: `register_*_opener(save_nclx_profile=False)`"""
+
+
+PREFERRED_ENCODER = {
+    "AVIF": "",
+    "HEIF": "",
+}
+"""Use the specified encoder for format.
+
+You can get the available encoders IDs using ``libheif_info()`` function.
+
+When use pillow_heif as a plugin you can set this option with ``preferred_encoder`` key.
+
+.. note:: If the specified encoder is missing, the option will be ignored."""
+
+
+PREFERRED_DECODER = {
+    "AVIF": "",
+    "HEIF": "",
+}
+"""Use the specified decoder for format.
+
+You can get the available decoders IDs using ``libheif_info()`` function.
+
+When use pillow_heif as a plugin you can set this option with ``preferred_decoder`` key.
+
+.. note:: If the specified decoder is missing, the option will be ignored."""