""":mod:`wand.api` --- Low-level interfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionchanged:: 0.1.10 Changed to throw :exc:`~exceptions.ImportError` instead of :exc:`~exceptions.AttributeError` when the shared library fails to load. """ import ctypes import ctypes.util import itertools import os import os.path import platform import sys import traceback # Forward import for backwards compatibility. from .cdefs.structures import (AffineMatrix, MagickPixelPacket, PixelInfo, PointInfo) if platform.system() == "Windows": try: import winreg except ImportError: import _winreg as winreg __all__ = ('AffineMatrix', 'MagickPixelPacket', 'library', 'libc', 'libmagick', 'load_library', 'PixelInfo', 'PointInfo') def library_paths(): """Iterates for library paths to try loading. The result paths are not guaranteed that they exist. :returns: a pair of libwand and libmagick paths. they can be the same. path can be ``None`` as well :rtype: :class:`tuple` """ libwand = None libmagick = None versions = '', '-7', '-7.Q8', '-7.Q16', '-6', '-Q16', '-Q8', '-6.Q16' options = '', 'HDRI', 'HDRI-2' system = platform.system() magick_home = os.environ.get('MAGICK_HOME') magick_suffix = os.environ.get('WAND_MAGICK_LIBRARY_SUFFIX') if system == 'Windows': # ImageMagick installers normally install coder and filter DLLs in # subfolders, we need to add those folders to PATH, otherwise loading # the DLL later will fail. try: with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\ImageMagick\Current") as reg_key: libPath = winreg.QueryValueEx(reg_key, "LibPath") coderPath = winreg.QueryValueEx(reg_key, "CoderModulesPath") filterPath = winreg.QueryValueEx(reg_key, "FilterModulesPath") magick_home = libPath[0] os.environ['PATH'] += str((';' + libPath[0] + ";" + coderPath[0] + ";" + filterPath[0])) except OSError: # otherwise use MAGICK_HOME, and we assume the coder and # filter DLLs are in the same directory pass def magick_path(path): return os.path.join(magick_home, *path) combinations = itertools.product(versions, options) suffixes = list() if magick_suffix: suffixes = str(magick_suffix).split(';') # We need to convert the ``combinations`` generator to a list so we can # iterate over it twice. suffixes.extend(list(version + option for version, option in combinations)) if magick_home: # exhaustively search for libraries in magick_home before calling # find_library. for suffix in suffixes: # On Windows, the API is split between two libs. On other # platforms, it's all contained in one. if system == 'Windows': libwand = 'CORE_RL_wand_{0}.dll'.format(suffix), libmagick = 'CORE_RL_magick_{0}.dll'.format(suffix), yield magick_path(libwand), magick_path(libmagick) libwand = 'CORE_RL_MagickWand_{0}.dll'.format(suffix), libmagick = 'CORE_RL_MagickCore_{0}.dll'.format(suffix), yield magick_path(libwand), magick_path(libmagick) libwand = 'libMagickWand{0}.dll'.format(suffix), libmagick = 'libMagickCore{0}.dll'.format(suffix), yield magick_path(libwand), magick_path(libmagick) elif system == 'Darwin': libwand = 'lib', 'libMagickWand{0}.dylib'.format(suffix), yield magick_path(libwand), magick_path(libwand) else: libwand = 'lib', 'libMagickWand{0}.so'.format(suffix), libmagick = 'lib', 'libMagickCore{0}.so'.format(suffix), yield magick_path(libwand), magick_path(libmagick) libwand = 'lib', 'libMagickWand{0}.so.9'.format(suffix), libmagick = 'lib', 'libMagickCore{0}.so.9'.format(suffix), yield magick_path(libwand), magick_path(libmagick) libwand = 'lib', 'libMagickWand{0}.so.6'.format(suffix), libmagick = 'lib', 'libMagickCore{0}.so.6'.format(suffix), yield magick_path(libwand), magick_path(libmagick) for suffix in suffixes: if system == 'Windows': libwand = ctypes.util.find_library('CORE_RL_wand_' + suffix) libmagick = ctypes.util.find_library('CORE_RL_magick_' + suffix) yield libwand, libmagick libwand = ctypes.util.find_library('CORE_RL_MagickWand_' + suffix) libmagick = ctypes.util.find_library( 'CORE_RL_MagickCore_' + suffix ) yield libwand, libmagick libwand = ctypes.util.find_library('libMagickWand' + suffix) libmagick = ctypes.util.find_library('libMagickCore' + suffix) yield libwand, libmagick else: libmagick = ctypes.util.find_library('MagickCore' + suffix) libwand = ctypes.util.find_library('MagickWand' + suffix) if libmagick is not None: yield libwand, libmagick yield libwand, libwand def load_library(): """Loads the MagickWand library. :returns: the MagickWand library and the ImageMagick library :rtype: :class:`ctypes.CDLL` """ tried_paths = [] for libwand_path, libmagick_path in library_paths(): if libwand_path is None or libmagick_path is None: continue try: tried_paths.append(libwand_path) libwand = ctypes.CDLL(str(libwand_path)) if libwand_path == libmagick_path: libmagick = libwand else: tried_paths.append(libmagick_path) libmagick = ctypes.CDLL(str(libmagick_path)) except (IOError, OSError): continue return libwand, libmagick raise IOError('cannot find library; tried paths: ' + repr(tried_paths)) try: # Preserve the module itself even if it fails to import sys.modules['wand._api'] = sys.modules['wand.api'] except KeyError: # Loading the module locally or a non-standard setting pass try: libraries = load_library() except (OSError, IOError): msg = 'https://docs.wand-py.org/en/latest/guide/install.html' if sys.platform.startswith(('dragonfly', 'freebsd')): msg = 'pkg install' elif sys.platform == 'win32': msg += '#install-imagemagick-on-windows' elif sys.platform == 'darwin': mac_pkgmgrs = {'brew': 'brew install freetype imagemagick', 'port': 'port install imagemagick'} for pkgmgr in mac_pkgmgrs: with os.popen('which ' + pkgmgr) as f: if f.read().strip(): msg = mac_pkgmgrs[pkgmgr] break else: msg += '#install-imagemagick-on-mac' elif hasattr(platform, 'linux_distribution'): distname, _, __ = platform.linux_distribution() distname = (distname or '').lower() if distname in ('debian', 'ubuntu'): msg = 'apt-get install libmagickwand-dev' elif distname in ('fedora', 'centos', 'redhat'): msg = 'yum install ImageMagick-devel' raise ImportError('MagickWand shared library not found.\n' 'You probably had not installed ImageMagick library.\n' 'Try to install:\n ' + msg) #: (:class:`ctypes.CDLL`) The MagickWand library. library = libraries[0] #: (:class:`ctypes.CDLL`) The ImageMagick library. It is the same with #: :data:`library` on platforms other than Windows. #: #: .. versionadded:: 0.1.10 libmagick = libraries[1] try: from wand.cdefs import (core, drawing_wand, magick_image, magick_property, magick_wand, pixel_iterator, pixel_wand) core.load(libmagick) # Let's get the magick-version number to pass to load methods. IM_VERSION = ctypes.c_size_t() libmagick.GetMagickVersion(ctypes.byref(IM_VERSION)) # Query Quantum Depth (i.e. Q8, Q16, ... etc). IM_QUANTUM_DEPTH = ctypes.c_size_t() libmagick.GetMagickQuantumDepth(ctypes.byref(IM_QUANTUM_DEPTH)) # Does the library support HDRI? IM_HDRI = 'HDRI' in str(libmagick.GetMagickFeatures()) core.load_with_version(libmagick, IM_VERSION.value) magick_wand.load(library, IM_VERSION.value) magick_property.load(library, IM_VERSION.value) magick_image.load(library, IM_VERSION.value) pixel_iterator.load(library, IM_VERSION.value) pixel_wand.load(library, IM_VERSION.value, IM_QUANTUM_DEPTH.value, IM_HDRI) drawing_wand.load(library, IM_VERSION.value) del IM_HDRI, IM_QUANTUM_DEPTH, IM_VERSION except AttributeError: raise ImportError('MagickWand shared library not found or incompatible\n' 'Original exception was raised in:\n' + traceback.format_exc()) #: (:class:`ctypes.CDLL`) The C standard library. libc = None if platform.system() == 'Windows': msvcrt = ctypes.util.find_msvcrt() # workaround -- the newest visual studio DLL is named differently: if not msvcrt and '1900' in platform.python_compiler(): msvcrt = 'vcruntime140.dll' if msvcrt: libc = ctypes.CDLL(msvcrt) else: libc_path = ctypes.util.find_library('c') if libc_path: libc = ctypes.cdll.LoadLibrary(libc_path) else: # Attempt to guess popular versions of libc libc_paths = ('libc.so.6', 'libc.so', 'libc.a', 'libc.dylib', '/usr/lib/libc.dylib') for libc_path in libc_paths: try: libc = ctypes.cdll.LoadLibrary(libc_path) break except (IOError, OSError): continue if libc: libc.fdopen.argtypes = [ctypes.c_int, ctypes.c_char_p] libc.fdopen.restype = ctypes.c_void_p libc.fflush.argtypes = [ctypes.c_void_p]