| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- # -*- 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)
|