ai-content-maker/.venv/Lib/site-packages/imageio/plugins/freeimage.py

405 lines
14 KiB
Python
Raw Normal View History

2024-05-11 23:00:43 +03:00
# -*- coding: utf-8 -*-
# imageio is distributed under the terms of the (new) BSD License.
"""Read/Write images using FreeImage.
Backend Library: `FreeImage <https://freeimage.sourceforge.io/>`_
.. note::
To use this plugin you have to install its backend::
imageio_download_bin freeimage
or you can download the backend using the function::
imageio.plugins.freeimage.download()
Each Freeimage format has the ``flags`` keyword argument. See the `Freeimage
documentation <https://freeimage.sourceforge.io/>`_ for more information.
Parameters
----------
flags : int
A freeimage-specific option. In most cases we provide explicit
parameters for influencing image reading.
"""
import numpy as np
from ..core import Format, image_as_uint
from ..core.request import RETURN_BYTES
from ._freeimage import FNAME_PER_PLATFORM, IO_FLAGS, download, fi # noqa
# todo: support files with only meta data
class FreeimageFormat(Format):
"""See :mod:`imageio.plugins.freeimage`"""
_modes = "i"
def __init__(self, name, description, extensions=None, modes=None, *, fif=None):
super().__init__(name, description, extensions=extensions, modes=modes)
self._fif = fif
@property
def fif(self):
return self._fif # Set when format is created
def _can_read(self, request):
# Ask freeimage if it can read it, maybe ext missing
if fi.has_lib():
if not hasattr(request, "_fif"):
try:
request._fif = fi.getFIF(request.filename, "r", request.firstbytes)
except Exception: # pragma: no cover
request._fif = -1
if request._fif == self.fif:
return True
elif request._fif == 7 and self.fif == 14:
# PPM gets identified as PBM and PPM can read PBM
# see: https://github.com/imageio/imageio/issues/677
return True
def _can_write(self, request):
# Ask freeimage, because we are not aware of all formats
if fi.has_lib():
if not hasattr(request, "_fif"):
try:
request._fif = fi.getFIF(request.filename, "w")
except ValueError: # pragma: no cover
if request.raw_uri == RETURN_BYTES:
request._fif = self.fif
else:
request._fif = -1
if request._fif is self.fif:
return True
# --
class Reader(Format.Reader):
def _get_length(self):
return 1
def _open(self, flags=0):
self._bm = fi.create_bitmap(self.request.filename, self.format.fif, flags)
self._bm.load_from_filename(self.request.get_local_filename())
def _close(self):
self._bm.close()
def _get_data(self, index):
if index != 0:
raise IndexError("This format only supports singleton images.")
return self._bm.get_image_data(), self._bm.get_meta_data()
def _get_meta_data(self, index):
if not (index is None or index == 0):
raise IndexError()
return self._bm.get_meta_data()
# --
class Writer(Format.Writer):
def _open(self, flags=0):
self._flags = flags # Store flags for later use
self._bm = None
self._is_set = False # To prevent appending more than one image
self._meta = {}
def _close(self):
# Set global meta data
self._bm.set_meta_data(self._meta)
# Write and close
self._bm.save_to_filename(self.request.get_local_filename())
self._bm.close()
def _append_data(self, im, meta):
# Check if set
if not self._is_set:
self._is_set = True
else:
raise RuntimeError(
"Singleton image; " "can only append image data once."
)
# Pop unit dimension for grayscale images
if im.ndim == 3 and im.shape[-1] == 1:
im = im[:, :, 0]
# Lazy instantaion of the bitmap, we need image data
if self._bm is None:
self._bm = fi.create_bitmap(
self.request.filename, self.format.fif, self._flags
)
self._bm.allocate(im)
# Set data
self._bm.set_image_data(im)
# There is no distinction between global and per-image meta data
# for singleton images
self._meta = meta
def _set_meta_data(self, meta):
self._meta = meta
# Special plugins
# todo: there is also FIF_LOAD_NOPIXELS,
# but perhaps that should be used with get_meta_data.
class FreeimageBmpFormat(FreeimageFormat):
"""A BMP format based on the Freeimage library.
This format supports grayscale, RGB and RGBA images.
The freeimage plugin requires a `freeimage` binary. If this binary
not available on the system, it can be downloaded manually from
<https://github.com/imageio/imageio-binaries> by either
- the command line script ``imageio_download_bin freeimage``
- the Python method ``imageio.plugins.freeimage.download()``
Parameters for saving
---------------------
compression : bool
Whether to compress the bitmap using RLE when saving. Default False.
It seems this does not always work, but who cares, you should use
PNG anyway.
"""
class Writer(FreeimageFormat.Writer):
def _open(self, flags=0, compression=False):
# Build flags from kwargs
flags = int(flags)
if compression:
flags |= IO_FLAGS.BMP_SAVE_RLE
else:
flags |= IO_FLAGS.BMP_DEFAULT
# Act as usual, but with modified flags
return FreeimageFormat.Writer._open(self, flags)
def _append_data(self, im, meta):
im = image_as_uint(im, bitdepth=8)
return FreeimageFormat.Writer._append_data(self, im, meta)
class FreeimagePngFormat(FreeimageFormat):
"""A PNG format based on the Freeimage library.
This format supports grayscale, RGB and RGBA images.
The freeimage plugin requires a `freeimage` binary. If this binary
not available on the system, it can be downloaded manually from
<https://github.com/imageio/imageio-binaries> by either
- the command line script ``imageio_download_bin freeimage``
- the Python method ``imageio.plugins.freeimage.download()``
Parameters for reading
----------------------
ignoregamma : bool
Avoid gamma correction. Default True.
Parameters for saving
---------------------
compression : {0, 1, 6, 9}
The compression factor. Higher factors result in more
compression at the cost of speed. Note that PNG compression is
always lossless. Default 9.
quantize : int
If specified, turn the given RGB or RGBA image in a paletted image
for more efficient storage. The value should be between 2 and 256.
If the value of 0 the image is not quantized.
interlaced : bool
Save using Adam7 interlacing. Default False.
"""
class Reader(FreeimageFormat.Reader):
def _open(self, flags=0, ignoregamma=True):
# Build flags from kwargs
flags = int(flags)
if ignoregamma:
flags |= IO_FLAGS.PNG_IGNOREGAMMA
# Enter as usual, with modified flags
return FreeimageFormat.Reader._open(self, flags)
# --
class Writer(FreeimageFormat.Writer):
def _open(self, flags=0, compression=9, quantize=0, interlaced=False):
compression_map = {
0: IO_FLAGS.PNG_Z_NO_COMPRESSION,
1: IO_FLAGS.PNG_Z_BEST_SPEED,
6: IO_FLAGS.PNG_Z_DEFAULT_COMPRESSION,
9: IO_FLAGS.PNG_Z_BEST_COMPRESSION,
}
# Build flags from kwargs
flags = int(flags)
if interlaced:
flags |= IO_FLAGS.PNG_INTERLACED
try:
flags |= compression_map[compression]
except KeyError:
raise ValueError("Png compression must be 0, 1, 6, or 9.")
# Act as usual, but with modified flags
return FreeimageFormat.Writer._open(self, flags)
def _append_data(self, im, meta):
if str(im.dtype) == "uint16":
im = image_as_uint(im, bitdepth=16)
else:
im = image_as_uint(im, bitdepth=8)
FreeimageFormat.Writer._append_data(self, im, meta)
# Quantize?
q = int(self.request.kwargs.get("quantize", False))
if not q:
pass
elif not (im.ndim == 3 and im.shape[-1] == 3):
raise ValueError("Can only quantize RGB images")
elif q < 2 or q > 256:
raise ValueError("PNG quantize param must be 2..256")
else:
bm = self._bm.quantize(0, q)
self._bm.close()
self._bm = bm
class FreeimageJpegFormat(FreeimageFormat):
"""A JPEG format based on the Freeimage library.
This format supports grayscale and RGB images.
The freeimage plugin requires a `freeimage` binary. If this binary
not available on the system, it can be downloaded manually from
<https://github.com/imageio/imageio-binaries> by either
- the command line script ``imageio_download_bin freeimage``
- the Python method ``imageio.plugins.freeimage.download()``
Parameters for reading
----------------------
exifrotate : bool
Automatically rotate the image according to the exif flag.
Default True. If 2 is given, do the rotation in Python instead
of freeimage.
quickread : bool
Read the image more quickly, at the expense of quality.
Default False.
Parameters for saving
---------------------
quality : scalar
The compression factor of the saved image (1..100), higher
numbers result in higher quality but larger file size. Default 75.
progressive : bool
Save as a progressive JPEG file (e.g. for images on the web).
Default False.
optimize : bool
On saving, compute optimal Huffman coding tables (can reduce a
few percent of file size). Default False.
baseline : bool
Save basic JPEG, without metadata or any markers. Default False.
"""
class Reader(FreeimageFormat.Reader):
def _open(self, flags=0, exifrotate=True, quickread=False):
# Build flags from kwargs
flags = int(flags)
if exifrotate and exifrotate != 2:
flags |= IO_FLAGS.JPEG_EXIFROTATE
if not quickread:
flags |= IO_FLAGS.JPEG_ACCURATE
# Enter as usual, with modified flags
return FreeimageFormat.Reader._open(self, flags)
def _get_data(self, index):
im, meta = FreeimageFormat.Reader._get_data(self, index)
im = self._rotate(im, meta)
return im, meta
def _rotate(self, im, meta):
"""Use Orientation information from EXIF meta data to
orient the image correctly. Freeimage is also supposed to
support that, and I am pretty sure it once did, but now it
does not, so let's just do it in Python.
Edit: and now it works again, just leave in place as a fallback.
"""
if self.request.kwargs.get("exifrotate", None) == 2:
try:
ori = meta["EXIF_MAIN"]["Orientation"]
except KeyError: # pragma: no cover
pass # Orientation not available
else: # pragma: no cover - we cannot touch all cases
# www.impulseadventure.com/photo/exif-orientation.html
if ori in [1, 2]:
pass
if ori in [3, 4]:
im = np.rot90(im, 2)
if ori in [5, 6]:
im = np.rot90(im, 3)
if ori in [7, 8]:
im = np.rot90(im)
if ori in [2, 4, 5, 7]: # Flipped cases (rare)
im = np.fliplr(im)
return im
# --
class Writer(FreeimageFormat.Writer):
def _open(
self, flags=0, quality=75, progressive=False, optimize=False, baseline=False
):
# Test quality
quality = int(quality)
if quality < 1 or quality > 100:
raise ValueError("JPEG quality should be between 1 and 100.")
# Build flags from kwargs
flags = int(flags)
flags |= quality
if progressive:
flags |= IO_FLAGS.JPEG_PROGRESSIVE
if optimize:
flags |= IO_FLAGS.JPEG_OPTIMIZE
if baseline:
flags |= IO_FLAGS.JPEG_BASELINE
# Act as usual, but with modified flags
return FreeimageFormat.Writer._open(self, flags)
def _append_data(self, im, meta):
if im.ndim == 3 and im.shape[-1] == 4:
raise IOError("JPEG does not support alpha channel.")
im = image_as_uint(im, bitdepth=8)
return FreeimageFormat.Writer._append_data(self, im, meta)
class FreeimagePnmFormat(FreeimageFormat):
"""A PNM format based on the Freeimage library.
This format supports single bit (PBM), grayscale (PGM) and RGB (PPM)
images, even with ASCII or binary coding.
The freeimage plugin requires a `freeimage` binary. If this binary
not available on the system, it can be downloaded manually from
<https://github.com/imageio/imageio-binaries> by either
- the command line script ``imageio_download_bin freeimage``
- the Python method ``imageio.plugins.freeimage.download()``
Parameters for saving
---------------------
use_ascii : bool
Save with ASCII coding. Default True.
"""
class Writer(FreeimageFormat.Writer):
def _open(self, flags=0, use_ascii=True):
# Build flags from kwargs
flags = int(flags)
if use_ascii:
flags |= IO_FLAGS.PNM_SAVE_ASCII
# Act as usual, but with modified flags
return FreeimageFormat.Writer._open(self, flags)