| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312 |
- # -*- coding: utf-8 -*-
- # imageio is distributed under the terms of the (new) BSD License.
- # styletest: ignore E261
- """Module imageio/freeimage.py
- This module contains the wrapper code for the freeimage library.
- The functions defined in this module are relatively thin; just thin
- enough so that arguments and results are native Python/numpy data
- types.
- """
- import os
- import sys
- import ctypes
- import threading
- import logging
- import numpy
- from ..core import (
- get_remote_file,
- load_lib,
- Dict,
- resource_dirs,
- IS_PYPY,
- get_platform,
- InternetNotAllowedError,
- NeedDownloadError,
- )
- logger = logging.getLogger(__name__)
- TEST_NUMPY_NO_STRIDES = False # To test pypy fallback
- FNAME_PER_PLATFORM = {
- "osx32": "libfreeimage-3.16.0-osx10.6.dylib", # universal library
- "osx64": "libfreeimage-3.16.0-osx10.6.dylib",
- "win32": "FreeImage-3.18.0-win32.dll",
- "win64": "FreeImage-3.18.0-win64.dll",
- "linux32": "libfreeimage-3.16.0-linux32.so",
- "linux64": "libfreeimage-3.16.0-linux64.so",
- }
- def download(directory=None, force_download=False):
- """Download the FreeImage library to your computer.
- Parameters
- ----------
- directory : str | None
- The directory where the file will be cached if a download was
- required to obtain the file. By default, the appdata directory
- is used. This is also the first directory that is checked for
- a local version of the file.
- force_download : bool | str
- If True, the file will be downloaded even if a local copy exists
- (and this copy will be overwritten). Can also be a YYYY-MM-DD date
- to ensure a file is up-to-date (modified date of a file on disk,
- if present, is checked).
- """
- plat = get_platform()
- if plat and plat in FNAME_PER_PLATFORM:
- fname = "freeimage/" + FNAME_PER_PLATFORM[plat]
- get_remote_file(fname=fname, directory=directory, force_download=force_download)
- fi._lib = None # allow trying again (needed to make tests work)
- def get_freeimage_lib():
- """Ensure we have our version of the binary freeimage lib."""
- lib = os.getenv("IMAGEIO_FREEIMAGE_LIB", None)
- if lib: # pragma: no cover
- return lib
- # Get filename to load
- # If we do not provide a binary, the system may still do ...
- plat = get_platform()
- if plat and plat in FNAME_PER_PLATFORM:
- try:
- return get_remote_file("freeimage/" + FNAME_PER_PLATFORM[plat], auto=False)
- except InternetNotAllowedError:
- pass
- except NeedDownloadError:
- raise NeedDownloadError(
- "Need FreeImage library. "
- "You can obtain it with either:\n"
- " - download using the command: "
- "imageio_download_bin freeimage\n"
- " - download by calling (in Python): "
- "imageio.plugins.freeimage.download()\n"
- )
- except RuntimeError as e: # pragma: no cover
- logger.warning(str(e))
- # Define function to encode a filename to bytes (for the current system)
- def efn(x):
- return x.encode(sys.getfilesystemencoding())
- # 4-byte quads of 0,v,v,v from 0,0,0,0 to 0,255,255,255
- GREY_PALETTE = numpy.arange(0, 0x01000000, 0x00010101, dtype=numpy.uint32)
- class FI_TYPES(object):
- FIT_UNKNOWN = 0
- FIT_BITMAP = 1
- FIT_UINT16 = 2
- FIT_INT16 = 3
- FIT_UINT32 = 4
- FIT_INT32 = 5
- FIT_FLOAT = 6
- FIT_DOUBLE = 7
- FIT_COMPLEX = 8
- FIT_RGB16 = 9
- FIT_RGBA16 = 10
- FIT_RGBF = 11
- FIT_RGBAF = 12
- dtypes = {
- FIT_BITMAP: numpy.uint8,
- FIT_UINT16: numpy.uint16,
- FIT_INT16: numpy.int16,
- FIT_UINT32: numpy.uint32,
- FIT_INT32: numpy.int32,
- FIT_FLOAT: numpy.float32,
- FIT_DOUBLE: numpy.float64,
- FIT_COMPLEX: numpy.complex128,
- FIT_RGB16: numpy.uint16,
- FIT_RGBA16: numpy.uint16,
- FIT_RGBF: numpy.float32,
- FIT_RGBAF: numpy.float32,
- }
- fi_types = {
- (numpy.uint8, 1): FIT_BITMAP,
- (numpy.uint8, 3): FIT_BITMAP,
- (numpy.uint8, 4): FIT_BITMAP,
- (numpy.uint16, 1): FIT_UINT16,
- (numpy.int16, 1): FIT_INT16,
- (numpy.uint32, 1): FIT_UINT32,
- (numpy.int32, 1): FIT_INT32,
- (numpy.float32, 1): FIT_FLOAT,
- (numpy.float64, 1): FIT_DOUBLE,
- (numpy.complex128, 1): FIT_COMPLEX,
- (numpy.uint16, 3): FIT_RGB16,
- (numpy.uint16, 4): FIT_RGBA16,
- (numpy.float32, 3): FIT_RGBF,
- (numpy.float32, 4): FIT_RGBAF,
- }
- extra_dims = {
- FIT_UINT16: [],
- FIT_INT16: [],
- FIT_UINT32: [],
- FIT_INT32: [],
- FIT_FLOAT: [],
- FIT_DOUBLE: [],
- FIT_COMPLEX: [],
- FIT_RGB16: [3],
- FIT_RGBA16: [4],
- FIT_RGBF: [3],
- FIT_RGBAF: [4],
- }
- class IO_FLAGS(object):
- FIF_LOAD_NOPIXELS = 0x8000 # loading: load the image header only
- # # (not supported by all plugins)
- BMP_DEFAULT = 0
- BMP_SAVE_RLE = 1
- CUT_DEFAULT = 0
- DDS_DEFAULT = 0
- EXR_DEFAULT = 0 # save data as half with piz-based wavelet compression
- EXR_FLOAT = 0x0001 # save data as float instead of half (not recommended)
- EXR_NONE = 0x0002 # save with no compression
- EXR_ZIP = 0x0004 # save with zlib compression, in blocks of 16 scan lines
- EXR_PIZ = 0x0008 # save with piz-based wavelet compression
- EXR_PXR24 = 0x0010 # save with lossy 24-bit float compression
- EXR_B44 = 0x0020 # save with lossy 44% float compression
- # # - goes to 22% when combined with EXR_LC
- EXR_LC = 0x0040 # save images with one luminance and two chroma channels,
- # # rather than as RGB (lossy compression)
- FAXG3_DEFAULT = 0
- GIF_DEFAULT = 0
- GIF_LOAD256 = 1 # Load the image as a 256 color image with ununsed
- # # palette entries, if it's 16 or 2 color
- GIF_PLAYBACK = 2 # 'Play' the GIF to generate each frame (as 32bpp)
- # # instead of returning raw frame data when loading
- HDR_DEFAULT = 0
- ICO_DEFAULT = 0
- ICO_MAKEALPHA = 1 # convert to 32bpp and create an alpha channel from the
- # # AND-mask when loading
- IFF_DEFAULT = 0
- J2K_DEFAULT = 0 # save with a 16:1 rate
- JP2_DEFAULT = 0 # save with a 16:1 rate
- JPEG_DEFAULT = 0 # loading (see JPEG_FAST);
- # # saving (see JPEG_QUALITYGOOD|JPEG_SUBSAMPLING_420)
- JPEG_FAST = 0x0001 # load the file as fast as possible,
- # # sacrificing some quality
- JPEG_ACCURATE = 0x0002 # load the file with the best quality,
- # # sacrificing some speed
- JPEG_CMYK = 0x0004 # load separated CMYK "as is"
- # # (use | to combine with other load flags)
- JPEG_EXIFROTATE = 0x0008 # load and rotate according to
- # # Exif 'Orientation' tag if available
- JPEG_QUALITYSUPERB = 0x80 # save with superb quality (100:1)
- JPEG_QUALITYGOOD = 0x0100 # save with good quality (75:1)
- JPEG_QUALITYNORMAL = 0x0200 # save with normal quality (50:1)
- JPEG_QUALITYAVERAGE = 0x0400 # save with average quality (25:1)
- JPEG_QUALITYBAD = 0x0800 # save with bad quality (10:1)
- JPEG_PROGRESSIVE = 0x2000 # save as a progressive-JPEG
- # # (use | to combine with other save flags)
- JPEG_SUBSAMPLING_411 = 0x1000 # save with high 4x1 chroma
- # # subsampling (4:1:1)
- JPEG_SUBSAMPLING_420 = 0x4000 # save with medium 2x2 medium chroma
- # # subsampling (4:2:0) - default value
- JPEG_SUBSAMPLING_422 = 0x8000 # save /w low 2x1 chroma subsampling (4:2:2)
- JPEG_SUBSAMPLING_444 = 0x10000 # save with no chroma subsampling (4:4:4)
- JPEG_OPTIMIZE = 0x20000 # on saving, compute optimal Huffman coding tables
- # # (can reduce a few percent of file size)
- JPEG_BASELINE = 0x40000 # save basic JPEG, without metadata or any markers
- KOALA_DEFAULT = 0
- LBM_DEFAULT = 0
- MNG_DEFAULT = 0
- PCD_DEFAULT = 0
- PCD_BASE = 1 # load the bitmap sized 768 x 512
- PCD_BASEDIV4 = 2 # load the bitmap sized 384 x 256
- PCD_BASEDIV16 = 3 # load the bitmap sized 192 x 128
- PCX_DEFAULT = 0
- PFM_DEFAULT = 0
- PICT_DEFAULT = 0
- PNG_DEFAULT = 0
- PNG_IGNOREGAMMA = 1 # loading: avoid gamma correction
- PNG_Z_BEST_SPEED = 0x0001 # save using ZLib level 1 compression flag
- # # (default value is 6)
- PNG_Z_DEFAULT_COMPRESSION = 0x0006 # save using ZLib level 6 compression
- # # flag (default recommended value)
- PNG_Z_BEST_COMPRESSION = 0x0009 # save using ZLib level 9 compression flag
- # # (default value is 6)
- PNG_Z_NO_COMPRESSION = 0x0100 # save without ZLib compression
- PNG_INTERLACED = 0x0200 # save using Adam7 interlacing (use | to combine
- # # with other save flags)
- PNM_DEFAULT = 0
- PNM_SAVE_RAW = 0 # Writer saves in RAW format (i.e. P4, P5 or P6)
- PNM_SAVE_ASCII = 1 # Writer saves in ASCII format (i.e. P1, P2 or P3)
- PSD_DEFAULT = 0
- PSD_CMYK = 1 # reads tags for separated CMYK (default is conversion to RGB)
- PSD_LAB = 2 # reads tags for CIELab (default is conversion to RGB)
- RAS_DEFAULT = 0
- RAW_DEFAULT = 0 # load the file as linear RGB 48-bit
- RAW_PREVIEW = 1 # try to load the embedded JPEG preview with included
- # # Exif Data or default to RGB 24-bit
- RAW_DISPLAY = 2 # load the file as RGB 24-bit
- SGI_DEFAULT = 0
- TARGA_DEFAULT = 0
- TARGA_LOAD_RGB888 = 1 # Convert RGB555 and ARGB8888 -> RGB888.
- TARGA_SAVE_RLE = 2 # Save with RLE compression
- TIFF_DEFAULT = 0
- TIFF_CMYK = 0x0001 # reads/stores tags for separated CMYK
- # # (use | to combine with compression flags)
- TIFF_PACKBITS = 0x0100 # save using PACKBITS compression
- TIFF_DEFLATE = 0x0200 # save using DEFLATE (a.k.a. ZLIB) compression
- TIFF_ADOBE_DEFLATE = 0x0400 # save using ADOBE DEFLATE compression
- TIFF_NONE = 0x0800 # save without any compression
- TIFF_CCITTFAX3 = 0x1000 # save using CCITT Group 3 fax encoding
- TIFF_CCITTFAX4 = 0x2000 # save using CCITT Group 4 fax encoding
- TIFF_LZW = 0x4000 # save using LZW compression
- TIFF_JPEG = 0x8000 # save using JPEG compression
- TIFF_LOGLUV = 0x10000 # save using LogLuv compression
- WBMP_DEFAULT = 0
- XBM_DEFAULT = 0
- XPM_DEFAULT = 0
- class METADATA_MODELS(object):
- FIMD_COMMENTS = 0
- FIMD_EXIF_MAIN = 1
- FIMD_EXIF_EXIF = 2
- FIMD_EXIF_GPS = 3
- FIMD_EXIF_MAKERNOTE = 4
- FIMD_EXIF_INTEROP = 5
- FIMD_IPTC = 6
- FIMD_XMP = 7
- FIMD_GEOTIFF = 8
- FIMD_ANIMATION = 9
- class METADATA_DATATYPE(object):
- FIDT_BYTE = 1 # 8-bit unsigned integer
- FIDT_ASCII = 2 # 8-bit bytes w/ last byte null
- FIDT_SHORT = 3 # 16-bit unsigned integer
- FIDT_LONG = 4 # 32-bit unsigned integer
- FIDT_RATIONAL = 5 # 64-bit unsigned fraction
- FIDT_SBYTE = 6 # 8-bit signed integer
- FIDT_UNDEFINED = 7 # 8-bit untyped data
- FIDT_SSHORT = 8 # 16-bit signed integer
- FIDT_SLONG = 9 # 32-bit signed integer
- FIDT_SRATIONAL = 10 # 64-bit signed fraction
- FIDT_FLOAT = 11 # 32-bit IEEE floating point
- FIDT_DOUBLE = 12 # 64-bit IEEE floating point
- FIDT_IFD = 13 # 32-bit unsigned integer (offset)
- FIDT_PALETTE = 14 # 32-bit RGBQUAD
- FIDT_LONG8 = 16 # 64-bit unsigned integer
- FIDT_SLONG8 = 17 # 64-bit signed integer
- FIDT_IFD8 = 18 # 64-bit unsigned integer (offset)
- dtypes = {
- FIDT_BYTE: numpy.uint8,
- FIDT_SHORT: numpy.uint16,
- FIDT_LONG: numpy.uint32,
- FIDT_RATIONAL: [("numerator", numpy.uint32), ("denominator", numpy.uint32)],
- FIDT_LONG8: numpy.uint64,
- FIDT_SLONG8: numpy.int64,
- FIDT_IFD8: numpy.uint64,
- FIDT_SBYTE: numpy.int8,
- FIDT_UNDEFINED: numpy.uint8,
- FIDT_SSHORT: numpy.int16,
- FIDT_SLONG: numpy.int32,
- FIDT_SRATIONAL: [("numerator", numpy.int32), ("denominator", numpy.int32)],
- FIDT_FLOAT: numpy.float32,
- FIDT_DOUBLE: numpy.float64,
- FIDT_IFD: numpy.uint32,
- FIDT_PALETTE: [
- ("R", numpy.uint8),
- ("G", numpy.uint8),
- ("B", numpy.uint8),
- ("A", numpy.uint8),
- ],
- }
- class Freeimage(object):
- """Class to represent an interface to the FreeImage library.
- This class is relatively thin. It provides a Pythonic API that converts
- Freeimage objects to Python objects, but that's about it.
- The actual implementation should be provided by the plugins.
- The recommended way to call into the Freeimage library (so that
- errors and warnings show up in the right moment) is to use this
- object as a context manager:
- with imageio.fi as lib:
- lib.FreeImage_GetPalette()
- """
- _API = {
- # All we're doing here is telling ctypes that some of the
- # FreeImage functions return pointers instead of integers. (On
- # 64-bit systems, without this information the pointers get
- # truncated and crashes result). There's no need to list
- # functions that return ints, or the types of the parameters
- # to these or other functions -- that's fine to do implicitly.
- # Note that the ctypes immediately converts the returned void_p
- # back to a python int again! This is really not helpful,
- # because then passing it back to another library call will
- # cause truncation-to-32-bits on 64-bit systems. Thanks, ctypes!
- # So after these calls one must immediately re-wrap the int as
- # a c_void_p if it is to be passed back into FreeImage.
- "FreeImage_AllocateT": (ctypes.c_void_p, None),
- "FreeImage_FindFirstMetadata": (ctypes.c_void_p, None),
- "FreeImage_GetBits": (ctypes.c_void_p, None),
- "FreeImage_GetPalette": (ctypes.c_void_p, None),
- "FreeImage_GetTagKey": (ctypes.c_char_p, None),
- "FreeImage_GetTagValue": (ctypes.c_void_p, None),
- "FreeImage_CreateTag": (ctypes.c_void_p, None),
- "FreeImage_Save": (ctypes.c_void_p, None),
- "FreeImage_Load": (ctypes.c_void_p, None),
- "FreeImage_LoadFromMemory": (ctypes.c_void_p, None),
- "FreeImage_OpenMultiBitmap": (ctypes.c_void_p, None),
- "FreeImage_LoadMultiBitmapFromMemory": (ctypes.c_void_p, None),
- "FreeImage_LockPage": (ctypes.c_void_p, None),
- "FreeImage_OpenMemory": (ctypes.c_void_p, None),
- # 'FreeImage_ReadMemory': (ctypes.c_void_p, None),
- # 'FreeImage_CloseMemory': (ctypes.c_void_p, None),
- "FreeImage_GetVersion": (ctypes.c_char_p, None),
- "FreeImage_GetFIFExtensionList": (ctypes.c_char_p, None),
- "FreeImage_GetFormatFromFIF": (ctypes.c_char_p, None),
- "FreeImage_GetFIFDescription": (ctypes.c_char_p, None),
- "FreeImage_ColorQuantizeEx": (ctypes.c_void_p, None),
- # Pypy wants some extra definitions, so here we go ...
- "FreeImage_IsLittleEndian": (ctypes.c_int, None),
- "FreeImage_SetOutputMessage": (ctypes.c_void_p, None),
- "FreeImage_GetFIFCount": (ctypes.c_int, None),
- "FreeImage_IsPluginEnabled": (ctypes.c_int, None),
- "FreeImage_GetFileType": (ctypes.c_int, None),
- #
- "FreeImage_GetTagType": (ctypes.c_int, None),
- "FreeImage_GetTagLength": (ctypes.c_int, None),
- "FreeImage_FindNextMetadata": (ctypes.c_int, None),
- "FreeImage_FindCloseMetadata": (ctypes.c_void_p, None),
- #
- "FreeImage_GetFIFFromFilename": (ctypes.c_int, None),
- "FreeImage_FIFSupportsReading": (ctypes.c_int, None),
- "FreeImage_FIFSupportsWriting": (ctypes.c_int, None),
- "FreeImage_FIFSupportsExportType": (ctypes.c_int, None),
- "FreeImage_FIFSupportsExportBPP": (ctypes.c_int, None),
- "FreeImage_GetHeight": (ctypes.c_int, None),
- "FreeImage_GetWidth": (ctypes.c_int, None),
- "FreeImage_GetImageType": (ctypes.c_int, None),
- "FreeImage_GetBPP": (ctypes.c_int, None),
- "FreeImage_GetColorsUsed": (ctypes.c_int, None),
- "FreeImage_ConvertTo32Bits": (ctypes.c_void_p, None),
- "FreeImage_GetPitch": (ctypes.c_int, None),
- "FreeImage_Unload": (ctypes.c_void_p, None),
- }
- def __init__(self):
- # Initialize freeimage lib as None
- self._lib = None
- # A lock to create thread-safety
- self._lock = threading.RLock()
- # Init log messages lists
- self._messages = []
- # Select functype for error handler
- if sys.platform.startswith("win"):
- functype = ctypes.WINFUNCTYPE
- else:
- functype = ctypes.CFUNCTYPE
- # Create output message handler
- @functype(None, ctypes.c_int, ctypes.c_char_p)
- def error_handler(fif, message):
- message = message.decode("utf-8")
- self._messages.append(message)
- while (len(self._messages)) > 256:
- self._messages.pop(0)
- # Make sure to keep a ref to function
- self._error_handler = error_handler
- @property
- def lib(self):
- if self._lib is None:
- try:
- self.load_freeimage()
- except OSError as err:
- self._lib = "The freeimage library could not be loaded: "
- self._lib += str(err)
- if isinstance(self._lib, str):
- raise RuntimeError(self._lib)
- return self._lib
- def has_lib(self):
- try:
- self.lib
- except Exception:
- return False
- return True
- def load_freeimage(self):
- """Try to load the freeimage lib from the system. If not successful,
- try to download the imageio version and try again.
- """
- # Load library and register API
- success = False
- try:
- # Try without forcing a download, but giving preference
- # to the imageio-provided lib (if previously downloaded)
- self._load_freeimage()
- self._register_api()
- if self.lib.FreeImage_GetVersion().decode("utf-8") >= "3.15":
- success = True
- except OSError:
- pass
- if not success:
- # Ensure we have our own lib, try again
- get_freeimage_lib()
- self._load_freeimage()
- self._register_api()
- # Wrap up
- self.lib.FreeImage_SetOutputMessage(self._error_handler)
- self.lib_version = self.lib.FreeImage_GetVersion().decode("utf-8")
- def _load_freeimage(self):
- # Define names
- lib_names = ["freeimage", "libfreeimage"]
- exact_lib_names = [
- "FreeImage",
- "libfreeimage.dylib",
- "libfreeimage.so",
- "libfreeimage.so.3",
- ]
- # Add names of libraries that we provide (that file may not exist)
- res_dirs = resource_dirs()
- plat = get_platform()
- if plat: # Can be None on e.g. FreeBSD
- fname = FNAME_PER_PLATFORM[plat]
- for dir in res_dirs:
- exact_lib_names.insert(0, os.path.join(dir, "freeimage", fname))
- # Add the path specified with IMAGEIO_FREEIMAGE_LIB:
- lib = os.getenv("IMAGEIO_FREEIMAGE_LIB", None)
- if lib is not None:
- exact_lib_names.insert(0, lib)
- # Load
- try:
- lib, fname = load_lib(exact_lib_names, lib_names, res_dirs)
- except OSError as err: # pragma: no cover
- err_msg = str(err) + "\nPlease install the FreeImage library."
- raise OSError(err_msg)
- # Store
- self._lib = lib
- self.lib_fname = fname
- def _register_api(self):
- # Albert's ctypes pattern
- for f, (restype, argtypes) in self._API.items():
- func = getattr(self.lib, f)
- func.restype = restype
- func.argtypes = argtypes
- # Handling of output messages
- def __enter__(self):
- self._lock.acquire()
- return self.lib
- def __exit__(self, *args):
- self._show_any_warnings()
- self._lock.release()
- def _reset_log(self):
- """Reset the list of output messages. Call this before
- loading or saving an image with the FreeImage API.
- """
- self._messages = []
- def _get_error_message(self):
- """Get the output messages produced since the last reset as
- one string. Returns 'No known reason.' if there are no messages.
- Also resets the log.
- """
- if self._messages:
- res = " ".join(self._messages)
- self._reset_log()
- return res
- else:
- return "No known reason."
- def _show_any_warnings(self):
- """If there were any messages since the last reset, show them
- as a warning. Otherwise do nothing. Also resets the messages.
- """
- if self._messages:
- logger.warning("imageio.freeimage warning: " + self._get_error_message())
- self._reset_log()
- def get_output_log(self):
- """Return a list of the last 256 output messages
- (warnings and errors) produced by the FreeImage library.
- """
- # This message log is not cleared/reset, but kept to 256 elements.
- return [m for m in self._messages]
- def getFIF(self, filename, mode, bb=None):
- """Get the freeimage Format (FIF) from a given filename.
- If mode is 'r', will try to determine the format by reading
- the file, otherwise only the filename is used.
- This function also tests whether the format supports reading/writing.
- """
- with self as lib:
- # Init
- ftype = -1
- if mode not in "rw":
- raise ValueError('Invalid mode (must be "r" or "w").')
- # Try getting format from the content. Note that some files
- # do not have a header that allows reading the format from
- # the file.
- if mode == "r":
- if bb is not None:
- fimemory = lib.FreeImage_OpenMemory(ctypes.c_char_p(bb), len(bb))
- ftype = lib.FreeImage_GetFileTypeFromMemory(
- ctypes.c_void_p(fimemory), len(bb)
- )
- lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
- if (ftype == -1) and os.path.isfile(filename):
- ftype = lib.FreeImage_GetFileType(efn(filename), 0)
- # Try getting the format from the extension
- if ftype == -1:
- ftype = lib.FreeImage_GetFIFFromFilename(efn(filename))
- # Test if ok
- if ftype == -1:
- raise ValueError('Cannot determine format of file "%s"' % filename)
- elif mode == "w" and not lib.FreeImage_FIFSupportsWriting(ftype):
- raise ValueError('Cannot write the format of file "%s"' % filename)
- elif mode == "r" and not lib.FreeImage_FIFSupportsReading(ftype):
- raise ValueError('Cannot read the format of file "%s"' % filename)
- return ftype
- def create_bitmap(self, filename, ftype, flags=0):
- """create_bitmap(filename, ftype, flags=0)
- Create a wrapped bitmap object.
- """
- return FIBitmap(self, filename, ftype, flags)
- def create_multipage_bitmap(self, filename, ftype, flags=0):
- """create_multipage_bitmap(filename, ftype, flags=0)
- Create a wrapped multipage bitmap object.
- """
- return FIMultipageBitmap(self, filename, ftype, flags)
- class FIBaseBitmap(object):
- def __init__(self, fi, filename, ftype, flags):
- self._fi = fi
- self._filename = filename
- self._ftype = ftype
- self._flags = flags
- self._bitmap = None
- self._close_funcs = []
- def __del__(self):
- self.close()
- def close(self):
- if (self._bitmap is not None) and self._close_funcs:
- for close_func in self._close_funcs:
- try:
- with self._fi:
- fun = close_func[0]
- fun(*close_func[1:])
- except Exception: # pragma: no cover
- pass
- self._close_funcs = []
- self._bitmap = None
- def _set_bitmap(self, bitmap, close_func=None):
- """Function to set the bitmap and specify the function to unload it."""
- if self._bitmap is not None:
- pass # bitmap is converted
- if close_func is None:
- close_func = self._fi.lib.FreeImage_Unload, bitmap
- self._bitmap = bitmap
- if close_func:
- self._close_funcs.append(close_func)
- def get_meta_data(self):
- # todo: there is also FreeImage_TagToString, is that useful?
- # and would that work well when reading and then saving?
- # Create a list of (model_name, number) tuples
- models = [
- (name[5:], number)
- for name, number in METADATA_MODELS.__dict__.items()
- if name.startswith("FIMD_")
- ]
- # Prepare
- metadata = Dict()
- tag = ctypes.c_void_p()
- with self._fi as lib:
- # Iterate over all FreeImage meta models
- for model_name, number in models:
- # Find beginning, get search handle
- mdhandle = lib.FreeImage_FindFirstMetadata(
- number, self._bitmap, ctypes.byref(tag)
- )
- mdhandle = ctypes.c_void_p(mdhandle)
- if mdhandle:
- # Iterate over all tags in this model
- more = True
- while more:
- # Get info about tag
- tag_name = lib.FreeImage_GetTagKey(tag).decode("utf-8")
- tag_type = lib.FreeImage_GetTagType(tag)
- byte_size = lib.FreeImage_GetTagLength(tag)
- char_ptr = ctypes.c_char * byte_size
- data = char_ptr.from_address(lib.FreeImage_GetTagValue(tag))
- # Convert in a way compatible with Pypy
- tag_bytes = bytes(bytearray(data))
- # The default value is the raw bytes
- tag_val = tag_bytes
- # Convert to a Python value in the metadata dict
- if tag_type == METADATA_DATATYPE.FIDT_ASCII:
- tag_val = tag_bytes.decode("utf-8", "replace")
- elif tag_type in METADATA_DATATYPE.dtypes:
- dtype = METADATA_DATATYPE.dtypes[tag_type]
- if IS_PYPY and isinstance(dtype, (list, tuple)):
- pass # pragma: no cover - or we get a segfault
- else:
- try:
- tag_val = numpy.frombuffer(
- tag_bytes, dtype=dtype
- ).copy()
- if len(tag_val) == 1:
- tag_val = tag_val[0]
- except Exception: # pragma: no cover
- pass
- # Store data in dict
- subdict = metadata.setdefault(model_name, Dict())
- subdict[tag_name] = tag_val
- # Next
- more = lib.FreeImage_FindNextMetadata(
- mdhandle, ctypes.byref(tag)
- )
- # Close search handle for current meta model
- lib.FreeImage_FindCloseMetadata(mdhandle)
- # Done
- return metadata
- def set_meta_data(self, metadata):
- # Create a dict mapping model_name to number
- models = {}
- for name, number in METADATA_MODELS.__dict__.items():
- if name.startswith("FIMD_"):
- models[name[5:]] = number
- # Create a mapping from numpy.dtype to METADATA_DATATYPE
- def get_tag_type_number(dtype):
- for number, numpy_dtype in METADATA_DATATYPE.dtypes.items():
- if dtype == numpy_dtype:
- return number
- else:
- return None
- with self._fi as lib:
- for model_name, subdict in metadata.items():
- # Get model number
- number = models.get(model_name, None)
- if number is None:
- continue # Unknown model, silent ignore
- for tag_name, tag_val in subdict.items():
- # Create new tag
- tag = lib.FreeImage_CreateTag()
- tag = ctypes.c_void_p(tag)
- try:
- # Convert Python value to FI type, val
- is_ascii = False
- if isinstance(tag_val, str):
- try:
- tag_bytes = tag_val.encode("ascii")
- is_ascii = True
- except UnicodeError:
- pass
- if is_ascii:
- tag_type = METADATA_DATATYPE.FIDT_ASCII
- tag_count = len(tag_bytes)
- else:
- if not hasattr(tag_val, "dtype"):
- tag_val = numpy.array([tag_val])
- tag_type = get_tag_type_number(tag_val.dtype)
- if tag_type is None:
- logger.warning(
- "imageio.freeimage warning: Could not "
- "determine tag type of %r." % tag_name
- )
- continue
- tag_bytes = tag_val.tobytes()
- tag_count = tag_val.size
- # Set properties
- lib.FreeImage_SetTagKey(tag, tag_name.encode("utf-8"))
- lib.FreeImage_SetTagType(tag, tag_type)
- lib.FreeImage_SetTagCount(tag, tag_count)
- lib.FreeImage_SetTagLength(tag, len(tag_bytes))
- lib.FreeImage_SetTagValue(tag, tag_bytes)
- # Store tag
- tag_key = lib.FreeImage_GetTagKey(tag)
- lib.FreeImage_SetMetadata(number, self._bitmap, tag_key, tag)
- except Exception as err: # pragma: no cover
- logger.warning(
- "imagio.freeimage warning: Could not set tag "
- "%r: %s, %s"
- % (tag_name, self._fi._get_error_message(), str(err))
- )
- finally:
- lib.FreeImage_DeleteTag(tag)
- class FIBitmap(FIBaseBitmap):
- """Wrapper for the FI bitmap object."""
- def allocate(self, array):
- # Prepare array
- assert isinstance(array, numpy.ndarray)
- shape = array.shape
- dtype = array.dtype
- # Get shape and channel info
- r, c = shape[:2]
- if len(shape) == 2:
- n_channels = 1
- elif len(shape) == 3:
- n_channels = shape[2]
- else:
- n_channels = shape[0]
- # Get fi_type
- try:
- fi_type = FI_TYPES.fi_types[(dtype.type, n_channels)]
- self._fi_type = fi_type
- except KeyError:
- raise ValueError("Cannot write arrays of given type and shape.")
- # Allocate bitmap
- with self._fi as lib:
- bpp = 8 * dtype.itemsize * n_channels
- bitmap = lib.FreeImage_AllocateT(fi_type, c, r, bpp, 0, 0, 0)
- bitmap = ctypes.c_void_p(bitmap)
- # Check and store
- if not bitmap: # pragma: no cover
- raise RuntimeError(
- "Could not allocate bitmap for storage: %s"
- % self._fi._get_error_message()
- )
- self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
- def load_from_filename(self, filename=None):
- if filename is None:
- filename = self._filename
- with self._fi as lib:
- # Create bitmap
- bitmap = lib.FreeImage_Load(self._ftype, efn(filename), self._flags)
- bitmap = ctypes.c_void_p(bitmap)
- # Check and store
- if not bitmap: # pragma: no cover
- raise ValueError(
- 'Could not load bitmap "%s": %s'
- % (self._filename, self._fi._get_error_message())
- )
- self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
- # def load_from_bytes(self, bb):
- # with self._fi as lib:
- # # Create bitmap
- # fimemory = lib.FreeImage_OpenMemory(
- # ctypes.c_char_p(bb), len(bb))
- # bitmap = lib.FreeImage_LoadFromMemory(
- # self._ftype, ctypes.c_void_p(fimemory), self._flags)
- # bitmap = ctypes.c_void_p(bitmap)
- # lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
- #
- # # Check
- # if not bitmap:
- # raise ValueError('Could not load bitmap "%s": %s'
- # % (self._filename, self._fi._get_error_message()))
- # else:
- # self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
- def save_to_filename(self, filename=None):
- if filename is None:
- filename = self._filename
- ftype = self._ftype
- bitmap = self._bitmap
- fi_type = self._fi_type # element type
- with self._fi as lib:
- # Check if can write
- if fi_type == FI_TYPES.FIT_BITMAP:
- can_write = lib.FreeImage_FIFSupportsExportBPP(
- ftype, lib.FreeImage_GetBPP(bitmap)
- )
- else:
- can_write = lib.FreeImage_FIFSupportsExportType(ftype, fi_type)
- if not can_write:
- raise TypeError("Cannot save image of this format to this file type")
- # Save to file
- res = lib.FreeImage_Save(ftype, bitmap, efn(filename), self._flags)
- # Check
- if res is None: # pragma: no cover, we do so many checks, this is rare
- raise RuntimeError(
- f"Could not save file `{self._filename}`: {self._fi._get_error_message()}"
- )
- # def save_to_bytes(self):
- # ftype = self._ftype
- # bitmap = self._bitmap
- # fi_type = self._fi_type # element type
- #
- # with self._fi as lib:
- # # Check if can write
- # if fi_type == FI_TYPES.FIT_BITMAP:
- # can_write = lib.FreeImage_FIFSupportsExportBPP(ftype,
- # lib.FreeImage_GetBPP(bitmap))
- # else:
- # can_write = lib.FreeImage_FIFSupportsExportType(ftype, fi_type)
- # if not can_write:
- # raise TypeError('Cannot save image of this format '
- # 'to this file type')
- #
- # # Extract the bytes
- # fimemory = lib.FreeImage_OpenMemory(0, 0)
- # res = lib.FreeImage_SaveToMemory(ftype, bitmap,
- # ctypes.c_void_p(fimemory),
- # self._flags)
- # if res:
- # N = lib.FreeImage_TellMemory(ctypes.c_void_p(fimemory))
- # result = ctypes.create_string_buffer(N)
- # lib.FreeImage_SeekMemory(ctypes.c_void_p(fimemory), 0)
- # lib.FreeImage_ReadMemory(result, 1, N, ctypes.c_void_p(fimemory))
- # result = result.raw
- # lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
- #
- # # Check
- # if not res:
- # raise RuntimeError('Could not save file "%s": %s'
- # % (self._filename, self._fi._get_error_message()))
- #
- # # Done
- # return result
- def get_image_data(self):
- dtype, shape, bpp = self._get_type_and_shape()
- array = self._wrap_bitmap_bits_in_array(shape, dtype, False)
- with self._fi as lib:
- isle = lib.FreeImage_IsLittleEndian()
- # swizzle the color components and flip the scanlines to go from
- # FreeImage's BGR[A] and upside-down internal memory format to
- # something more normal
- def n(arr):
- # return arr[..., ::-1].T # Does not work on numpypy yet
- if arr.ndim == 1: # pragma: no cover
- return arr[::-1].T
- elif arr.ndim == 2: # Always the case here ...
- return arr[:, ::-1].T
- elif arr.ndim == 3: # pragma: no cover
- return arr[:, :, ::-1].T
- elif arr.ndim == 4: # pragma: no cover
- return arr[:, :, :, ::-1].T
- if len(shape) == 3 and isle and dtype.type == numpy.uint8:
- b = n(array[0])
- g = n(array[1])
- r = n(array[2])
- if shape[0] == 3:
- return numpy.dstack((r, g, b))
- elif shape[0] == 4:
- a = n(array[3])
- return numpy.dstack((r, g, b, a))
- else: # pragma: no cover - we check this earlier
- raise ValueError("Cannot handle images of shape %s" % shape)
- # We need to copy because array does *not* own its memory
- # after bitmap is freed.
- a = n(array).copy()
- return a
- def set_image_data(self, array):
- # Prepare array
- assert isinstance(array, numpy.ndarray)
- shape = array.shape
- dtype = array.dtype
- with self._fi as lib:
- isle = lib.FreeImage_IsLittleEndian()
- # Calculate shape and channels
- r, c = shape[:2]
- if len(shape) == 2:
- n_channels = 1
- w_shape = (c, r)
- elif len(shape) == 3:
- n_channels = shape[2]
- w_shape = (n_channels, c, r)
- else:
- n_channels = shape[0]
- def n(arr): # normalise to freeimage's in-memory format
- return arr[::-1].T
- wrapped_array = self._wrap_bitmap_bits_in_array(w_shape, dtype, True)
- # swizzle the color components and flip the scanlines to go to
- # FreeImage's BGR[A] and upside-down internal memory format
- # The BGR[A] order is only used for 8bits per channel images
- # on little endian machines. For everything else RGB[A] is
- # used.
- if len(shape) == 3 and isle and dtype.type == numpy.uint8:
- R = array[:, :, 0]
- G = array[:, :, 1]
- B = array[:, :, 2]
- wrapped_array[0] = n(B)
- wrapped_array[1] = n(G)
- wrapped_array[2] = n(R)
- if shape[2] == 4:
- A = array[:, :, 3]
- wrapped_array[3] = n(A)
- else:
- wrapped_array[:] = n(array)
- if self._need_finish:
- self._finish_wrapped_array(wrapped_array)
- if len(shape) == 2 and dtype.type == numpy.uint8:
- with self._fi as lib:
- palette = lib.FreeImage_GetPalette(self._bitmap)
- palette = ctypes.c_void_p(palette)
- if not palette:
- raise RuntimeError("Could not get image palette")
- try:
- palette_data = GREY_PALETTE.ctypes.data
- except Exception: # pragma: no cover - IS_PYPY
- palette_data = GREY_PALETTE.__array_interface__["data"][0]
- ctypes.memmove(palette, palette_data, 1024)
- def _wrap_bitmap_bits_in_array(self, shape, dtype, save):
- """Return an ndarray view on the data in a FreeImage bitmap. Only
- valid for as long as the bitmap is loaded (if single page) / locked
- in memory (if multipage). This is used in loading data, but
- also during saving, to prepare a strided numpy array buffer.
- """
- # Get bitmap info
- with self._fi as lib:
- pitch = lib.FreeImage_GetPitch(self._bitmap)
- bits = lib.FreeImage_GetBits(self._bitmap)
- # Get more info
- height = shape[-1]
- byte_size = height * pitch
- itemsize = dtype.itemsize
- # Get strides
- if len(shape) == 3:
- strides = (itemsize, shape[0] * itemsize, pitch)
- else:
- strides = (itemsize, pitch)
- # Create numpy array and return
- data = (ctypes.c_char * byte_size).from_address(bits)
- try:
- self._need_finish = False
- if TEST_NUMPY_NO_STRIDES:
- raise NotImplementedError()
- return numpy.ndarray(shape, dtype=dtype, buffer=data, strides=strides)
- except NotImplementedError:
- # IS_PYPY - not very efficient. We create a C-contiguous
- # numpy array (because pypy does not support Fortran-order)
- # and shape it such that the rest of the code can remain.
- if save:
- self._need_finish = True # Flag to use _finish_wrapped_array
- return numpy.zeros(shape, dtype=dtype)
- else:
- bb = bytes(bytearray(data))
- array = numpy.frombuffer(bb, dtype=dtype).copy()
- # Deal with strides
- if len(shape) == 3:
- array.shape = shape[2], strides[-1] // shape[0], shape[0]
- array2 = array[: shape[2], : shape[1], : shape[0]]
- array = numpy.zeros(shape, dtype=array.dtype)
- for i in range(shape[0]):
- array[i] = array2[:, :, i].T
- else:
- array.shape = shape[1], strides[-1]
- array = array[: shape[1], : shape[0]].T
- return array
- def _finish_wrapped_array(self, array): # IS_PYPY
- """Hardcore way to inject numpy array in bitmap."""
- # Get bitmap info
- with self._fi as lib:
- pitch = lib.FreeImage_GetPitch(self._bitmap)
- bits = lib.FreeImage_GetBits(self._bitmap)
- bpp = lib.FreeImage_GetBPP(self._bitmap)
- # Get channels and realwidth
- nchannels = bpp // 8 // array.itemsize
- realwidth = pitch // nchannels
- # Apply padding for pitch if necessary
- extra = realwidth - array.shape[-2]
- assert 0 <= extra < 10
- # Make sort of Fortran, also take padding (i.e. pitch) into account
- newshape = array.shape[-1], realwidth, nchannels
- array2 = numpy.zeros(newshape, array.dtype)
- if nchannels == 1:
- array2[:, : array.shape[-2], 0] = array.T
- else:
- for i in range(nchannels):
- array2[:, : array.shape[-2], i] = array[i, :, :].T
- # copy data
- data_ptr = array2.__array_interface__["data"][0]
- ctypes.memmove(bits, data_ptr, array2.nbytes)
- del array2
- def _get_type_and_shape(self):
- bitmap = self._bitmap
- # Get info on bitmap
- with self._fi as lib:
- w = lib.FreeImage_GetWidth(bitmap)
- h = lib.FreeImage_GetHeight(bitmap)
- self._fi_type = fi_type = lib.FreeImage_GetImageType(bitmap)
- if not fi_type:
- raise ValueError("Unknown image pixel type")
- # Determine required props for numpy array
- bpp = None
- dtype = FI_TYPES.dtypes[fi_type]
- if fi_type == FI_TYPES.FIT_BITMAP:
- with self._fi as lib:
- bpp = lib.FreeImage_GetBPP(bitmap)
- has_pallette = lib.FreeImage_GetColorsUsed(bitmap)
- if has_pallette:
- # Examine the palette. If it is grayscale, we return as such
- if has_pallette == 256:
- palette = lib.FreeImage_GetPalette(bitmap)
- palette = ctypes.c_void_p(palette)
- p = (ctypes.c_uint8 * (256 * 4)).from_address(palette.value)
- p = numpy.frombuffer(p, numpy.uint32).copy()
- if (GREY_PALETTE == p).all():
- extra_dims = []
- return numpy.dtype(dtype), extra_dims + [w, h], bpp
- # Convert bitmap and call this method again
- newbitmap = lib.FreeImage_ConvertTo32Bits(bitmap)
- newbitmap = ctypes.c_void_p(newbitmap)
- self._set_bitmap(newbitmap)
- return self._get_type_and_shape()
- elif bpp == 8:
- extra_dims = []
- elif bpp == 24:
- extra_dims = [3]
- elif bpp == 32:
- extra_dims = [4]
- else: # pragma: no cover
- # raise ValueError('Cannot convert %d BPP bitmap' % bpp)
- # Convert bitmap and call this method again
- newbitmap = lib.FreeImage_ConvertTo32Bits(bitmap)
- newbitmap = ctypes.c_void_p(newbitmap)
- self._set_bitmap(newbitmap)
- return self._get_type_and_shape()
- else:
- extra_dims = FI_TYPES.extra_dims[fi_type]
- # Return dtype and shape
- return numpy.dtype(dtype), extra_dims + [w, h], bpp
- def quantize(self, quantizer=0, palettesize=256):
- """Quantize the bitmap to make it 8-bit (paletted). Returns a new
- FIBitmap object.
- Only for 24 bit images.
- """
- with self._fi as lib:
- # New bitmap
- bitmap = lib.FreeImage_ColorQuantizeEx(
- self._bitmap, quantizer, palettesize, 0, None
- )
- bitmap = ctypes.c_void_p(bitmap)
- # Check and return
- if not bitmap:
- raise ValueError(
- 'Could not quantize bitmap "%s": %s'
- % (self._filename, self._fi._get_error_message())
- )
- new = FIBitmap(self._fi, self._filename, self._ftype, self._flags)
- new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
- new._fi_type = self._fi_type
- return new
- # def convert_to_32bit(self):
- # """ Convert to 32bit image.
- # """
- # with self._fi as lib:
- # # New bitmap
- # bitmap = lib.FreeImage_ConvertTo32Bits(self._bitmap)
- # bitmap = ctypes.c_void_p(bitmap)
- #
- # # Check and return
- # if not bitmap:
- # raise ValueError('Could not convert bitmap to 32bit "%s": %s' %
- # (self._filename,
- # self._fi._get_error_message()))
- # else:
- # new = FIBitmap(self._fi, self._filename, self._ftype,
- # self._flags)
- # new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
- # new._fi_type = self._fi_type
- # return new
- class FIMultipageBitmap(FIBaseBitmap):
- """Wrapper for the multipage FI bitmap object."""
- def load_from_filename(self, filename=None):
- if filename is None: # pragma: no cover
- filename = self._filename
- # Prepare
- create_new = False
- read_only = True
- keep_cache_in_memory = False
- # Try opening
- with self._fi as lib:
- # Create bitmap
- multibitmap = lib.FreeImage_OpenMultiBitmap(
- self._ftype,
- efn(filename),
- create_new,
- read_only,
- keep_cache_in_memory,
- self._flags,
- )
- multibitmap = ctypes.c_void_p(multibitmap)
- # Check
- if not multibitmap: # pragma: no cover
- err = self._fi._get_error_message()
- raise ValueError(
- 'Could not open file "%s" as multi-image: %s'
- % (self._filename, err)
- )
- self._set_bitmap(multibitmap, (lib.FreeImage_CloseMultiBitmap, multibitmap))
- # def load_from_bytes(self, bb):
- # with self._fi as lib:
- # # Create bitmap
- # fimemory = lib.FreeImage_OpenMemory(
- # ctypes.c_char_p(bb), len(bb))
- # multibitmap = lib.FreeImage_LoadMultiBitmapFromMemory(
- # self._ftype, ctypes.c_void_p(fimemory), self._flags)
- # multibitmap = ctypes.c_void_p(multibitmap)
- # #lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
- # self._mem = fimemory
- # self._bytes = bb
- # # Check
- # if not multibitmap:
- # raise ValueError('Could not load multibitmap "%s": %s'
- # % (self._filename, self._fi._get_error_message()))
- # else:
- # self._set_bitmap(multibitmap,
- # (lib.FreeImage_CloseMultiBitmap, multibitmap))
- def save_to_filename(self, filename=None):
- if filename is None: # pragma: no cover
- filename = self._filename
- # Prepare
- create_new = True
- read_only = False
- keep_cache_in_memory = False
- # Open the file
- # todo: Set flags at close func
- with self._fi as lib:
- multibitmap = lib.FreeImage_OpenMultiBitmap(
- self._ftype,
- efn(filename),
- create_new,
- read_only,
- keep_cache_in_memory,
- 0,
- )
- multibitmap = ctypes.c_void_p(multibitmap)
- # Check
- if not multibitmap: # pragma: no cover
- msg = 'Could not open file "%s" for writing multi-image: %s' % (
- self._filename,
- self._fi._get_error_message(),
- )
- raise ValueError(msg)
- self._set_bitmap(multibitmap, (lib.FreeImage_CloseMultiBitmap, multibitmap))
- def __len__(self):
- with self._fi as lib:
- return lib.FreeImage_GetPageCount(self._bitmap)
- def get_page(self, index):
- """Return the sub-bitmap for the given page index.
- Please close the returned bitmap when done.
- """
- with self._fi as lib:
- # Create low-level bitmap in freeimage
- bitmap = lib.FreeImage_LockPage(self._bitmap, index)
- bitmap = ctypes.c_void_p(bitmap)
- if not bitmap: # pragma: no cover
- raise ValueError(
- "Could not open sub-image %i in %r: %s"
- % (index, self._filename, self._fi._get_error_message())
- )
- # Get bitmap object to wrap this bitmap
- bm = FIBitmap(self._fi, self._filename, self._ftype, self._flags)
- bm._set_bitmap(
- bitmap, (lib.FreeImage_UnlockPage, self._bitmap, bitmap, False)
- )
- return bm
- def append_bitmap(self, bitmap):
- """Add a sub-bitmap to the multi-page bitmap."""
- with self._fi as lib:
- # no return value
- lib.FreeImage_AppendPage(self._bitmap, bitmap._bitmap)
- # Create instance
- fi = Freeimage()
|