You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
10 KiB
Python
251 lines
10 KiB
Python
""":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]
|