""":mod:`wand.image` --- Image objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Opens and manipulates images. Image objects can be used in :keyword:`with`
statement, and these resources will be automatically managed (even if any
error happened)::

    with Image(filename='pikachu.png') as i:
        print('width =', i.width)
        print('height =', i.height)

"""
import ctypes
import functools
import numbers
import weakref
from collections import abc
from io import IOBase

from . import assertions
from .api import libc, libmagick, library
from .cdefs.structures import (AffineMatrix, CCObjectInfo, CCObjectInfo70A,
                               CCObjectInfo710, ChannelFeature, GeometryInfo,
                               PixelInfo, RectangleInfo)
from .color import Color
from .compat import binary, encode_filename, text, to_bytes
from .exceptions import (MissingDelegateError, WandException,
                         WandLibraryVersionError, WandRuntimeError)
from .font import Font
from .resource import DestroyedResourceError, Resource, genesis
from .version import MAGICK_HDRI, MAGICK_VERSION_NUMBER

__all__ = ('ALPHA_CHANNEL_TYPES', 'AUTO_THRESHOLD_METHODS', 'CHANNELS',
           'COLORSPACE_TYPES', 'COMPARE_METRICS', 'COMPOSITE_OPERATORS',
           'COMPRESSION_TYPES', 'DISPOSE_TYPES', 'DISTORTION_METHODS',
           'DITHER_METHODS', 'EVALUATE_OPS', 'FILTER_TYPES', 'FUNCTION_TYPES',
           'GRAVITY_TYPES', 'IMAGE_LAYER_METHOD', 'IMAGE_TYPES',
           'INTERLACE_TYPES', 'KERNEL_INFO_TYPES', 'MORPHOLOGY_METHODS',
           'NOISE_TYPES', 'ORIENTATION_TYPES', 'PAPERSIZE_MAP',
           'PIXEL_INTERPOLATE_METHODS', 'RENDERING_INTENT_TYPES',
           'SPARSE_COLOR_METHODS', 'STATISTIC_TYPES',
           'STORAGE_TYPES', 'VIRTUAL_PIXEL_METHOD', 'UNIT_TYPES',
           'BaseImage', 'ChannelDepthDict', 'ChannelImageDict',
           'ClosedImageError', 'HistogramDict', 'Image', 'ImageProperty',
           'Iterator', 'Metadata', 'OptionDict', 'manipulative',
           'ArtifactTree', 'ProfileDict', 'ConnectedComponentObject')


#: (:class:`tuple`) The list of :attr:`~wand.image.BaseImage.alpha_channel`
#: types.
#:
#: - ``'undefined'``
#: - ``'activate'``
#: - ``'background'``
#: - ``'copy'``
#: - ``'deactivate'``
#: - ``'discrete'`` - Only available in ImageMagick-7
#: - ``'extract'``
#: - ``'off'`` - Only available in ImageMagick-7
#: - ``'on'`` - Only available in ImageMagick-7
#: - ``'opaque'``
#: - ``'reset'`` - Only available in ImageMagick-6
#: - ``'set'``
#: - ``'shape'``
#: - ``'transparent'``
#: - ``'flatten'`` - Only available in ImageMagick-6
#: - ``'remove'``
#:
#: .. seealso::
#:    `ImageMagick Image Channel`__
#:       Describes the SetImageAlphaChannel method which can be used
#:       to modify alpha channel. Also describes AlphaChannelType
#:
#:    __ http://www.imagemagick.org/api/channel.php#SetImageAlphaChannel
ALPHA_CHANNEL_TYPES = ('undefined', 'activate', 'background', 'copy',
                       'deactivate', 'extract', 'opaque', 'reset', 'set',
                       'shape', 'transparent', 'flatten', 'remove',
                       'associate', 'disassociate')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    ALPHA_CHANNEL_TYPES = ('undefined', 'activate', 'associate', 'background',
                           'copy', 'deactivate', 'discrete', 'disassociate',
                           'extract', 'off', 'on', 'opaque', 'remove', 'set',
                           'shape', 'transparent')


#: (:class:`tuple`) The list of methods used by
#: :meth:`Image.auto_threshold() <wand.image.BaseImage.auto_threshold>`
#:
#: - ``'undefined'``
#: - ``'kapur'``
#: - ``'otsu'``
#: - ``'triangle'``
#:
#: .. versionadded:: 0.5.5
AUTO_THRESHOLD_METHODS = ('undefined', 'kapur', 'otsu', 'triangle')


#: (:class:`dict`) The dictionary of channel types.
#:
#: - ``'undefined'``
#: - ``'red'``
#: - ``'gray'``
#: - ``'cyan'``
#: - ``'green'``
#: - ``'magenta'``
#: - ``'blue'``
#: - ``'yellow'``
#: - ``'alpha'``
#: - ``'opacity'``
#: - ``'black'``
#: - ``'index'``
#: - ``'composite_channels'``
#: - ``'all_channels'``
#: - ``'sync_channels'``
#: - ``'default_channels'``
#:
#: .. seealso::
#:
#:    `ImageMagick Color Channels`__
#:       Lists the various channel types with descriptions of each
#:
#:    __ http://www.imagemagick.org/Magick++/Enumerations.html#ChannelType
#:
#: .. versionchanged:: 0.5.5
#:    Deprecated ``true_alpha``, ``rgb_channels``, and ``gray_channels``
#:    values in favor of MagickCore channel parser.
#:
CHANNELS = dict(undefined=0, red=1, gray=1, cyan=1, green=2, magenta=2,
                blue=4, yellow=4, alpha=8, opacity=8, black=32, index=32,
                composite_channels=47, all_channels=134217727, true_alpha=64,
                rgb=7, rgb_channels=7, gray_channels=1, sync_channels=256,
                default_channels=134217719)
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    CHANNELS = dict(undefined=0, red=1, gray=1, cyan=1, green=2, magenta=2,
                    blue=4, yellow=4, black=8, alpha=16, opacity=16, index=32,
                    readmask=0x0040, write_mask=128, meta=256,
                    composite_channels=31, all_channels=134217727,
                    true_alpha=256, rgb=7, rgb_channels=7, gray_channels=1,
                    sync_channels=131072, default_channels=134217727)


#: (:class:`tuple`) The list of colorspaces.
#:
#: - ``'undefined'``
#: - ``'rgb'``
#: - ``'gray'``
#: - ``'transparent'``
#: - ``'ohta'``
#: - ``'lab'``
#: - ``'xyz'``
#: - ``'ycbcr'``
#: - ``'ycc'``
#: - ``'yiq'``
#: - ``'ypbpr'``
#: - ``'yuv'``
#: - ``'cmyk'``
#: - ``'srgb'``
#: - ``'hsb'``
#: - ``'hsl'``
#: - ``'hwb'``
#: - ``'rec601luma'`` - Only available with ImageMagick-6
#: - ``'rec601ycbcr'``
#: - ``'rec709luma'`` - Only available with ImageMagick-6
#: - ``'rec709ycbcr'``
#: - ``'log'``
#: - ``'cmy'``
#: - ``'luv'``
#: - ``'hcl'``
#: - ``'lch'``
#: - ``'lms'``
#: - ``'lchab'``
#: - ``'lchuv'``
#: - ``'scrgb'``
#: - ``'hsi'``
#: - ``'hsv'``
#: - ``'hclp'``
#: - ``'xyy'`` - Only available with ImageMagick-7
#: - ``'ydbdr'``
#:
#: .. seealso::
#:
#:    `ImageMagick Color Management`__
#:       Describes the ImageMagick color management operations
#:
#:    __ http://www.imagemagick.org/script/color-management.php
#:
#: .. versionadded:: 0.3.4
COLORSPACE_TYPES = ('undefined', 'rgb', 'gray', 'transparent', 'ohta', 'lab',
                    'xyz', 'ycbcr', 'ycc', 'yiq', 'ypbpr', 'yuv', 'cmyk',
                    'srgb', 'hsb', 'hsl', 'hwb', 'rec601luma', 'rec601ycbcr',
                    'rec709luma', 'rec709ycbcr', 'log', 'cmy', 'luv', 'hcl',
                    'lch', 'lms', 'lchab', 'lchuv', 'scrgb', 'hsi', 'hsv',
                    'hclp', 'ydbdr')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    COLORSPACE_TYPES = ('undefined', 'cmy', 'cmyk', 'gray', 'hcl', 'hclp',
                        'hsb', 'hsi', 'hsl', 'hsv', 'hwb', 'lab', 'lch',
                        'lchab', 'lchuv', 'log', 'lms', 'luv', 'ohta',
                        'rec601ycbcr', 'rec709ycbcr', 'rgb', 'scrgb', 'srgb',
                        'transparent', 'xyy', 'xyz', 'ycbcr', 'ycc', 'ydbdr',
                        'yiq', 'ypbpr', 'yuv')

#: (:class:`tuple`) The list of compare metric types used by
#: :meth:`Image.compare() <wand.image.BaseImage.compare>` and
#: :meth:`Image.similarity() <wand.image.BaseImage.similarity>` methods.
#:
#: - ``'undefined'``
#: - ``'absolute'``
#: - ``'fuzz'``
#: - ``'mean_absolute'``
#: - ``'mean_error_per_pixel'``
#: - ``'mean_squared'``
#: - ``'normalized_cross_correlation'``
#: - ``'peak_absolute'``
#: - ``'peak_signal_to_noise_ratio'``
#: - ``'perceptual_hash'`` - Available with ImageMagick-7
#: - ``'root_mean_square'``
#: - ``'structural_similarity'`` - Available with ImageMagick-7
#: - ``'structural_dissimilarity'`` - Available with ImageMagick-7
#:
#: .. seealso::
#:
#:    `ImageMagick Compare Operations`__
#:
#:    __ http://www.imagemagick.org/Usage/compare/
#:
#: .. versionadded:: 0.4.3
#:
#: .. versionchanged:: 0.5.4 - Remapped :c:data:`MetricType` enum.
COMPARE_METRICS = ('undefined', 'absolute',
                   'mean_absolute', 'mean_error_per_pixel',
                   'mean_squared', 'peak_absolute',
                   'peak_signal_to_noise_ratio', 'root_mean_square',
                   'normalized_cross_correlation', 'fuzz')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    COMPARE_METRICS = ('undefined', 'absolute', 'fuzz', 'mean_absolute',
                       'mean_error_per_pixel', 'mean_squared',
                       'normalized_cross_correlation', 'peak_absolute',
                       'peak_signal_to_noise_ratio', 'perceptual_hash',
                       'root_mean_square', 'structural_similarity',
                       'structural_dissimilarity')


#: (:class:`tuple`) The list of complex operators used by
#: :meth:`Image.complex() <wand.image.BaseImage.complex>`.
#:
#: - ``'undefined'``
#: - ``'add'``
#: - ``'conjugate'``
#: - ``'divide'``
#: - ``'magnitude',``
#: - ``'multiply'``
#: - ``'real_imaginary'``
#: - ``'subtract'``
#:
#: .. versionadded:: 0.5.5
COMPLEX_OPERATORS = ('undefined', 'add', 'conjugate', 'divide', 'magnitude',
                     'multiply', 'real_imaginary', 'subtract')


#: (:class:`tuple`) The list of composition operators
#:
#: - ``'undefined'``
#: - ``'alpha'`` - Only available with ImageMagick-7
#: - ``'atop'``
#: - ``'blend'``
#: - ``'blur'``
#: - ``'bumpmap'``
#: - ``'change_mask'``
#: - ``'clear'``
#: - ``'color_burn'``
#: - ``'color_dodge'``
#: - ``'colorize'``
#: - ``'copy_black'``
#: - ``'copy_blue'``
#: - ``'copy'``
#: - ``'copy_alpha'`` - Only available with ImageMagick-7
#: - ``'copy_cyan'``
#: - ``'copy_green'``
#: - ``'copy_magenta'``
#: - ``'copy_opacity'`` - Only available with ImageMagick-6
#: - ``'copy_red'``
#: - ``'copy_yellow'``
#: - ``'darken'``
#: - ``'darken_intensity'``
#: - ``'difference'``
#: - ``'displace'``
#: - ``'dissolve'``
#: - ``'distort'``
#: - ``'divide_dst'``
#: - ``'divide_src'``
#: - ``'dst_atop'``
#: - ``'dst'``
#: - ``'dst_in'``
#: - ``'dst_out'``
#: - ``'dst_over'``
#: - ``'exclusion'``
#: - ``'freeze'`` - Added with ImageMagick-7.0.10
#: - ``'hard_light'``
#: - ``'hard_mix'``
#: - ``'hue'``
#: - ``'in'``
#: - ``'intensity'`` - Only available with ImageMagick-7
#: - ``'interpolate'`` - Added with ImageMagick-7.0.10
#: - ``'lighten'``
#: - ``'lighten_intensity'``
#: - ``'linear_burn'``
#: - ``'linear_dodge'``
#: - ``'linear_light'``
#: - ``'luminize'``
#: - ``'mathematics'``
#: - ``'minus_dst'``
#: - ``'minus_src'``
#: - ``'modulate'``
#: - ``'modulus_add'``
#: - ``'modulus_subtract'``
#: - ``'multiply'``
#: - ``'negate'`` - Added with ImageMagick-7.0.10
#: - ``'no'``
#: - ``'out'``
#: - ``'over'``
#: - ``'overlay'``
#: - ``'pegtop_light'``
#: - ``'pin_light'``
#: - ``'plus'``
#: - ``'reflect'`` - Added with ImageMagick-7.0.10
#: - ``'replace'``
#: - ``'rmse'`` - Added with ImageMagick-7.1.0
#: - ``'saliency_blend'`` - Added with ImageMagick-7.1.1
#: - ``'saturate'``
#: - ``'screen'``
#: - ``'seamless_blend'`` - Added with ImageMagick-7.1.1
#: - ``'soft_burn'`` - Added with ImageMagick-7.0.10
#: - ``'soft_dudge'`` - Added with ImageMagick-7.0.10
#: - ``'soft_light'``
#: - ``'src_atop'``
#: - ``'src'``
#: - ``'src_in'``
#: - ``'src_out'``
#: - ``'src_over'``
#: - ``'stamp'`` - Added with ImageMagick-7.0.10
#: - ``'stereo'``
#: - ``'threshold'``
#: - ``'vivid_light'``
#: - ``'xor'``
#:
#: .. versionchanged:: 0.3.0
#:    Renamed from :const:`COMPOSITE_OPS` to :const:`COMPOSITE_OPERATORS`.
#:
#: .. versionchanged:: 0.5.6
#:    Operators have been updated to reflect latest changes in C-API.
#:    For ImageMagick-6, ``'add'`` has been renamed to ``'modulus_add'``,
#:    ``'subtract'`` has been renamed to ``'modulus_subtract'``,
#:    ``'divide'`` has been split into ``'divide_dst'`` & ``'divide_src'``, and
#:    ``'minus'`` has been split into ``'minus_dst'`` & ``'minus_src'``.
#:
#: .. seealso::
#:
#:    `Compositing Images`__ ImageMagick v6 Examples
#:       Image composition is the technique of combining images that have,
#:       or do not have, transparency or an alpha channel.
#:       This is usually performed using the IM :program:`composite` command.
#:       It may also be performed as either part of a larger sequence of
#:       operations or internally by other image operators.
#:
#:    `ImageMagick Composition Operators`__
#:       Demonstrates the results of applying the various composition
#:       composition operators.
#:
#:    __ http://www.imagemagick.org/Usage/compose/
#:    __ http://www.rubblewebs.co.uk/imagemagick/operators/compose.php
COMPOSITE_OPERATORS = (
    'undefined', 'no', 'modulus_add', 'atop', 'blend', 'bumpmap',
    'change_mask', 'clear', 'color_burn', 'color_dodge', 'colorize',
    'copy_black', 'copy_blue', 'copy', 'copy_cyan', 'copy_green',
    'copy_magenta', 'copy_opacity', 'copy_red', 'copy_yellow', 'darken',
    'dst_atop', 'dst', 'dst_in', 'dst_out', 'dst_over', 'difference',
    'displace', 'dissolve', 'exclusion', 'hard_light', 'hue', 'in', 'lighten',
    'linear_light', 'luminize', 'minus_dst', 'modulate', 'multiply', 'out',
    'over', 'overlay', 'plus', 'replace', 'saturate', 'screen', 'soft_light',
    'src_atop', 'src', 'src_in', 'src_out', 'src_over', 'modulus_subtract',
    'threshold', 'xor', 'divide_dst', 'distort', 'blur', 'pegtop_light',
    'vivid_light', 'pin_light', 'linear_dodge', 'linear_burn', 'mathematics',
    'divide_src', 'minus_src', 'darken_intensity', 'lighten_intensity',
    'hard_mix', 'stereo'
)
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    COMPOSITE_OPERATORS = (
        'undefined', 'alpha', 'atop', 'blend', 'blur', 'bumpmap',
        'change_mask', 'clear', 'color_burn', 'color_dodge', 'colorize',
        'copy_black', 'copy_blue', 'copy', 'copy_cyan', 'copy_green',
        'copy_magenta', 'copy_alpha', 'copy_red', 'copy_yellow', 'darken',
        'darken_intensity', 'difference', 'displace', 'dissolve', 'distort',
        'divide_dst', 'divide_src', 'dst_atop', 'dst', 'dst_in', 'dst_out',
        'dst_over', 'exclusion', 'hard_light', 'hard_mix', 'hue', 'in',
        'intensity', 'lighten', 'lighten_intensity', 'linear_burn',
        'linear_dodge', 'linear_light', 'luminize', 'mathematics', 'minus_dst',
        'minus_src', 'modulate', 'modulus_add', 'modulus_subtract', 'multiply',
        'no', 'out', 'over', 'overlay', 'pegtop_light', 'pin_light', 'plus',
        'replace', 'saturate', 'screen', 'soft_light', 'src_atop', 'src',
        'src_in', 'src_out', 'src_over', 'threshold', 'vivid_light', 'xor',
        'stereo', 'freeze', 'interpolate', 'negate', 'reflect', 'soft_burn',
        'soft_dodge', 'stamp', 'rmse', 'saliency_blend', 'seamless_blend'
    )

#: (:class:`tuple`) The list of :attr:`Image.compression` types.
#:
#: - ``'undefined'``
#: - ``'b44a'``
#: - ``'b44'``
#: - ``'bzip'``
#: - ``'dxt1'``
#: - ``'dxt3'``
#: - ``'dxt5'``
#: - ``'fax'``
#: - ``'group4'``
#: - ``'jbig1'``
#: - ``'jbig2'``
#: - ``'jpeg2000'``
#: - ``'jpeg'``
#: - ``'losslessjpeg'``
#: - ``'lzma'``
#: - ``'lzw'``
#: - ``'no'``
#: - ``'piz'``
#: - ``'pxr24'``
#: - ``'rle'``
#: - ``'zip'``
#: - ``'zips'``
#:
#: .. versionadded:: 0.3.6
#: .. versionchanged:: 0.5.0
#:    Support for ImageMagick-6 & ImageMagick-7
COMPRESSION_TYPES = (
    'undefined', 'no', 'bzip', 'dxt1', 'dxt3', 'dxt5',
    'fax', 'group4', 'jpeg', 'jpeg2000', 'losslessjpeg',
    'lzw', 'rle', 'zip', 'zips', 'piz', 'pxr24', 'b44',
    'b44a', 'lzma', 'jbig1', 'jbig2'
)
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    COMPRESSION_TYPES = (
        'undefined', 'b44a', 'b44', 'bzip', 'dxt1', 'dxt3', 'dxt5', 'fax',
        'group4', 'jbig1', 'jbig2', 'jpeg2000', 'jpeg', 'losslessjpeg',
        'lzma', 'lzw', 'no', 'piz', 'pxr24', 'rle', 'zip', 'zips'
    )

#: (:class:`tuple`) The list of :attr:`BaseImage.dispose` types.
#:
#: - ``'undefined'``
#: - ``'none'``
#: - ``'background'``
#: - ``'previous'``
#:
#: .. versionadded:: 0.5.0
DISPOSE_TYPES = (
    'undefined',
    'none',
    'background',
    'previous'
)


#: (:class:`tuple`) The list of :meth:`BaseImage.distort` methods.
#:
#: - ``'undefined'``
#: - ``'affine'``
#: - ``'affine_projection'``
#: - ``'scale_rotate_translate'``
#: - ``'perspective'``
#: - ``'perspective_projection'``
#: - ``'bilinear_forward'``
#: - ``'bilinear_reverse'``
#: - ``'polynomial'``
#: - ``'arc'``
#: - ``'polar'``
#: - ``'depolar'``
#: - ``'cylinder_2_plane'``
#: - ``'plane_2_cylinder'``
#: - ``'barrel'``
#: - ``'barrel_inverse'``
#: - ``'shepards'``
#: - ``'resize'``
#: - ``'sentinel'``
#: - ``'rigidaffine'`` - Only available with ImageMagick-7.0.10, or later.
#:
#: .. versionadded:: 0.4.1
DISTORTION_METHODS = (
    'undefined', 'affine', 'affine_projection', 'scale_rotate_translate',
    'perspective', 'perspective_projection', 'bilinear_forward',
    'bilinear_reverse', 'polynomial', 'arc', 'polar', 'depolar',
    'cylinder_2_plane', 'plane_2_cylinder', 'barrel', 'barrel_inverse',
    'shepards', 'resize', 'sentinel', 'rigidaffine'
)


#: (:class:`tuple`) The list of Dither methods. Used by
#: :meth:`Image.posterize() <BaseImage.posterize>` and
#: :meth:`Image.remap() <BaseImage.remap>` methods.
#:
#: - ``'undefined'``
#: - ``'no'``
#: - ``'riemersma'``
#: - ``'floyd_steinberg'``
#:
#: .. versionadded:: 0.5.0
DITHER_METHODS = ('undefined', 'no', 'riemersma', 'floyd_steinberg')


#: (:class:`tuple`) The list of evaluation operators. Used by
#: :meth:`Image.evaluate() <BaseImage.evaluate>` method.
#:
#: - ``'undefined'``
#: - ``'abs'``
#: - ``'add'``
#: - ``'addmodulus'``
#: - ``'and'``
#: - ``'cosine'``
#: - ``'divide'``
#: - ``'exponential'``
#: - ``'gaussiannoise'``
#: - ``'impulsenoise'``
#: - ``'inverse_log'`` - Added with ImageMagick-7.0.10-24
#: - ``'laplaciannoise'``
#: - ``'leftshift'``
#: - ``'log'``
#: - ``'max'``
#: - ``'mean'``
#: - ``'median'``
#: - ``'min'``
#: - ``'multiplicativenoise'``
#: - ``'multiply'``
#: - ``'or'``
#: - ``'poissonnoise'``
#: - ``'pow'``
#: - ``'rightshift'``
#: - ``'set'``
#: - ``'sine'``
#: - ``'subtract'``
#: - ``'sum'``
#: - ``'threshold'``
#: - ``'thresholdblack'``
#: - ``'thresholdwhite'``
#: - ``'uniformnoise'``
#: - ``'xor'``
#:
#: .. seealso::
#:
#:    `ImageMagick Image Evaluation Operators`__
#:       Describes the MagickEvaluateImageChannel method and lists the
#:       various evaluations operators
#:
#:    __ http://www.magickwand.org/MagickEvaluateImage.html
EVALUATE_OPS = ('undefined', 'add', 'and', 'divide', 'leftshift', 'max',
                'min', 'multiply', 'or', 'rightshift', 'set', 'subtract',
                'xor', 'pow', 'log', 'threshold', 'thresholdblack',
                'thresholdwhite', 'gaussiannoise', 'impulsenoise',
                'laplaciannoise', 'multiplicativenoise', 'poissonnoise',
                'uniformnoise', 'cosine', 'sine', 'addmodulus', 'mean',
                'abs', 'exponential', 'median', 'sum', 'rootmeansquare')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    EVALUATE_OPS = ('undefined', 'abs', 'add', 'addmodulus', 'and', 'cosine',
                    'divide', 'exponential', 'gaussiannoise', 'impulsenoise',
                    'laplaciannoise', 'leftshift', 'log', 'max', 'mean',
                    'median', 'min', 'multiplicativenoise', 'multiply', 'or',
                    'poissonnoise', 'pow', 'rightshift', 'rootmeansquare',
                    'set', 'sine', 'subtract', 'sum', 'thresholdblack',
                    'threshold', 'thresholdwhite', 'uniformnoise', 'xor',
                    'inverse_log')


#: (:class:`tuple`) The list of filter types. Used by
#: :meth:`Image.resample() <BaseImage.resample>` and
#: :meth:`Image.resize() <BaseImage.resize>` methods.
#:
#: - ``'undefined'``
#: - ``'point'``
#: - ``'box'``
#: - ``'triangle'``
#: - ``'hermite'``
#: - ``'hanning'``
#: - ``'hamming'``
#: - ``'blackman'``
#: - ``'gaussian'``
#: - ``'quadratic'``
#: - ``'cubic'``
#: - ``'catrom'``
#: - ``'mitchell'``
#: - ``'jinc'``
#: - ``'sinc'``
#: - ``'sincfast'``
#: - ``'kaiser'``
#: - ``'welsh'``
#: - ``'parzen'``
#: - ``'bohman'``
#: - ``'bartlett'``
#: - ``'lagrange'``
#: - ``'lanczos'``
#: - ``'lanczossharp'``
#: - ``'lanczos2'``
#: - ``'lanczos2sharp'``
#: - ``'robidoux'``
#: - ``'robidouxsharp'``
#: - ``'cosine'``
#: - ``'spline'``
#: - ``'sentinel'``
#:
#: .. seealso::
#:
#:    `ImageMagick Resize Filters`__
#:       Demonstrates the results of resampling images using the various
#:       resize filters and blur settings available in ImageMagick.
#:
#:    __ http://www.imagemagick.org/Usage/resize/
FILTER_TYPES = ('undefined', 'point', 'box', 'triangle', 'hermite', 'hanning',
                'hamming', 'blackman', 'gaussian', 'quadratic', 'cubic',
                'catrom', 'mitchell', 'jinc', 'sinc', 'sincfast', 'kaiser',
                'welsh', 'parzen', 'bohman', 'bartlett', 'lagrange', 'lanczos',
                'lanczossharp', 'lanczos2', 'lanczos2sharp', 'robidoux',
                'robidouxsharp', 'cosine', 'spline', 'sentinel')


#: (:class:`tuple`) The list of :attr:`Image.function <BaseImage.function>`
#: types.
#:
#: - ``'undefined'``
#: - ``'arcsin'``
#: - ``'arctan'``
#: - ``'polynomial'``
#: - ``'sinusoid'``
FUNCTION_TYPES = ('undefined', 'polynomial', 'sinusoid', 'arcsin', 'arctan')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    FUNCTION_TYPES = ('undefined', 'arcsin', 'arctan', 'polynomial',
                      'sinusoid')


#: (:class:`tuple`) The list of :attr:`~BaseImage.gravity` types.
#:
#: - ``'forget'``
#: - ``'north_west'``
#: - ``'north'``
#: - ``'north_east'``
#: - ``'west'``
#: - ``'center'``
#: - ``'east'``
#: - ``'south_west'``
#: - ``'south'``
#: - ``'south_east'``
#:
#: .. versionadded:: 0.3.0
GRAVITY_TYPES = ('forget', 'north_west', 'north', 'north_east', 'west',
                 'center', 'east', 'south_west', 'south', 'south_east',
                 'static')


#: (:class:`tuple`) The list of methods for :meth:`~BaseImage.merge_layers`
#: and :meth:`~Image.compare_layers`.
#:
#: - ``'undefined'``
#: - ``'coalesce'``
#: - ``'compareany'`` - Only used for :meth:`~Image.compare_layers`.
#: - ``'compareclear'`` - Only used for :meth:`~Image.compare_layers`.
#: - ``'compareoverlay'`` - Only used for :meth:`~Image.compare_layers`.
#: - ``'dispose'``
#: - ``'optimize'``
#: - ``'optimizeimage'``
#: - ``'optimizeplus'``
#: - ``'optimizetrans'``
#: - ``'removedups'``
#: - ``'removezero'``
#: - ``'composite'``
#: - ``'merge'`` - Only used for :meth:`~BaseImage.merge_layers`.
#: - ``'flatten'`` - Only used for :meth:`~BaseImage.merge_layers`.
#: - ``'mosaic'`` - Only used for :meth:`~BaseImage.merge_layers`.
#: - ``'trimbounds'`` - Only used for :meth:`~BaseImage.merge_layers`.
#:
#: .. versionadded:: 0.4.3
IMAGE_LAYER_METHOD = ('undefined', 'coalesce', 'compareany', 'compareclear',
                      'compareoverlay', 'dispose', 'optimize', 'optimizeimage',
                      'optimizeplus', 'optimizetrans', 'removedups',
                      'removezero', 'composite', 'merge', 'flatten', 'mosaic',
                      'trimbounds')


#: (:class:`tuple`) The list of image types
#:
#: - ``'undefined'``
#: - ``'bilevel'``
#: - ``'grayscale'``
#: - ``'grayscalealpha'`` - Only available with ImageMagick-7
#: - ``'grayscalematte'`` - Only available with ImageMagick-6
#: - ``'palette'``
#: - ``'palettealpha'`` - Only available with ImageMagick-7
#: - ``'palettematte'`` - Only available with ImageMagick-6
#: - ``'truecolor'``
#: - ``'truecoloralpha'`` - Only available with ImageMagick-7
#: - ``'truecolormatte'`` - Only available with ImageMagick-6
#: - ``'colorseparation'``
#: - ``'colorseparationalpha'`` - Only available with ImageMagick-7
#: - ``'colorseparationmatte'`` - Only available with ImageMagick-6
#: - ``'optimize'``
#: - ``'palettebilevelalpha'`` - Only available with ImageMagick-7
#: - ``'palettebilevelmatte'`` - Only available with ImageMagick-6
#:
#: .. seealso::
#:
#:    `ImageMagick Image Types`__
#:       Describes the MagickSetImageType method which can be used
#:       to set the type of an image
#:
#:    __ http://www.imagemagick.org/api/magick-image.php#MagickSetImageType
IMAGE_TYPES = ('undefined', 'bilevel', 'grayscale', 'grayscalematte',
               'palette', 'palettematte', 'truecolor', 'truecolormatte',
               'colorseparation', 'colorseparationmatte', 'optimize',
               'palettebilevelmatte')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    IMAGE_TYPES = ('undefined', 'bilevel', 'grayscale', 'grayscalealpha',
                   'palette', 'palettealpha', 'truecolor', 'truecoloralpha',
                   'colorseparation', 'colorseparationalpha', 'optimize',
                   'palettebilevelalpha')


#: (:class:`tuple`) The list of interlace schemes.
#:
#: - ``'undefined'``
#: - ``'no'``
#: - ``'line'``
#: - ``'plane'``
#: - ``'partition'``
#: - ``'gif'``
#: - ``'jpeg'``
#: - ``'png'``
#:
#: .. versionadded:: 0.5.2
INTERLACE_TYPES = ('undefined', 'no', 'line', 'plane', 'partition', 'gif',
                   'jpeg', 'png')


#: (:class:`tuple`) The list of builtin kernels.
#:
#: - ``'undefined'``
#: - ``'unity'``
#: - ``'gaussian'``
#: - ``'dog'``
#: - ``'log'``
#: - ``'blur'``
#: - ``'comet'``
#: - ``'laplacian'``
#: - ``'sobel'``
#: - ``'frei_chen'``
#: - ``'roberts'``
#: - ``'prewitt'``
#: - ``'compass'``
#: - ``'kirsch'``
#: - ``'diamond'``
#: - ``'square'``
#: - ``'rectangle'``
#: - ``'octagon'``
#: - ``'disk'``
#: - ``'plus'``
#: - ``'cross'``
#: - ``'ring'``
#: - ``'peaks'``
#: - ``'edges'``
#: - ``'corners'``
#: - ``'diagonals'``
#: - ``'line_ends'``
#: - ``'line_junctions'``
#: - ``'ridges'``
#: - ``'convex_hull'``
#: - ``'thin_se'``
#: - ``'skeleton'``
#: - ``'chebyshev'``
#: - ``'manhattan'``
#: - ``'octagonal'``
#: - ``'euclidean'``
#: - ``'user_defined'``
#: - ``'binomial'``
#:
KERNEL_INFO_TYPES = ('undefined', 'unity', 'gaussian', 'dog', 'log', 'blur',
                     'comet', 'laplacian', 'sobel', 'frei_chen', 'roberts',
                     'prewitt', 'compass', 'kirsch', 'diamond', 'square',
                     'rectangle', 'octagon', 'disk', 'plus', 'cross', 'ring',
                     'peaks', 'edges', 'corners', 'diagonals', 'line_ends',
                     'line_junctions', 'ridges', 'convex_hull', 'thin_se',
                     'skeleton', 'chebyshev', 'manhattan', 'octagonal',
                     'euclidean', 'user_defined', 'binomial')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    KERNEL_INFO_TYPES = ('undefined', 'unity', 'gaussian', 'dog', 'log',
                         'blur', 'comet', 'binomial', 'laplacian', 'sobel',
                         'frei_chen', 'roberts', 'prewitt', 'compass',
                         'kirsch', 'diamond', 'square', 'rectangle', 'octagon',
                         'disk', 'plus', 'cross', 'ring', 'peaks', 'edges',
                         'corners', 'diagonals', 'line_ends', 'line_junctions',
                         'ridges', 'convex_hull', 'thin_se', 'skeleton',
                         'chebyshev', 'manhattan', 'octagonal', 'euclidean',
                         'user_defined')

#: (:class:`tuple`) The list of morphology methods.
#:
#: - ``'undefined'``
#: - ``'convolve'``
#: - ``'correlate'``
#: - ``'erode'``
#: - ``'dilate'``
#: - ``'erode_intensity'``
#: - ``'dilate_intensity'``
#: - ``'distance'``
#: - ``'open'``
#: - ``'close'``
#: - ``'open_intensity'``
#: - ``'close_intensity'``
#: - ``'smooth'``
#: - ``'edgein'``
#: - ``'edgeout'``
#: - ``'edge'``
#: - ``'tophat'``
#: - ``'bottom_hat'``
#: - ``'hit_and_miss'``
#: - ``'thinning'``
#: - ``'thicken'``
#: - ``'voronoi'``
#: - ``'iterative_distance'``
#:
MORPHOLOGY_METHODS = ('undefined', 'convolve', 'correlate', 'erode', 'dilate',
                      'erode_intensity', 'dilate_intensity', 'distance',
                      'open', 'close', 'open_intensity', 'close_intensity',
                      'smooth', 'edgein', 'edgeout', 'edge', 'tophat',
                      'bottom_hat', 'hit_and_miss', 'thinning', 'thicken',
                      'voronoi', 'iterative_distance')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    MORPHOLOGY_METHODS = ('undefined', 'convolve', 'correlate', 'erode',
                          'dilate', 'erode_intensity', 'dilate_intensity',
                          'iterative_distance', 'open', 'close',
                          'open_intensity', 'close_intensity', 'smooth',
                          'edgein', 'edgeout', 'edge', 'tophat', 'bottom_hat',
                          'hit_and_miss', 'thinning', 'thicken', 'distance',
                          'voronoi')


#: (:class:`tuple`) The list of montage behaviors used by
#: :meth:`Image.montage()` method.
#:
#: - ``'undefined'``
#: - ``'frame'``
#: - ``'unframe'``
#: - ``'concatenate'``
#:
#: .. versionadded:: 0.6.8
MONTAGE_MODES = ('undefined', 'frame', 'unframe', 'concatenate')


#: (:class:`tuple`) The list of noise types used by
#: :meth:`Image.noise() <wand.image.BaseImage.noise>` method.
#:
#: - ``'undefined'``
#: - ``'uniform'``
#: - ``'gaussian'``
#: - ``'multiplicative_gaussian'``
#: - ``'impulse'``
#: - ``'laplacian'``
#: - ``'poisson'``
#: - ``'random'``
#:
#: .. versionadded:: 0.5.3
NOISE_TYPES = ('undefined', 'uniform', 'gaussian', 'multiplicative_gaussian',
               'impulse', 'laplacian', 'poisson', 'random')


#: (:class:`collections.abc.Set`) The set of available
#: :attr:`~BaseImage.options`.
#:
#: .. versionadded:: 0.3.0
#:
#: .. versionchanged:: 0.3.4
#:    Added ``'jpeg:sampling-factor'`` option.
#:
#: .. versionchanged:: 0.3.9
#:    Added ``'pdf:use-cropbox'`` option. Ensure you set this option *before*
#:    reading the PDF document.
#:
#: .. deprecated:: 0.5.0
#:    Any arbitrary key can be set to the option table. Key-Value pairs set
#:    on the MagickWand stack allowing for various coders, kernels, morphology
#:    (&tc) to pick and choose additional user-supplied properties/artifacts.
OPTIONS = frozenset([
    'caption',
    'comment',
    'date:create',
    'date:modify',
    'exif:ColorSpace',
    'exif:InteroperabilityIndex',
    'fill',
    'film-gamma',
    'gamma',
    'hdr:exposure',
    'jpeg:colorspace',
    'jpeg:sampling-factor',
    'label',
    'pdf:use-cropbox',
    'png:bit-depth-written',
    'png:IHDR.bit-depth-orig',
    'png:IHDR.color-type-orig',
    'png:tIME',
    'reference-black',
    'reference-white',
    'signature',
    'tiff:Orientation',
    'tiff:photometric',
    'tiff:ResolutionUnit',
    'type:hinting',
    'vips:metadata'
])


#: (:class:`tuple`) The list of :attr:`~BaseImage.orientation` types.
#:
#: .. versionadded:: 0.3.0
ORIENTATION_TYPES = ('undefined', 'top_left', 'top_right', 'bottom_right',
                     'bottom_left', 'left_top', 'right_top', 'right_bottom',
                     'left_bottom')


#: (:class:`dict`) Map of papersize names to page sizes. Each page size
#: is a width & height :class:`tuple` at a 72dpi resolution.
#:
#: .. code::
#:
#:     from wand.image import Image, PAPERSIZE_MAP
#:
#:     w, h = PAPERSIZE_MAP["a4"]
#:     with Image(width=w, height=h, background="white") as img:
#:         img.save(filename="a4_page.png")
#:
#: .. versionadded:: 0.6.4
PAPERSIZE_MAP = {
    '4x6': (288, 432), '5x7': (360, 504), '7x9': (504, 648),
    '8x10': (576, 720), '9x11': (648, 792), '9x12': (648, 864),
    '10x13': (720, 936), '10x14': (720, 1008), '11x17': (792, 1224),
    '4A0': (4768, 6741), '2A0': (3370, 4768), 'a0': (2384, 3370),
    'a1': (1684, 2384), 'a2': (1191, 1684), 'a3': (842, 1191),
    'a4': (595, 842), 'a4small': (595, 842), 'a5': (420, 595),
    'a6': (298, 420), 'a7': (210, 298), 'a8': (147, 210), 'a9': (105, 147),
    'a10': (74, 105), 'archa': (648, 864), 'archb': (864, 1296),
    'archC': (1296, 1728), 'archd': (1728, 2592), 'arche': (2592, 3456),
    'b0': (2920, 4127), 'b1': (2064, 2920), 'b10': (91, 127),
    'b2': (1460, 2064), 'b3': (1032, 1460), 'b4': (729, 1032),
    'b5': (516, 729), 'b6': (363, 516), 'b7': (258, 363), 'b8': (181, 258),
    'b9': (127, 181), 'c0': (2599, 3676), 'c1': (1837, 2599),
    'c2': (1298, 1837), 'c3': (918, 1296), 'c4': (649, 918), 'c5': (459, 649),
    'c6': (323, 459), 'c7': (230, 323), 'csheet': (1224, 1584),
    'dsheet': (1584, 2448), 'esheet': (2448, 3168), 'executive': (540, 720),
    'flsa': (612, 936), 'flse': (612, 936), 'folio': (612, 936),
    'halfletter': (396, 612), 'isob0': (2835, 4008), 'isob1': (2004, 2835),
    'isob10': (88, 125), 'isob2': (1417, 2004), 'isob3': (1001, 1417),
    'isob4': (709, 1001), 'isob5': (499, 709), 'isob6': (354, 499),
    'isob7': (249, 354), 'isob8': (176, 249), 'isob9': (125, 176),
    'jisb0': (1030, 1456), 'jisb1': (728, 1030), 'jisb2': (515, 728),
    'jisb3': (364, 515), 'jisb4': (257, 364), 'jisb5': (182, 257),
    'jisb6': (128, 182), 'ledger': (1224, 792), 'legal': (612, 1008),
    'letter': (612, 792), 'lettersmall': (612, 792), 'monarch': (279, 540),
    'quarto': (610, 780), 'statement': (396, 612),  'tabloid': (792, 1224)
}


#: (:class:`tuple`) List of interpolate pixel methods (ImageMagick-7 only.)
#:
#: - ``'undefined'``
#: - ``'average'``
#: - ``'average9'``
#: - ``'average16'``
#: - ``'background'``
#: - ``'bilinear'``
#: - ``'blend'``
#: - ``'catrom'``
#: - ``'integer'``
#: - ``'mesh'``
#: - ``'nearest'``
#: - ``'spline'``
#:
#: .. versionadded:: 0.5.0
PIXEL_INTERPOLATE_METHODS = ('undefined', 'average', 'average9', 'average16',
                             'background', 'bilinear', 'blend', 'catrom',
                             'integer', 'mesh', 'nearest', 'spline')


#: (:class:`tuple`) List of rendering intent types used for
#: :attr:`Image.rendering_intent <wand.image.BaseImage.rendering_intent>`
#: property.
#:
#: - ``'undefined'``
#: - ``'saturation'``
#: - ``'perceptual'``
#: - ``'absolute'``
#: - ``'relative'``
#:
#: .. versionadded:: 0.5.4
RENDERING_INTENT_TYPES = ('undefined', 'saturation', 'perceptual', 'absolute',
                          'relative')


#: (:class:`tuple`) List of sparse color methods used by
#: :class:`Image.sparse_color() <wand.image.BaseImage.sparse_color>`
#:
#: - ``'undefined'``
#: - ``'barycentric'``
#: - ``'bilinear'``
#: - ``'shepards'``
#: - ``'voronoi'``
#: - ``'inverse'``
#: - ``'manhattan'``
#:
#: .. versionadded:: 0.5.3
SPARSE_COLOR_METHODS = dict(undefined=0, barycentric=1, bilinear=7,
                            shepards=16, voronoi=18, inverse=19,
                            manhattan=20)


#: (:class:`tuple`) The list of statistic types used by
#: :meth:`Image.statistic() <wand.image.BaseImage.statistic>`.
#:
#: - ``'undefined'``
#: - ``'gradient'``
#: - ``'maximum'``
#: - ``'mean'``
#: - ``'median'``
#: - ``'minimum'``
#: - ``'mode'``
#: - ``'nonpeak'``
#: - ``'root_mean_square'``
#: - ``'standard_deviation'``
#:
#: .. versionadded:: 0.5.3
STATISTIC_TYPES = ('undefined', 'gradient', 'maximum', 'mean', 'median',
                   'minimum', 'mode', 'nonpeak', 'standard_deviation',
                   'root_mean_square')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    STATISTIC_TYPES = ('undefined', 'gradient', 'maximum', 'mean', 'median',
                       'minimum', 'mode', 'nonpeak', 'root_mean_square',
                       'standard_deviation')


#: (:class:`tuple`) The list of pixel storage types.
#:
#: - ``'undefined'``
#: - ``'char'``
#: - ``'double'``
#: - ``'float'``
#: - ``'integer'``
#: - ``'long'``
#: - ``'quantum'``
#: - ``'short'``
#:
#: .. versionadded:: 0.5.0
STORAGE_TYPES = ('undefined', 'char', 'double', 'float', 'integer',
                 'long', 'quantum', 'short')


#: (:class:`tuple`) The list of resolution unit types.
#:
#: - ``'undefined'``
#: - ``'pixelsperinch'``
#: - ``'pixelspercentimeter'``
#:
#: .. seealso::
#:
#:    `ImageMagick Image Units`__
#:       Describes the MagickSetImageUnits method which can be used
#:       to set image units of resolution
#:
#:    __ http://www.imagemagick.org/api/magick-image.php#MagickSetImageUnits
UNIT_TYPES = ('undefined', 'pixelsperinch', 'pixelspercentimeter')


#: (:class:`tuple`) The list of :attr:`~BaseImage.virtual_pixel` types.
#:
#: - ``'undefined'``
#: - ``'background'``
#: - ``'constant'`` - Only available with ImageMagick-6
#: - ``'dither'``
#: - ``'edge'``
#: - ``'mirror'``
#: - ``'random'``
#: - ``'tile'``
#: - ``'transparent'``
#: - ``'mask'``
#: - ``'black'``
#: - ``'gray'``
#: - ``'white'``
#: - ``'horizontal_tile'``
#: - ``'vertical_tile'``
#: - ``'horizontal_tile_edge'``
#: - ``'vertical_tile_edge'``
#: - ``'checker_tile'``
#:
#: .. versionadded:: 0.4.1
VIRTUAL_PIXEL_METHOD = ('undefined', 'background', 'constant', 'dither',
                        'edge', 'mirror', 'random', 'tile', 'transparent',
                        'mask', 'black', 'gray', 'white', 'horizontal_tile',
                        'vertical_tile', 'horizontal_tile_edge',
                        'vertical_tile_edge', 'checker_tile')
if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
    VIRTUAL_PIXEL_METHOD = ('undefined', 'background', 'dither',
                            'edge', 'mirror', 'random', 'tile', 'transparent',
                            'mask', 'black', 'gray', 'white',
                            'horizontal_tile', 'vertical_tile',
                            'horizontal_tile_edge', 'vertical_tile_edge',
                            'checker_tile')


def manipulative(function):
    """Mark the operation manipulating itself instead of returning new one."""
    @functools.wraps(function)
    def wrapped(self, *args, **kwargs):
        result = function(self, *args, **kwargs)
        self.dirty = True
        return result
    return wrapped


def trap_exception(function):
    @functools.wraps(function)
    def wrapped(self, *args, **kwargs):
        result = function(self, *args, **kwargs)
        if not bool(result):
            self.raise_exception()
        return result
    return wrapped


class BaseImage(Resource):
    """The abstract base of :class:`Image` (container) and
    :class:`~wand.sequence.SingleImage`.  That means the most of
    operations, defined in this abstract class, are possible for
    both :class:`Image` and :class:`~wand.sequence.SingleImage`.

    .. versionadded:: 0.3.0

    """

    #: (:class:`OptionDict`) The mapping of internal option settings.
    #:
    #: .. versionadded:: 0.3.0
    #:
    #: .. versionchanged:: 0.3.4
    #:    Added ``'jpeg:sampling-factor'`` option.
    #:
    #: .. versionchanged:: 0.3.9
    #:    Added ``'pdf:use-cropbox'`` option.
    options = None

    #: (:class:`collections.abc.Sequence`) The list of
    #: :class:`~wand.sequence.SingleImage`\ s that the image contains.
    #:
    #: .. versionadded:: 0.3.0
    sequence = None

    #: (:class:`bool`) Whether the image is changed or not.
    dirty = None

    #: (:class:`numbers.Integral`) Internal placeholder for
    #: :attr:`seed` property.
    #:
    #: .. versionadded:: 0.5.5
    _seed = None

    c_is_resource = library.IsMagickWand
    c_destroy_resource = library.DestroyMagickWand
    c_get_exception = library.MagickGetException
    c_clear_exception = library.MagickClearException

    __slots__ = '_wand',

    def __init__(self, wand):
        self.wand = wand
        self.channel_images = ChannelImageDict(self)
        self.channel_depths = ChannelDepthDict(self)
        self.options = OptionDict(self)
        self.dirty = False

    def __eq__(self, other):
        if isinstance(other, type(self)):
            return self.signature == other.signature
        return False

    def __getitem__(self, idx):
        if (not isinstance(idx, str) and isinstance(idx, abc.Iterable)):
            idx = tuple(idx)
            d = len(idx)
            if not (1 <= d <= 2):
                raise ValueError('index cannot be {0}-dimensional'.format(d))
            elif d == 2:
                x, y = idx
                x_slice = isinstance(x, slice)
                y_slice = isinstance(y, slice)
                if x_slice and not y_slice:
                    y = slice(y, y + 1)
                elif not x_slice and y_slice:
                    x = slice(x, x + 1)
                elif not (x_slice or y_slice):
                    if not (isinstance(x, numbers.Integral) and
                            isinstance(y, numbers.Integral)):
                        raise TypeError('x and y must be integral, not ' +
                                        repr((x, y)))
                    if x < 0:
                        x += self.width
                    if y < 0:
                        y += self.height
                    if x >= self.width:
                        raise IndexError('x must be less than width')
                    elif y >= self.height:
                        raise IndexError('y must be less than height')
                    elif x < 0:
                        raise IndexError('x cannot be less than 0')
                    elif y < 0:
                        raise IndexError('y cannot be less than 0')
                    with iter(self) as iterator:
                        iterator.seek(y)
                        return iterator.__next__(x)
                if not (x.step is None and y.step is None):
                    raise ValueError('slicing with step is unsupported')
                elif (x.start is None and x.stop is None and
                      y.start is None and y.stop is None):
                    return self.clone()
                cloned = self.clone()
                try:
                    cloned.crop(x.start, y.start, x.stop, y.stop)
                except ValueError as e:
                    raise IndexError(str(e))
                return cloned
            else:
                return self[idx[0]]
        elif isinstance(idx, numbers.Integral):
            if idx < 0:
                idx += self.height
            elif idx >= self.height:
                raise IndexError('index must be less than height, but got ' +
                                 repr(idx))
            elif idx < 0:
                raise IndexError('index cannot be less than zero, but got ' +
                                 repr(idx))
            with iter(self) as iterator:
                iterator.seek(idx)
                return next(iterator)
        elif isinstance(idx, slice):
            return self[:, idx]
        raise TypeError('unsupported index type: ' + repr(idx))

    def __setitem__(self, idx, color):
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(color=color)
        if not isinstance(idx, abc.Iterable):
            raise TypeError('Expecting list of x,y coordinates, not ' +
                            repr(idx))
        idx = tuple(idx)
        if len(idx) != 2:
            msg = 'pixel index can not be {0}-dimensional'.format(len(idx))
            raise ValueError(msg)
        colorspace = self.colorspace
        s_index = STORAGE_TYPES.index("double")
        width, height = self.size
        x1, y1 = idx
        x2, y2 = 1, 1
        if not (isinstance(x1, numbers.Integral) and
                isinstance(y1, numbers.Integral)):
            raise TypeError('Expecting x & y to be integers')
        if x1 < 0:
            x1 += width
        if y1 < 0:
            y1 += height
        if x1 >= width:
            raise ValueError('x must be less than image width')
        elif y1 >= height:
            raise ValueError('y must be less than image height')
        if colorspace == 'gray':
            channel_map = b'I'
            pixel = (ctypes.c_double * 1)()
            pixel[0] = color.red
        elif colorspace == 'cmyk':
            channel_map = b'CMYK'
            pixel = (ctypes.c_double * 5)()
            pixel[0] = color.red
            pixel[1] = color.green
            pixel[2] = color.blue
            pixel[3] = color.black
            if self.alpha_channel:
                channel_map += b'A'
                pixel[4] = color.alpha
        else:
            channel_map = b'RGB'
            pixel = (ctypes.c_double * 4)()
            pixel[0] = color.red
            pixel[1] = color.green
            pixel[2] = color.blue
            if self.alpha_channel:
                channel_map += b'A'
                pixel[3] = color.alpha
        r = library.MagickImportImagePixels(self.wand,
                                            x1, y1, x2, y2,
                                            channel_map,
                                            s_index,
                                            ctypes.byref(pixel))
        if not r:
            self.raise_exception()

    def __hash__(self):
        return hash(self.signature)

    def __iter__(self):
        return Iterator(image=self)

    def __len__(self):
        return self.height

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self, extra_format=' ({self.width}x{self.height})'):
        cls = type(self)
        typename = '{0}.{1}'.format(
            cls.__module__,
            getattr(cls, '__qualname__', cls.__name__)
        )
        if getattr(self, 'c_resource', None) is None:
            return '<{0}: (closed)>'.format(typename)
        sig = self.signature
        if not sig:
            return '<{0}: (empty)>'.format(typename)
        return '<{0}: {1}{2}>'.format(
            typename, sig[:7], extra_format.format(self=self)
        )

    @property
    def __array_interface__(self):
        """Allows image-data from :class:`Image <wand.image.BaseImage>`
        instances to be loaded into numpy's array.

        .. code::

            import numpy
            from wand.image import Image

            with Image(filename='rose:') as img:
                img_data = numpy.asarray(img)

        :raises ValueError: if image has no data.

        .. versionadded:: 0.5.0
        .. versionchanged:: 0.6.0
           The :attr:`shape` property is now ordered by ``height``, ``width``,
           and ``channel``.
        .. versionchanged:: 0.6.2
           Color spaces ``gray`` & ``cmyk`` are now supported.
        """
        if not self.signature:
            raise ValueError("No image data to interface with.")
        width, height = self.size
        storage_type = 1  # CharPixel
        cs = self.colorspace
        if cs in ('gray',):
            channel_format = b'R'
        elif cs in ('cmyk',):
            channel_format = b'CMYK'
        else:
            channel_format = b'RGB'
        if self.alpha_channel:
            channel_format += b'A'
        channel_number = len(channel_format)
        self._c_buffer = (width * height * channel_number * ctypes.c_char)()
        # FIXME: Move to pixel-data import/export methods.
        r = library.MagickExportImagePixels(self.wand,
                                            0, 0, width, height,
                                            channel_format, storage_type,
                                            ctypes.byref(self._c_buffer))
        if not r:
            self.raise_exception()
        return dict(data=(ctypes.addressof(self._c_buffer), True),
                    shape=(height, width, channel_number),
                    typestr='|u1',
                    version=3)

    @property
    def alpha_channel(self):
        """(:class:`bool`) Get state of image alpha channel.
        It can also be used to enable/disable alpha channel, but with different
        behavior such as new, copied, or existing.

        Behavior of setting :attr:`alpha_channel` is defined with the
        following values:

        - ``'activate'``, ``'on'``, or :const:`True` will enable an images
           alpha channel. Existing alpha data is preserved.
        - ``'deactivate'``, ``'off'``, or :const:`False` will disable an images
           alpha channel. Any data on the alpha will be preserved.
        - ``'associate'`` & ``'disassociate'`` toggle alpha channel flag in
           certain image-file specifications.
        - ``'set'`` enables and resets any data in an images alpha channel.
        - ``'opaque'`` enables alpha/matte channel, and forces full opaque
           image.
        - ``'transparent'`` enables alpha/matte channel, and forces full
           transparent image.
        - ``'extract'`` copies data in alpha channel across all other channels,
           and disables alpha channel.
        - ``'copy'`` calculates the gray-scale of RGB channels,
            and applies it to alpha channel.
        - ``'shape'`` is identical to ``'copy'``, but will color the resulting
           image with the value defined with :attr:`background_color`.
        - ``'remove'`` will composite :attr:`background_color` value.
        - ``'background'`` replaces full-transparent color with background
           color.

        .. note::

            The :attr:`alpha_channel` attribute will always return ``True``
            if alpha channel is enabled, and ``False`` otherwise. Setting
            this property with a string value from :const:`ALPHA_CHANNEL_TYPES`
            will resolve to a :class:`bool` after applying channel operations
            listed above.

            With ImageMagick-6, values ``'on'`` & ``'off'`` are aliased to
            ``'activate'`` & ``'deactivate'``. However in ImageMagick-7,
            both ``'on'`` & ``'off'`` have their own behavior.


        .. versionadded:: 0.2.1

        .. versionchanged:: 0.4.1
           Support for additional setting values.
           However :attr:`Image.alpha_channel` will continue to return
           :class:`bool` if the current alpha/matte state is enabled.

        .. versionchanged:: 0.6.0
           Setting the alpha channel will apply the change to all frames
           in the image stack.
        """
        return bool(library.MagickGetImageAlphaChannel(self.wand))

    @alpha_channel.setter
    @manipulative
    def alpha_channel(self, alpha_type):
        is_im6 = MAGICK_VERSION_NUMBER < 0x700
        # Map common aliases for ``'deactivate'``
        if alpha_type is False or (alpha_type == 'off' and is_im6):
            alpha_type = 'deactivate'
        # Map common aliases for ``'activate'``
        elif alpha_type is True or (alpha_type == 'on' and is_im6):
            alpha_type = 'activate'
        assertions.string_in_list(ALPHA_CHANNEL_TYPES,
                                  'wand.image.ALPHA_CHANNEL_TYPES',
                                  alpha_channel=alpha_type)
        alpha_index = ALPHA_CHANNEL_TYPES.index(alpha_type)
        library.MagickSetLastIterator(self.wand)
        n = library.MagickGetIteratorIndex(self.wand)
        library.MagickResetIterator(self.wand)
        for i in range(0, n + 1):
            library.MagickSetIteratorIndex(self.wand, i)
            library.MagickSetImageAlphaChannel(self.wand, alpha_index)

    @property
    def animation(self):
        """(:class:`bool`) Whether the image is animation or not.
        It doesn't only mean that the image has two or more images (frames),
        but all frames are even the same size.  It's about image format,
        not content.  It's :const:`False` even if :mimetype:`image/ico`
        consists of two or more images of the same size.

        For example, it's :const:`False` for :mimetype:`image/jpeg`,
        :mimetype:`image/gif`, :mimetype:`image/ico`.

        If :mimetype:`image/gif` has two or more frames, it's :const:`True`.
        If :mimetype:`image/gif` has only one frame, it's :const:`False`.

        .. versionadded:: 0.3.0

        .. versionchanged:: 0.3.8
           Became to accept :mimetype:`image/x-gif` as well.

        """
        return False

    @property
    def antialias(self):
        """(:class:`bool`) If vectors & fonts will use anti-aliasing.

        .. versionchanged:: 0.5.0
           Previously named :attr:`font_antialias`.
        """
        return bool(library.MagickGetAntialias(self.wand))

    @antialias.setter
    @manipulative
    def antialias(self, antialias):
        assertions.assert_bool(antialias=antialias)
        library.MagickSetAntialias(self.wand, antialias)

    @property
    def background_color(self):
        """(:class:`wand.color.Color`) The image background color.
        It can also be set to change the background color.

        .. versionadded:: 0.1.9

        .. versionchanged:: 0.6.7
           Allow property to be set before image read.
        """
        pixel = library.NewPixelWand()
        if library.MagickGetNumberImages(self.wand):
            result = library.MagickGetImageBackgroundColor(self.wand, pixel)
        else:
            pixel = library.MagickGetBackgroundColor(self.wand)
            result = True
        if not result:  # pragma: no cover
            self.raise_exception()
        else:
            color = Color.from_pixelwand(pixel)
            pixel = library.DestroyPixelWand(pixel)
            return color

    @background_color.setter
    @manipulative
    def background_color(self, color):
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(color=color)
        with color:
            # Only set the image background if an image was loaded.
            if library.MagickGetNumberImages(self.wand):
                result = library.MagickSetImageBackgroundColor(self.wand,
                                                               color.resource)
                if not result:  # pragma: no cover
                    self.raise_exception()
            # Also set the image stack.
            result = library.MagickSetBackgroundColor(self.wand,
                                                      color.resource)
            if not result:
                self.raise_exception()

    @property
    def blue_primary(self):
        """(:class:`tuple`) The chromatic blue primary point for the image.
        With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
        however, ImageMagick-7 has ``(x, y, z)``.

        .. versionadded:: 0.5.2
        """
        x = ctypes.c_double(0.0)
        y = ctypes.c_double(0.0)
        r = None
        p = None
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickGetImageBluePrimary(self.wand, x, y)
            p = (x.value, y.value)
        else:  # pragma: no cover
            z = ctypes.c_double(0.0)
            r = library.MagickGetImageBluePrimary(self.wand, x, y, z)
            p = (x.value, y.value, z.value)
        if not r:  # pragma: no cover
            self.raise_exception()
        return p

    @blue_primary.setter
    def blue_primary(self, coordinates):
        r = None
        if not isinstance(coordinates, abc.Sequence):
            raise TypeError('Primary must be a tuple')
        if MAGICK_VERSION_NUMBER < 0x700:
            x, y = coordinates
            r = library.MagickSetImageBluePrimary(self.wand, x, y)
        else:  # pragma: no cover
            x, y, z = coordinates
            r = library.MagickSetImageBluePrimary(self.wand, x, y, z)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def border_color(self):
        """(:class:`wand.color.Color`) The image border color. Used for
        special effects like :meth:`polaroid()`.

        .. versionadded:: 0.5.4
        """
        pixel = library.NewPixelWand()
        result = library.MagickGetImageBorderColor(self.wand, pixel)
        if not result:  # pragma: no cover
            self.raise_exception()
        else:
            color = Color.from_pixelwand(pixel)
            pixel = library.DestroyPixelWand(pixel)
            return color

    @border_color.setter
    def border_color(self, color):
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(border_color=color)
        with color:
            r = library.MagickSetImageBorderColor(self.wand, color.resource)
            if not r:  # pragma: no cover
                self.raise_exception()

    @property
    def colors(self):
        """(:class:`numbers.Integral`) Count of unique colors used within the
        image. This is READ ONLY property.

        .. versionadded:: 0.5.3
        """
        return library.MagickGetImageColors(self.wand)

    @property
    def colorspace(self):
        """(:class:`str`) The image colorspace.

        Defines image colorspace as in :const:`COLORSPACE_TYPES` enumeration.

        It may raise :exc:`ValueError` when the colorspace is unknown.

        .. versionadded:: 0.3.4

        """
        colorspace_type_index = library.MagickGetImageColorspace(self.wand)
        if not colorspace_type_index:  # pragma: no cover
            self.raise_exception()
        return COLORSPACE_TYPES[colorspace_type_index]

    @colorspace.setter
    @manipulative
    def colorspace(self, colorspace_type):
        assertions.string_in_list(COLORSPACE_TYPES,
                                  'wand.image.COLORSPACE_TYPES',
                                  colorspace=colorspace_type)
        r = library.MagickSetImageColorspace(
            self.wand,
            COLORSPACE_TYPES.index(colorspace_type)
        )
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def compose(self):
        """(:class:`str`) The type of image compose.
        It's a string from :const:`COMPOSITE_OPERATORS` list.
        It also can be set.

        .. versionadded:: 0.5.1
        """
        compose_index = library.MagickGetImageCompose(self.wand)
        return COMPOSITE_OPERATORS[compose_index]

    @compose.setter
    def compose(self, operator):
        assertions.string_in_list(COMPOSITE_OPERATORS,
                                  'wand.image.COMPOSITE_OPERATORS',
                                  compose=operator)
        library.MagickSetImageCompose(self.wand,
                                      COMPOSITE_OPERATORS.index(operator))

    @property
    def compression(self):
        """(:class:`str`) The type of image compression.
        It's a string from :const:`COMPRESSION_TYPES` list.
        It also can be set.

        .. versionadded:: 0.3.6
        .. versionchanged:: 0.5.2
           Setting :attr:`compression` now sets both `image_info`
           and `images` in the internal image stack.
        """
        compression_index = library.MagickGetImageCompression(self.wand)
        return COMPRESSION_TYPES[compression_index]

    @compression.setter
    def compression(self, value):
        assertions.string_in_list(COMPRESSION_TYPES,
                                  'wand.image.COMPRESSION_TYPES',
                                  compression=value)
        library.MagickSetCompression(
            self.wand,
            COMPRESSION_TYPES.index(value)
        )
        library.MagickSetImageCompression(
            self.wand,
            COMPRESSION_TYPES.index(value)
        )

    @property
    def compression_quality(self):
        """(:class:`numbers.Integral`) Compression quality of this image.

        .. versionadded:: 0.2.0
        .. versionchanged:: 0.5.2
           Setting :attr:`compression_quality` now sets both `image_info`
           and `images` in the internal image stack.
        """
        return library.MagickGetImageCompressionQuality(self.wand)

    @compression_quality.setter
    @manipulative
    def compression_quality(self, quality):
        """Set compression quality for the image.

        :param quality: new compression quality setting
        :type quality: :class:`numbers.Integral`

        """
        assertions.assert_integer(compression_quality=quality)
        library.MagickSetCompressionQuality(self.wand, quality)
        r = library.MagickSetImageCompressionQuality(self.wand, quality)
        if not r:  # pragma: no cover
            raise ValueError('Unable to set compression quality to ' +
                             repr(quality))

    @property
    def delay(self):
        """(:class:`numbers.Integral`) The number of ticks between frames.

        .. versionadded:: 0.5.9
        """
        return library.MagickGetImageDelay(self.wand)

    @delay.setter
    def delay(self, value):
        assertions.assert_integer(delay=value)
        library.MagickSetImageDelay(self.wand, value)

    @property
    def depth(self):
        """(:class:`numbers.Integral`) The depth of this image.

        .. versionadded:: 0.2.1

        """
        return library.MagickGetImageDepth(self.wand)

    @depth.setter
    @manipulative
    def depth(self, depth):
        r = library.MagickSetImageDepth(self.wand, depth)
        if not r:  # pragma: no cover
            raise self.raise_exception()

    @property
    def dispose(self):
        """(:class:`str`) Controls how the image data is
        handled during animations. Values are from :const:`DISPOSE_TYPES`
        list, and can also be set.

        .. seealso::

            `Dispose Images`__ section in ``Animation Basics`` article.

        __ https://www.imagemagick.org/Usage/anim_basics/#dispose_images

        .. versionadded:: 0.5.0
        """
        dispose_idx = library.MagickGetImageDispose(self.wand)
        try:
            return DISPOSE_TYPES[dispose_idx]
        except IndexError:  # pragma: no cover
            return DISPOSE_TYPES[0]

    @dispose.setter
    def dispose(self, value):
        assertions.string_in_list(DISPOSE_TYPES,
                                  'wand.image.DISPOSE_TYPES',
                                  dispose=value)
        library.MagickSetImageDispose(self.wand, DISPOSE_TYPES.index(value))

    @property
    def font(self):
        """(:class:`wand.font.Font`) The current font options."""
        if not self.font_path:
            return None
        return Font(
            path=self.font_path,
            size=self.font_size,
            color=self.font_color,
            antialias=self.antialias,
            stroke_color=self.stroke_color,
            stroke_width=self.stroke_width
        )

    @font.setter
    @manipulative
    def font(self, font):
        if not isinstance(font, Font):
            raise TypeError('font must be a wand.font.Font, not ' + repr(font))
        self.font_path = font.path
        self.font_size = font.size
        self.font_color = font.color
        self.antialias = font.antialias
        if font.stroke_color:
            self.stroke_color = font.stroke_color
        if font.stroke_width is not None:
            self.stroke_width = font.stroke_width

    @property
    def font_antialias(self):
        """
        .. deprecated:: 0.5.0
           Use :attr:`antialias` instead.
        """
        return self.antialias

    @font_antialias.setter
    def font_antialias(self, antialias):
        self.antialias = antialias

    @property
    def font_color(self):
        return Color(self.options['fill'])

    @font_color.setter
    @manipulative
    def font_color(self, color):
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(font_color=color)
        self.options['fill'] = color.string

    @property
    def font_path(self):
        """(:class:`str`) The path of the current font.
        It also can be set.

        """
        font_str = None
        font_p = library.MagickGetFont(self.wand)
        if font_p:
            font_str = text(ctypes.string_at(font_p))
            font_p = library.MagickRelinquishMemory(font_p)
        return font_str

    @font_path.setter
    @manipulative
    def font_path(self, font):
        font = binary(font)
        r = library.MagickSetFont(self.wand, font)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def font_size(self):
        """(:class:`numbers.Real`) The font size.  It also can be set."""
        return library.MagickGetPointsize(self.wand)

    @font_size.setter
    @manipulative
    def font_size(self, size):
        assertions.assert_real(font_size=size)
        if size < 0.0:
            raise ValueError('cannot be less than 0.0, but got ' + repr(size))
        r = library.MagickSetPointsize(self.wand, size)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def format(self):
        """(:class:`str`) The image format.

        If you want to convert the image format, just reset this property::

            assert isinstance(img, wand.image.Image)
            img.format = 'png'

        It may raise :exc:`ValueError` when the format is unsupported.

        .. seealso::

           `ImageMagick Image Formats`__
              ImageMagick uses an ASCII string known as *magick* (e.g. ``GIF``)
              to identify file formats, algorithms acting as formats,
              built-in patterns, and embedded profile types.

           __ http://www.imagemagick.org/script/formats.php

        .. versionadded:: 0.1.6

        """
        fmt_str = None
        fmt_p = library.MagickGetImageFormat(self.wand)
        if fmt_p:
            fmt_str = text(ctypes.string_at(fmt_p))
            fmt_p = library.MagickRelinquishMemory(fmt_p)
        return fmt_str

    @format.setter
    def format(self, fmt):
        assertions.assert_string(format=fmt)
        fmt = fmt.strip()
        r = library.MagickSetImageFormat(self.wand, binary(fmt.upper()))
        if not r:
            raise ValueError(repr(fmt) + ' is unsupported format')
        r = library.MagickSetFilename(self.wand,
                                      b'buffer.' + binary(fmt.lower()))
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def fuzz(self):
        """(:class:`numbers.Real`) The normalized real number between ``0.0``
        and :attr:`quantum_range`. This property influences the accuracy of
        :meth:`compare()`.

        .. versionadded:: 0.5.3
        """
        return library.MagickGetImageFuzz(self.wand)

    @fuzz.setter
    def fuzz(self, value):
        assertions.assert_real(fuzz=value)
        library.MagickSetImageFuzz(self.wand, value)

    @property
    def gravity(self):
        """(:class:`str`) The text placement gravity used when
        annotating with text.  It's a string from :const:`GRAVITY_TYPES`
        list.  It also can be set.

        """
        gravity_index = library.MagickGetGravity(self.wand)
        if not gravity_index:  # pragma: no cover
            self.raise_exception()
        return GRAVITY_TYPES[gravity_index]

    @gravity.setter
    @manipulative
    def gravity(self, value):
        assertions.string_in_list(GRAVITY_TYPES,
                                  'wand.image.GRAVITY_TYPES',
                                  gravity=value)
        library.MagickSetGravity(self.wand, GRAVITY_TYPES.index(value))

    @property
    def green_primary(self):
        """(:class:`tuple`) The chromatic green primary point for the image.
        With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
        however, ImageMagick-7 has ``(x, y, z)``.

        .. versionadded:: 0.5.2
        """
        x = ctypes.c_double(0.0)
        y = ctypes.c_double(0.0)
        r = None
        p = None
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickGetImageGreenPrimary(self.wand, x, y)
            p = (x.value, y.value)
        else:  # pragma: no cover
            z = ctypes.c_double(0.0)
            r = library.MagickGetImageGreenPrimary(self.wand, x, y, z)
            p = (x.value, y.value, z.value)
        if not r:  # pragma: no cover
            self.raise_exception()
        return p

    @green_primary.setter
    def green_primary(self, coordinates):
        r = None
        if not isinstance(coordinates, abc.Sequence):
            raise TypeError('Primary must be a tuple')
        if MAGICK_VERSION_NUMBER < 0x700:
            x, y = coordinates
            r = library.MagickSetImageGreenPrimary(self.wand, x, y)
        else:  # pragma: no cover
            x, y, z = coordinates
            r = library.MagickSetImageGreenPrimary(self.wand, x, y, z)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def height(self):
        """(:class:`numbers.Integral`) The height of this image."""
        return library.MagickGetImageHeight(self.wand)

    @height.setter
    @manipulative
    def height(self, height):
        assertions.assert_unsigned_integer(height=height)
        library.MagickSetSize(self.wand, self.width, height)

    @property
    def histogram(self):
        """(:class:`HistogramDict`) The mapping that represents the histogram.
        Keys are :class:`~wand.color.Color` objects, and values are
        the number of pixels.

        .. tip::

            True-color photos can have millions of color values. If performance
            is more valuable than accuracy, remember to :meth:`quantize` the
            image before generating a :class:`HistogramDict`.

                with Image(filename='hd_photo.jpg') as img:
                    img.quantize(255, 'RGB', 0, False, False)
                    hist = img.histogram

        .. versionadded:: 0.3.0

        """
        return HistogramDict(self)

    @property
    def interlace_scheme(self):
        """(:class:`str`) The interlace used by the image.
        See :const:`INTERLACE_TYPES`.

        .. versionadded:: 0.5.2

        .. versionchanged:: 0.6.2
           The :attr:`interlace_scheme` property now points to the image.
           Previously was pointing to the :c:struct:`MagickWand`.
        """
        scheme_idx = library.MagickGetImageInterlaceScheme(self.wand)
        return INTERLACE_TYPES[scheme_idx]

    @interlace_scheme.setter
    def interlace_scheme(self, scheme):
        assertions.string_in_list(INTERLACE_TYPES,
                                  'wand.image.INTERLACE_TYPES',
                                  interlace_scheme=scheme)
        scheme_idx = INTERLACE_TYPES.index(scheme)
        library.MagickSetImageInterlaceScheme(self.wand, scheme_idx)

    @property
    def interpolate_method(self):
        """(:class:`str`) The interpolation method of the image.
        See :const:`PIXEL_INTERPOLATE_METHODS`.

        .. versionadded:: 0.5.2
        """
        method_idx = library.MagickGetImageInterpolateMethod(self.wand)
        return PIXEL_INTERPOLATE_METHODS[method_idx]

    @interpolate_method.setter
    def interpolate_method(self, method):
        assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                  'wand.image.PIXEL_INTERPOLATE_METHODS',
                                  interpolate_method=method)
        method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
        library.MagickSetImageInterpolateMethod(self.wand, method_idx)

    @property
    def kurtosis(self):
        """(:class:`numbers.Real`) The kurtosis of the image.

        .. tip::

            If you want both :attr:`kurtosis` & :attr:`skewness`, it
            would be faster to call :meth:`kurtosis_channel()` directly.

        .. versionadded:: 0.5.3
        """
        k, _ = self.kurtosis_channel()
        return k

    @property
    def length_of_bytes(self):
        """(:class:`numbers.Integral`) The original size, in bytes,
        of the image read. This will return `0` if the image was modified in
        a way that would invalidate the original length value.

        .. versionadded:: 0.5.4
        """
        size_ptr = ctypes.c_size_t(0)
        library.MagickGetImageLength(self.wand, ctypes.byref(size_ptr))
        return size_ptr.value

    @property
    def loop(self):
        """(:class:`numbers.Integral`) Number of frame iterations.
        A value of ``0`` will loop forever."""
        return library.MagickGetImageIterations(self.wand)

    @loop.setter
    def loop(self, iterations):
        assertions.assert_unsigned_integer(loop=iterations)
        library.MagickSetImageIterations(self.wand, iterations)

    @property
    def matte_color(self):
        """(:class:`wand.color.Color`) The color value of the matte channel.
        This can also be set.

        .. versionadded:: 0.4.1
        """
        pixel = library.NewPixelWand()
        result = library.MagickGetImageMatteColor(self.wand, pixel)
        if result:
            color = Color.from_pixelwand(pixel)
            pixel = library.DestroyPixelWand(pixel)
            return color
        else:  # pragma: no cover
            self.raise_exception()

    @matte_color.setter
    @manipulative
    def matte_color(self, color):
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(matte_color=color)
        with color:
            result = library.MagickSetImageMatteColor(self.wand,
                                                      color.resource)
            if not result:  # pragma: no cover
                self.raise_exception()

    @property
    def maxima(self):
        """(:class:`numbers.Real`) The maximum quantum value within the image.
        Value between 0.0 and :attr:`quantum_range`

        .. tip::

            If you want both :attr:`maxima` & :attr:`minima`,
            it would be faster to call :meth:`range_channel()` directly.

        .. versionadded:: 0.5.3
        """
        _, max_q = self.range_channel()
        return max_q

    @property
    def mean(self):
        """(:class:`numbers.Real`) The mean of the image, and have a value
        between 0.0 and :attr:`quantum_range`

        .. tip::

            If you want both :attr:`mean` & :attr:`standard_deviation`, it
            would be faster to call :meth:`mean_channel()` directly.

        .. versionadded:: 0.5.3
        """
        m, _ = self.mean_channel()
        return m

    @property
    def minima(self):
        """(:class:`numbers.Real`) The minimum quantum value within the image.
        Value between 0.0 and :attr:`quantum_range`

        .. tip::

            If you want both :attr:`maxima` & :attr:`minima`,
            it would be faster to call :meth:`range_channel()` directly.

        .. versionadded:: 0.5.3
        """
        min_q, _ = self.range_channel()
        return min_q

    @property
    def orientation(self):
        """(:class:`str`) The image orientation.  It's a string from
        :const:`ORIENTATION_TYPES` list.  It also can be set.

        .. versionadded:: 0.3.0

        """
        orientation_index = library.MagickGetImageOrientation(self.wand)
        try:
            return ORIENTATION_TYPES[orientation_index]
        except IndexError:  # pragma: no cover
            return ORIENTATION_TYPES[0]

    @orientation.setter
    @manipulative
    def orientation(self, value):
        assertions.string_in_list(ORIENTATION_TYPES,
                                  'wand.image.ORIENTATION_TYPES',
                                  orientation=value)
        index = ORIENTATION_TYPES.index(value)
        library.MagickSetImageOrientation(self.wand, index)

    @property
    def page(self):
        """The dimensions and offset of this Wand's page as a 4-tuple:
        ``(width, height, x, y)``.

        .. code::

            with Image(filename='wizard:') as img:
                img.page = (595, 842, 0, 0)

        Note that since it is based on the virtual canvas, it may not equal the
        dimensions of an image. See the ImageMagick documentation on the
        virtual canvas for more information.

        This attribute can also be set by using a named papersize. For
        example::

            with Image(filename='wizard:') as img:
                img.page = 'a4'

        .. versionadded:: 0.4.3

        .. versionchanged:: 0.6.4
           Added support for setting by papersize.
        """
        w = ctypes.c_size_t()
        h = ctypes.c_size_t()
        x = ctypes.c_ssize_t()
        y = ctypes.c_ssize_t()
        r = library.MagickGetImagePage(self.wand, w, h, x, y)
        if not r:  # pragma: no cover
            self.raise_exception()
        return int(w.value), int(h.value), int(x.value), int(y.value)

    @page.setter
    @manipulative
    def page(self, newpage):
        if isinstance(newpage, str):
            c_ptr = libmagick.GetPageGeometry(newpage.encode())
            ri = RectangleInfo()
            c_ptr = ctypes.cast(c_ptr, ctypes.c_char_p)
            libmagick.ParseAbsoluteGeometry(c_ptr, ctypes.byref(ri))
            newpage = (ri.width, ri.height, ri.x, ri.y)
            libmagick.DestroyString(c_ptr)
            del ri
        if isinstance(newpage, abc.Sequence):
            w, h, x, y = newpage
        else:
            raise TypeError("page layout must be 4-tuple")
        r = library.MagickSetImagePage(self.wand, w, h, x, y)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def page_height(self):
        """(:class:`numbers.Integral`) The height of the page for this wand.

        .. versionadded:: 0.4.3

        """
        return self.page[1]

    @page_height.setter
    @manipulative
    def page_height(self, height):
        newpage = list(self.page)
        newpage[1] = height
        self.page = newpage

    @property
    def page_width(self):
        """(:class:`numbers.Integral`) The width of the page for this wand.

        .. versionadded:: 0.4.3

        """
        return self.page[0]

    @page_width.setter
    @manipulative
    def page_width(self, width):
        newpage = list(self.page)
        newpage[0] = width
        self.page = newpage

    @property
    def page_x(self):
        """(:class:`numbers.Integral`) The X-offset of the page for this wand.

        .. versionadded:: 0.4.3

        """
        return self.page[2]

    @page_x.setter
    @manipulative
    def page_x(self, x):
        newpage = list(self.page)
        newpage[2] = x
        self.page = newpage

    @property
    def page_y(self):
        """(:class:`numbers.Integral`) The Y-offset of the page for this wand.

        .. versionadded:: 0.4.3

        """
        return self.page[3]

    @page_y.setter
    @manipulative
    def page_y(self, y):
        newpage = list(self.page)
        newpage[3] = y
        self.page = newpage

    @property
    def quantum_range(self):
        """(`:class:`numbers.Integral`) The maximum value of a color
        channel that is supported by the imagemagick library.

        .. versionadded:: 0.2.0

        """
        result = ctypes.c_size_t()
        library.MagickGetQuantumRange(ctypes.byref(result))
        return result.value

    @property
    def red_primary(self):
        """(:class:`tuple`) The chromatic red primary point for the image.
        With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
        however, ImageMagick-7 has ``(x, y, z)``.

        .. versionadded:: 0.5.2
        """
        x = ctypes.c_double(0.0)
        y = ctypes.c_double(0.0)
        r = None
        p = None
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickGetImageRedPrimary(self.wand, x, y)
            p = (x.value, y.value)
        else:  # pragma: no cover
            z = ctypes.c_double(0.0)
            r = library.MagickGetImageRedPrimary(self.wand, x, y, z)
            p = (x.value, y.value, z.value)
        if not r:  # pragma: no cover
            self.raise_exception()
        return p

    @red_primary.setter
    def red_primary(self, coordinates):
        r = None
        if not isinstance(coordinates, abc.Sequence):
            raise TypeError('Primary must be a tuple')
        if MAGICK_VERSION_NUMBER < 0x700:
            x, y = coordinates
            r = library.MagickSetImageRedPrimary(self.wand, x, y)
        else:  # pragma: no cover
            x, y, z = coordinates
            r = library.MagickSetImageRedPrimary(self.wand, x, y, z)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def rendering_intent(self):
        """(:class:`str`) PNG rendering intent. See
        :const:`RENDERING_INTENT_TYPES` for valid options.

        .. versionadded:: 0.5.4
        """
        ri_index = library.MagickGetImageRenderingIntent(self.wand)
        return RENDERING_INTENT_TYPES[ri_index]

    @rendering_intent.setter
    def rendering_intent(self, value):
        assertions.string_in_list(RENDERING_INTENT_TYPES,
                                  'wand.image.RENDERING_INTENT_TYPES',
                                  rendering_intent=value)
        ri_index = RENDERING_INTENT_TYPES.index(value)
        library.MagickSetImageRenderingIntent(self.wand, ri_index)

    @property
    def resolution(self):
        """(:class:`tuple`) Resolution of this image.

        .. versionadded:: 0.3.0

        .. versionchanged:: 0.5.8
           Resolution returns a tuple of float values to
           match ImageMagick's behavior.
        """
        x = ctypes.c_double(0.0)
        y = ctypes.c_double(0.0)
        r = library.MagickGetImageResolution(self.wand, x, y)
        if not r:  # pragma: no cover
            self.raise_exception()
        return x.value, y.value

    @resolution.setter
    @manipulative
    def resolution(self, geometry):
        if isinstance(geometry, abc.Sequence):
            x, y = geometry
        elif isinstance(geometry, numbers.Real):
            x, y = geometry, geometry
        else:
            raise TypeError('resolution must be a (x, y) pair or a float '
                            'of the same x/y')
        if self.size == (0, 0):
            r = library.MagickSetResolution(self.wand, x, y)
        else:
            r = library.MagickSetImageResolution(self.wand, x, y)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def sampling_factors(self):
        """(:class:`tuple`) Factors used in sampling data streams.
        This can be set by given it a string ``"4:2:2"``, or tuple of numbers
        ``(2, 1, 1)``. However the given string value will be parsed to aspect
        ratio (i.e. ``"4:2:2"`` internally becomes ``"2,1"``).

        .. note::
            This property is only used by YUV, DPX, & EXR encoders. For
            JPEG & TIFF set ``"jpeg:sampling-factor"`` on
            :attr:`Image.options` dictionary::

                with Image(filename='input.jpg') as img:
                    img.options['jpeg:sampling-factor'] = '2x1'

        .. versionadded:: 0.6.3
        """
        factors_len = ctypes.c_size_t(0)
        factors = library.MagickGetSamplingFactors(self.wand,
                                                   ctypes.byref(factors_len))
        factors_tuple = tuple(factors[x] for x in range(factors_len.value))
        factors = library.MagickRelinquishMemory(factors)
        return factors_tuple

    @sampling_factors.setter
    def sampling_factors(self, factors):
        if isinstance(factors, str):
            geometry_info = GeometryInfo()
            flags = libmagick.ParseGeometry(binary(factors),
                                            ctypes.byref(geometry_info))
            if (flags & geometry_info.SigmaValue) == 0:
                factors = (geometry_info.rho, geometry_info.rho)
            else:
                factors = (geometry_info.rho, geometry_info.sigma)
        elif not isinstance(factors, abc.Sequence):
            raise TypeError('sampling_factors must be a sequence of real '
                            'numbers, not ' + repr(factors))
        factors_len = len(factors)
        factors_ptr = (ctypes.c_double * factors_len)(*factors)
        library.MagickSetSamplingFactors(self.wand, factors_len, factors_ptr)

    @property
    def scene(self):
        """(:class:`numbers.Integral`) The scene number of the current frame
        within an animated image.

        .. versionadded:: 0.5.4
        """
        return library.MagickGetImageScene(self.wand)

    @scene.setter
    def scene(self, value):
        assertions.assert_unsigned_integer(scene=value)
        library.MagickSetImageScene(self.wand, value)

    @property
    def seed(self):
        """(:class:`numbers.Integral`) The seed for random number generator.

        .. warning::

            This property is only available with ImageMagick 7.0.8-41, or
            greater.

        .. versionadded:: 0.5.5
        """
        return self._seed

    @seed.setter
    def seed(self, value):
        if library.MagickSetSeed is None:
            msg = 'Property requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.assert_unsigned_integer(seed=value)
        self._seed = value
        library.MagickSetSeed(self.wand, value)

    @property
    def signature(self):
        """(:class:`str`) The SHA-256 message digest for the image pixel
        stream.

        .. versionadded:: 0.1.9

        """
        sig_str = None
        sig_p = library.MagickGetImageSignature(self.wand)
        if sig_p:
            sig_str = text(ctypes.string_at(sig_p))
            sig_p = library.MagickRelinquishMemory(sig_p)
        return sig_str

    @property
    def size(self):
        """(:class:`tuple`) The pair of (:attr:`width`, :attr:`height`).

        .. note::

            When working with animations, or other layer-based image formats,
            the :attr:`width` & :attr:`height` properties are referencing the
            last frame read into the image stack. To get the :attr:`size`
            of the entire animated images, call
            :meth:`Image.coalesce() <wand.image.BaseImage.coalesce>` method
            immediately after reading the image.
        """
        return self.width, self.height

    @property
    def skewness(self):
        """(:class:`numbers.Real`) The skewness of the image.

        .. tip::

            If you want both :attr:`kurtosis` & :attr:`skewness`, it
            would be faster to call :meth:`kurtosis_channel()` directly.

        .. versionadded:: 0.5.3
        """
        _, s = self.kurtosis_channel()
        return s

    @property
    def standard_deviation(self):
        """(:class:`numbers.Real`) The standard deviation of the image.

        .. tip::

            If you want both :attr:`mean` & :attr:`standard_deviation`, it
            would be faster to call :meth:`mean_channel()` directly.

        .. versionadded:: 0.5.3
        """
        _, s = self.mean_channel()
        return s

    @property
    def stroke_color(self):
        stroke = self.options['stroke']
        return Color(stroke) if stroke else None

    @stroke_color.setter
    def stroke_color(self, color):
        if isinstance(color, str):
            color = Color(color)
        if isinstance(color, Color):
            self.options['stroke'] = color.string
        elif color is None:
            del self.options['stroke']
        else:
            raise TypeError('stroke_color must be a wand.color.Color, not ' +
                            repr(color))

    @property
    def stroke_width(self):
        strokewidth = self.options['strokewidth']
        return float(strokewidth) if strokewidth else None

    @stroke_width.setter
    def stroke_width(self, width):
        assertions.assert_real(stroke_width=width)
        self.options['strokewidth'] = str(width)

    @property
    def ticks_per_second(self):
        """(:class:`numbers.Integral`) Internal clock for animated images.
        .. versionadded:: 0.5.4
        """
        return library.MagickGetImageTicksPerSecond(self.wand)

    @ticks_per_second.setter
    def ticks_per_second(self, value):
        assertions.assert_unsigned_integer(ticks_per_second=value)
        r = library.MagickSetImageTicksPerSecond(self.wand, value)
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def type(self):
        """(:class:`str`) The image type.

        Defines image type as in :const:`IMAGE_TYPES` enumeration.

        It may raise :exc:`ValueError` when the type is unknown.

        .. versionadded:: 0.2.2

        """
        image_type_index = library.MagickGetImageType(self.wand)
        if not image_type_index:  # pragma: no cover
            self.raise_exception()
        return IMAGE_TYPES[image_type_index]

    @type.setter
    @manipulative
    def type(self, image_type):
        assertions.string_in_list(IMAGE_TYPES, 'wand.image.IMAGE_TYPES',
                                  type=image_type)
        r = library.MagickSetImageType(self.wand,
                                       IMAGE_TYPES.index(image_type))
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def units(self):
        """(:class:`str`) The resolution units of this image."""
        r = library.MagickGetImageUnits(self.wand)
        return UNIT_TYPES[r]

    @units.setter
    @manipulative
    def units(self, units):
        assertions.string_in_list(UNIT_TYPES, 'wand.image.UNIT_TYPES',
                                  units=units)
        r = library.MagickSetImageUnits(self.wand, UNIT_TYPES.index(units))
        if not r:  # pragma: no cover
            self.raise_exception()

    @property
    def virtual_pixel(self):
        """(:class:`str`) The virtual pixel of image.
        This can also be set with a value from :const:`VIRTUAL_PIXEL_METHOD`
        ... versionadded:: 0.4.1
        """
        method_index = library.MagickGetImageVirtualPixelMethod(self.wand)
        return VIRTUAL_PIXEL_METHOD[method_index]

    @virtual_pixel.setter
    def virtual_pixel(self, method):
        assertions.string_in_list(VIRTUAL_PIXEL_METHOD,
                                  'wand.image.VIRTUAL_PIXEL_METHOD',
                                  virtual_pixel=method)
        library.MagickSetImageVirtualPixelMethod(
            self.wand,
            VIRTUAL_PIXEL_METHOD.index(method)
        )

    @property
    def wand(self):
        """Internal pointer to the MagickWand instance. It may raise
        :exc:`ClosedImageError` when the instance has destroyed already.

        """
        try:
            return self.resource
        except DestroyedResourceError:
            raise ClosedImageError(repr(self) + ' is closed already')

    @wand.setter
    def wand(self, wand):
        try:
            self.resource = wand
        except TypeError:
            raise TypeError(repr(wand) + ' is not a MagickWand instance')

    @wand.deleter
    def wand(self):
        del self.resource

    @property
    def width(self):
        """(:class:`numbers.Integral`) The width of this image."""
        return library.MagickGetImageWidth(self.wand)

    @width.setter
    @manipulative
    def width(self, width):
        assertions.assert_unsigned_integer(width=width)
        library.MagickSetSize(self.wand, width, self.height)

    @property
    def white_point(self):
        """(:class:`tuple`) The chromatic white point for the image.
        With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
        however, ImageMagick-7 has ``(x, y, z)``.

        .. versionadded:: 0.5.2
        """
        x = ctypes.c_double(0.0)
        y = ctypes.c_double(0.0)
        r = None
        p = None
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickGetImageWhitePoint(self.wand, x, y)
            p = (x.value, y.value)
        else:  # pragma: no cover
            z = ctypes.c_double(0.0)
            r = library.MagickGetImageWhitePoint(self.wand, x, y, z)
            p = (x.value, y.value, z.value)
        if not r:  # pragma: no cover
            self.raise_exception()
        return p

    @white_point.setter
    def white_point(self, coordinates):
        r = None
        if not isinstance(coordinates, abc.Sequence):
            raise TypeError('Primary must be a tuple')
        if MAGICK_VERSION_NUMBER < 0x700:
            x, y = coordinates
            r = library.MagickSetImageWhitePoint(self.wand, x, y)
        else:  # pragma: no cover
            x, y, z = coordinates
            r = library.MagickSetImageWhitePoint(self.wand, x, y, z)
        if not r:  # pragma: no cover
            self.raise_exception()

    @manipulative
    def _auto_orient(self):
        """Fallback for :attr:`auto_orient()` method
        (which wraps :c:func:`MagickAutoOrientImage`),
        fixes orientation by checking EXIF data.

        .. versionadded:: 0.4.1

        """
        v_ptr = library.MagickGetImageProperty(self.wand,
                                               b'exif:orientation')
        if v_ptr:
            exif_orientation = ctypes.string_at(v_ptr)
            v_ptr = library.MagickRelinquishMemory(v_ptr)
        else:
            return

        if not exif_orientation:
            return

        orientation_type = ORIENTATION_TYPES[int(exif_orientation)]

        fn_lookup = {
            'undefined': None,
            'top_left': None,
            'top_right': self.flop,
            'bottom_right': functools.partial(self.rotate, degree=180.0),
            'bottom_left': self.flip,
            'left_top': self.transpose,
            'right_top': functools.partial(self.rotate, degree=90.0),
            'right_bottom': self.transverse,
            'left_bottom': functools.partial(self.rotate, degree=270.0)
        }

        fn = fn_lookup.get(orientation_type)

        if not fn:
            return

        fn()
        self.orientation = 'top_left'

    def _channel_to_mask(self, value):
        """Attempts to resolve user input into a :c:type:`ChannelType`
        bit-mask. User input can be an integer, a string defined in
        :const:`CHANNELS`, or a string following ImageMagick's `CLI format`__.

        __ https://imagemagick.org/script/command-line-options.php#channel

        .. code::

            # User generated bit-mask.
            mask = self._channel_to_mask(CHANNELS['red'] | CHANNELS['green'])
            # Defined constant.
            mask = self._channel_to_mask('red')
            # CLI format.
            mask = self._channel_to_mask('RGB,Sync')

        :param value: Mixed user input.
        :type value: :class:`numbers.Integral` or :class:`str`
        :returns: Bit-mask constant.
        :rtype: :class:`int`

        .. versionadded:: 0.5.5
        """
        mask = -1
        if isinstance(value, numbers.Integral) and not isinstance(value, bool):
            mask = value
        elif isinstance(value, str):
            if value in CHANNELS:
                mask = CHANNELS[value]
            elif libmagick.ParseChannelOption:
                mask = libmagick.ParseChannelOption(binary(value))
        else:
            raise TypeError(repr(value) + ' is an invalid channel type'
                            '; see wand.image.CHANNELS dictionary')
        if mask < 0:
            raise ValueError('expected value from wand.image.CHANNELS, not '
                             + repr(value))
        return mask

    def _gravity_to_offset(self, gravity, width, height):
        """Calculate the top/left offset by a given gravity.

        Some methods in MagickWand's C-API do not respect gravity, but
        instead, expect a x/y offset. This is confusing to folks coming from
        the CLI documentation that does respect gravity

        :param gravity: Value from :const:`GRAVITY_TYPES`.
        :type gravity: :class:`str`
        :raises: :class:`ValueError` if gravity is no known.
        :returns: :class:`numbers.Intergal` top, :class:`numbers.Intergal` left

        .. versionadded:: 0.5.3
        """
        top, left = 0, 0
        assertions.string_in_list(GRAVITY_TYPES, 'wand.image.GRAVITY_TYPES',
                                  gravity=gravity)
        # Set `top` based on given gravity
        if gravity in ('north_west', 'north', 'north_east'):
            top = 0
        elif gravity in ('west', 'center', 'east'):
            top = int(self.height / 2) - int(height / 2)
        elif gravity in ('south_west', 'south', 'south_east'):
            top = self.height - height
        # Set `left` based on given gravity
        if gravity in ('north_west', 'west', 'south_west'):
            left = 0
        elif gravity in ('north', 'center', 'south'):
            left = int(self.width / 2) - int(width / 2)
        elif gravity in ('north_east', 'east', 'south_east'):
            left = self.width - width
        return top, left

    @manipulative
    @trap_exception
    def adaptive_blur(self, radius=0.0, sigma=0.0, channel=None):
        """Adaptively blurs the image by decreasing Gaussian as the operator
        approaches detected edges.

        :see: Example of :ref:`adaptive_blur`.

        :param radius: size of gaussian aperture.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of the gaussian filter.
        :type sigma: :class:`numbers.Real`
        :param channel: Apply the blur effect on a specific channel.
                        See :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        if channel is None:
            r = library.MagickAdaptiveBlurImage(self.wand, radius, sigma)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickAdaptiveBlurImageChannel(self.wand,
                                                           channel_ch,
                                                           radius,
                                                           sigma)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand,
                                                         channel_ch)
                r = library.MagickAdaptiveBlurImage(self.wand, radius, sigma)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def adaptive_resize(self, columns=None, rows=None):
        """Adaptively resize image by applying Mesh interpolation.

        :param columns: width of resized image.
        :type columns: :class:`numbers.Integral`
        :param rows: height of resized image.
        :type rows: :class:`numbers.Integral`

        .. versionadded:: 0.5.3
        """
        if columns is None:
            columns = self.width
        if rows is None:
            rows = self.height
        assertions.assert_integer(columns=columns, rows=rows)
        return library.MagickAdaptiveResizeImage(self.wand, columns, rows)

    @manipulative
    @trap_exception
    def adaptive_sharpen(self, radius=0.0, sigma=0.0, channel=None):
        """Adaptively sharpens the image by sharpening more intensely near
        image edges and less intensely far from edges.

        :see: Example of :ref:`adaptive_sharpen`.

        :param radius: size of gaussian aperture.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of the gaussian filter.
        :type sigma: :class:`numbers.Real`
        :param channel: Apply the sharpen effect on a specific channel.
                        See :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        if channel is None:
            r = library.MagickAdaptiveSharpenImage(self.wand, radius, sigma)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickAdaptiveSharpenImageChannel(self.wand,
                                                              channel_ch,
                                                              radius,
                                                              sigma)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand,
                                                         channel_ch)
                r = library.MagickAdaptiveSharpenImage(self.wand,
                                                       radius,
                                                       sigma)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    def adaptive_threshold(self, width, height, offset=0.0):
        """Applies threshold for each pixel based on neighboring pixel values.

        :param width: size of neighboring pixels on the X-axis.
        :type width: :class:`numbers.Integral`
        :param height: size of neighboring pixels on the Y-axis.
        :type height: :class:`numbers.Integral`
        :param offset: normalized number between `0.0` and
                       :attr:`quantum_range`. Forces the pixels to black if
                       values are below ``offset``.
        :type offset: :class:`numbers.Real`

        .. versionadded:: 0.5.3
        """
        assertions.assert_integer(width=width, height=height)
        assertions.assert_real(offset=offset)
        if MAGICK_VERSION_NUMBER < 0x700:
            offset = int(offset)
        return library.MagickAdaptiveThresholdImage(self.wand, width,
                                                    height, offset)

    @manipulative
    @trap_exception
    def affine(self, sx=1, rx=0, ry=0, sy=1, tx=0, ty=0):
        """Transforms an image by applying an affine matrix.

        .. sourcecode:: text

                                              | sx  rx  0 |
            | x', y', 1 |  =  | x, y, 1 |  *  | ry  sy  0 |
                                              | tx  ty  1 |

        Where ``sx`` & ``sy`` control the scale / shearing,
        ``rx`` & ``ry`` control the rotational values,
        and the ``tx`` & ``ty`` control the translations.

        Set the :attr:`virtual_pixel` attribute before invoking :meth:`affine`
        method.

        .. code::

            from wand.image import Image

            with Image(filename='wizard:') as img:
                img.virtual_pixel = 'mirror'
                img.affine(sx=0.9, ry=1.1)
                img.save(filename='output.png')

        :param sx: Horizontal scale/shear. Default value `1.0`
        :type sx: :class:`numbers.Real`
        :param rx: Horizontal rotation. Default value `0.0`
        :type rx: :class:`numbers.Real`
        :param ry: Vertical rotation. Default value `0.0`
        :type ry: :class:`numbers.Real`
        :param sy: Vertical scale/shear. Default value `1.0`
        :type sy: :class:`numbers.Real`
        :param tx: Horizontal translation. Default value `0.0`
        :type tx: :class:`numbers.Real`
        :param ty: Vertical translation. Default value `0.0`
        :type ty: :class:`numbers.Real`

        .. versionadded:: 0.7.0
        """
        assertions.assert_real(sx=sx, rx=rx, ry=ry,
                               sy=sy, tx=tx, ty=ty)
        dw = library.NewDrawingWand()
        amx = AffineMatrix(sx=sx, rx=rx, ry=ry,
                           sy=sy, tx=tx, ty=ty)
        library.DrawAffine(dw, ctypes.byref(amx))
        ok = library.MagickAffineTransformImage(self.wand, dw)
        dw = library.DestroyDrawingWand(dw)
        return ok

    @manipulative
    @trap_exception
    def annotate(self, text, drawing_wand, left=0, baseline=0, angle=0):
        """Draws text on an image. This method differs from :meth:`caption()`
        as it uses :class:`~wand.drawing.Drawing` class to manage the
        font configuration & style context.

        .. code::

            from wand.drawing import Drawing
            from wand.image import Image

            with Image(filename='input.jpg') as img:
                with Drawing() as ctx:
                    ctx.font_family = 'Times New Roman, Nimbus Roman No9'
                    ctx.font_size = 18
                    ctx.text_decoration = 'underline'
                    ctx.text_kerning = -1
                    img.annotate('Hello World', ctx, left=20, baseline=50)
                img.save(filename='output.jpg')

        :param text: String to annotate on image.
        :type text: :class:`str`
        :param drawing_wand: Font configuration & style context.
        :type text: :class:`wand.drawing.Drawing`
        :param left: X-axis offset of the text baseline.
        :type left: :class:`numbers.Real`
        :param baseline: Y-axis offset of the bottom of the text.
        :type baseline: :class:`numbers.Real`
        :param angle: Degree rotation to draw text at.
        :type angle: :class:`numbers.Real`

        .. versionadded:: 0.5.6
        """
        from .drawing import Drawing
        if not isinstance(drawing_wand, Drawing):
            raise TypeError('drawing_wand must be in instances of ' +
                            'wand.drawing.Drawing, not ' + repr(drawing_wand))
        assertions.assert_real(left=left, baseline=baseline, angle=angle)
        btext = binary(text)
        return library.MagickAnnotateImage(self.wand, drawing_wand.resource,
                                           left, baseline, angle, btext)

    @manipulative
    @trap_exception
    def auto_gamma(self):
        """Adjust the gamma level of an image.

        .. versionadded:: 0.5.4
        """
        return library.MagickAutoGammaImage(self.wand)

    @manipulative
    @trap_exception
    def auto_level(self):
        """Scale the minimum and maximum values to a full quantum range.

        .. versionadded:: 0.5.4
        """
        return library.MagickAutoLevelImage(self.wand)

    @manipulative
    @trap_exception
    def auto_orient(self):
        """Adjusts an image so that its orientation is suitable
        for viewing (i.e. top-left orientation). If available it uses
        :c:func:`MagickAutoOrientImage` (was added in ImageMagick 6.8.9+)
        if you have an older magick library,
        it will use :attr:`_auto_orient()` method for fallback.

        .. versionadded:: 0.4.1

        """
        try:
            return library.MagickAutoOrientImage(self.wand)
        except AttributeError:  # pragma: no cover
            self._auto_orient()
            return True

    @manipulative
    @trap_exception
    def auto_threshold(self, method='kapur'):
        """Automatically performs threshold method to reduce grayscale data
        down to a binary black & white image. Included algorithms are
        Kapur, Otsu, and Triangle methods.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param method: Which threshold method to apply.
                       See :const:`AUTO_THRESHOLD_METHODS`.
                       Defaults to ``'kapur'``.
        :type method: :class:`str`
        :raises WandLibraryVersionError: if function is not available on
                                         system's library.

        .. versionadded:: 0.5.5
        """
        if library.MagickAutoThresholdImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.string_in_list(AUTO_THRESHOLD_METHODS,
                                  'wand.image.AUTO_THRESHOLD_METHODS',
                                  method=method)
        method_idx = AUTO_THRESHOLD_METHODS.index(method)
        return library.MagickAutoThresholdImage(self.wand, method_idx)

    @manipulative
    @trap_exception
    def bilateral_blur(self, width=1.0, height=None, intensity=None,
                       spatial=None):
        """Noise-reducing Gaussian distrbution filter. Preserves edges by
        replacing pixel intensity with weighted averages of neighboring
        pixels. Parameters ``width`` & ``height`` define the area-size around
        each pixel to include in the neighboring sample.

        .. warning::
            This method is only available with ImageMagick version 7.1.1 or
            greater.

        :param width: The neighboring pixel area width.
        :type width: :class:`numbers.Real`
        :param height: The neighboring pixel area height. Default the given
                      ``width`` value.
        :type height: :class:`numbers.Real`
        :param intensity: Influence the distance between colors values.
                          Default 75% of the neighboring pixel area.
        :type intensity: :class:`numbers.Real`
        :param spatial: Influence the coordinate distance weights.
                        Default 25% of the neighboring pixel area.
        :type spatial: :class:`numbers.Real`

        .. versionadded:: 0.7.0
        """
        if MAGICK_VERSION_NUMBER < 0x711:
            msg = 'Method requires ImageMagick version 7.1.1 or greater.'
            raise WandLibraryVersionError(msg)
        if height is None:
            height = width
        if intensity is None:
            intensity = (((width**2) + (height**2))**0.5)*0.75
        if spatial is None:
            spatial = (((width**2) + (height**2))**0.5)*0.25
        assertions.assert_real(width=width, height=height,
                               intensity=intensity, spatial=spatial)
        return library.MagickBilateralBlurImage(self.wand, width, height,
                                                intensity, spatial)

    @manipulative
    @trap_exception
    def black_threshold(self, threshold):
        """Forces all pixels above a given color as black. Leaves pixels
        above threshold unaltered.

        :param threshold: Color to be referenced as a threshold.
        :type threshold: :class:`Color`

        .. versionadded:: 0.5.3
        """
        if isinstance(threshold, str):
            threshold = Color(threshold)
        assertions.assert_color(threshold=threshold)
        with threshold:
            r = library.MagickBlackThresholdImage(self.wand,
                                                  threshold.resource)
        return r

    @manipulative
    @trap_exception
    def blue_shift(self, factor=1.5):
        """Mutes colors of the image by shifting blue values.

        :see: Example of :ref:`blue_shift`

        :param factor: Amount to adjust values.
        :type factor: :class:`numbers.Real`

        .. versionadded:: 0.5.3
        """
        assertions.assert_real(factor=factor)
        return library.MagickBlueShiftImage(self.wand, factor)

    @manipulative
    @trap_exception
    def blur(self, radius=0.0, sigma=0.0, channel=None):
        """Blurs the image.  Convolve the image with a gaussian operator
        of the given ``radius`` and standard deviation (``sigma``).
        For reasonable results, the ``radius`` should be larger
        than ``sigma``.  Use a ``radius`` of 0 and :meth:`blur()` selects
        a suitable ``radius`` for you.

        :see: Example of :ref:`blur`.

        :param radius: the radius of the, in pixels,
                       not counting the center pixel. Default is ``0.0``.
        :type radius: :class:`numbers.Real`
        :param sigma: the standard deviation of the, in pixels. Default value
                      is ``0.0``.
        :type sigma: :class:`numbers.Real`
        :param channel: Optional color channel to apply blur. See
                        :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.4.5

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument.

        .. versionchanged:: 0.5.7
           Positional arguments ``radius`` & ``sigman`` have been converted to
           key-word arguments.
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        if channel is None:
            r = library.MagickBlurImage(self.wand, radius, sigma)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickBlurImageChannel(self.wand,
                                                   channel_ch,
                                                   radius,
                                                   sigma)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickBlurImage(self.wand, radius, sigma)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @trap_exception
    def border(self, color, width, height, compose="copy"):
        """Surrounds the image with a border.

        :param bordercolor: the border color pixel wand
        :type image: :class:`~wand.color.Color`
        :param width: the border width
        :type width: :class:`numbers.Integral`
        :param height: the border height
        :type height: :class:`numbers.Integral`
        :param compose: Use composite operator when applying frame. Only used
                        if called with ImageMagick 7+.
        :type compose: :class:`str`

        .. versionadded:: 0.3.0
        .. versionchanged:: 0.5.0
           Added ``compose`` parameter, and ImageMagick 7 support.
        """
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(color=color)
        with color:
            if MAGICK_VERSION_NUMBER < 0x700:
                result = library.MagickBorderImage(self.wand, color.resource,
                                                   width, height)
            else:  # pragma: no cover
                assertions.string_in_list(COMPOSITE_OPERATORS,
                                          'wand.image.COMPOSITE_OPERATORS',
                                          compose=compose)
                compose_idx = COMPOSITE_OPERATORS.index(compose)
                result = library.MagickBorderImage(self.wand, color.resource,
                                                   width, height, compose_idx)
        return result

    @manipulative
    @trap_exception
    def brightness_contrast(self, brightness=0.0, contrast=0.0, channel=None):
        """Converts ``brightness`` & ``contrast`` parameters into a slope &
        intercept, and applies a polynomial function.

        :param brightness: between ``-100.0`` and ``100.0``. Default is ``0.0``
                           for unchanged.
        :type brightness: :class:`numbers.Real`
        :param contrast: between ``-100.0`` and ``100.0``. Default is ``0.0``
                         for unchanged.
        :type contrast: :class:`numbers.Real`
        :param channel: Isolate a single color channel to apply contrast.
                        See :const:`CHANNELS`.

        .. versionadded:: 0.5.4

        .. versionchanged:: 0.5.5
           Optional ``channel`` argument added.
        """
        assertions.assert_real(brightness=brightness, contrast=contrast)
        if channel is None:
            r = library.MagickBrightnessContrastImage(self.wand,
                                                      brightness,
                                                      contrast)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickBrightnessContrastImageChannel(self.wand,
                                                                 channel_ch,
                                                                 brightness,
                                                                 contrast)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickBrightnessContrastImage(self.wand,
                                                          brightness,
                                                          contrast)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def canny(self, radius=0.0, sigma=1.0, lower_percent=0.1,
              upper_percent=0.3):
        """Detect edges by leveraging a multi-stage Canny algorithm.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param radius: Size of gaussian filter.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of gaussian filter.
        :type sigma: :class:`numbers.Real`
        :param lower_percent: Normalized lower threshold. Values between
                              ``0.0`` (0%) and ``1.0`` (100%). The default
                              value is ``0.1`` or 10%.
        :type lower_percent: :class:`numbers.Real`
        :param upper_percent: Normalized upper threshold. Values between
                              ``0.0`` (0%) and ``1.0`` (100%). The default
                              value is ``0.3`` or 30%.
        :type upper_percent: :class:`numbers.Real`
        :raises WandLibraryVersionError: if function is not available on
                                         system's library.

        .. versionadded:: 0.5.5
        """
        if library.MagickCannyEdgeImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.assert_real(radius=radius, sigma=sigma,
                               lower_percent=lower_percent,
                               upper_percent=upper_percent)
        return library.MagickCannyEdgeImage(self.wand, radius, sigma,
                                            lower_percent, upper_percent)

    @manipulative
    def caption(self, text, left=0, top=0, width=None, height=None, font=None,
                gravity=None):
        """Writes a caption ``text`` into the position.

        :param text: text to write
        :type text: :class:`str`
        :param left: x offset in pixels
        :type left: :class:`numbers.Integral`
        :param top: y offset in pixels
        :type top: :class:`numbers.Integral`
        :param width: width of caption in pixels.
                      default is :attr:`width` of the image
        :type width: :class:`numbers.Integral`
        :param height: height of caption in pixels.
                       default is :attr:`height` of the image
        :type height: :class:`numbers.Integral`
        :param font: font to use.  default is :attr:`font` of the image
        :type font: :class:`wand.font.Font`
        :param gravity: text placement gravity.
                        uses the current :attr:`gravity` setting of the image
                        by default
        :type gravity: :class:`str`

        .. versionadded:: 0.3.0

        """
        assertions.assert_integer(left=left, top=top)
        if font is not None and not isinstance(font, Font):
            raise TypeError('font must be a wand.font.Font, not ' + repr(font))
        if gravity is not None:
            assertions.string_in_list(GRAVITY_TYPES,
                                      'wand.image.GRAVITY_TYPES',
                                      gravity=gravity)
        if width is None:
            width = self.width - left
        else:
            assertions.assert_integer(width=width)
        if height is None:
            height = self.height - top
        else:
            assertions.assert_integer(height=height)
        if font is None:
            try:
                font = self.font
                if font is None:
                    raise TypeError()
            except TypeError:
                raise TypeError('font must be specified or existing in image')
        with Image() as textboard:
            library.MagickSetSize(textboard.wand, width, height)
            textboard.font = font
            textboard.gravity = gravity or self.gravity
            with Color('transparent') as background_color:
                library.MagickSetBackgroundColor(textboard.wand,
                                                 background_color.resource)
            textboard.read(filename=b'caption:' + text.encode('utf-8'))
            self.composite(textboard, left, top)

    def cdl(self, ccc):
        """Alias for :meth:`color_decision_list`.

        .. versionadded:: 0.5.7
        """
        return self.color_decision_list(ccc)

    @trap_exception
    def charcoal(self, radius, sigma):
        """Transform an image into a simulated charcoal drawing.

        :see: Example of :ref:`charcoal`.

        :param radius: The size of the Gaussian operator.
        :type radius: :class:`numbers.Real`
        :param sigma: The standard deviation of the Gaussian.
        :type sigma: :class:`numbers.Real`

        .. versionadded:: 0.5.3
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        return library.MagickCharcoalImage(self.wand, radius, sigma)

    @manipulative
    @trap_exception
    def chop(self, width=None, height=None, x=None, y=None, gravity=None):
        """Removes a region of an image, and reduces the image size
        accordingly.

        :param width: Size of region.
        :type width: :class:`numbers.Integral`
        :param height: Size of region.
        :type height: :class:`numbers.Integral`
        :param x: Offset on the X-axis.
        :type x: :class:`numbers.Integral`
        :param y: Offset on the Y-axis.
        :type y: :class:`numbers.Integral`
        :param gravity: Helper to auto-calculate offset.
                        See :const:`GRAVITY_TYPES`.
        :type gravity: :class:`str`

        .. versionadded:: 0.5.5

        .. versionchanged:: 0.6.8
           Added ``gravity`` argument.

        .. versionchanged:: 0.6.12
           Allow zero values for ``width`` & ``height`` arguments.

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        ow, oh = self.size
        if width is None:
            width = ow
        if height is None:
            height = oh
        assertions.assert_unsigned_integer(width=width, height=height)
        if x is None:
            x = 0
        if y is None:
            y = 0
        assertions.assert_integer(x=x, y=y)
        if gravity is not None:
            gy, gx = self._gravity_to_offset(gravity, width, height)
            x += gx
            y += gy
        return library.MagickChopImage(self.wand, width, height, x, y)

    @manipulative
    @trap_exception
    def clahe(self, width, height, number_bins, clip_limit):
        """Contrast limited adaptive histogram equalization.

        .. warning::

            The CLAHE method is only available with ImageMagick-7.

        :param width: Tile division width.
        :type width: :class:`numbers.Integral`
        :param height: Tile division height.
        :type height: :class:`numbers.Integral`
        :param number_bins: Histogram bins.
        :type number_bins: :class:`numbers.Real`
        :param clip_limit: contrast limit.
        :type clip_limit: :class:`numbers.Real`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickCLAHEImage is None:
            msg = 'CLAHE method not defined in ImageMagick library.'
            raise WandLibraryVersionError(msg)
        assertions.assert_unsigned_integer(width=width, height=height)
        assertions.assert_real(number_bins=number_bins, clip_limit=clip_limit)
        return library.MagickCLAHEImage(self.wand, width, height,
                                        number_bins, clip_limit)

    @trap_exception
    def clamp(self, channel=None):
        """Restrict color values between 0 and quantum range. This is useful
        when applying arithmetic operations that could result in color values
        over/under-flowing.

        :param channel: Optional color channel.
        :type channel: :class:`str`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        if channel is None:
            r = library.MagickClampImage(self.wand)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickClampImageChannel(self.wand, channel_ch)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickClampImage(self.wand)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    def clone(self):
        """Clones the image. It is equivalent to call :class:`Image` with
        ``image`` parameter. ::

            with img.clone() as cloned:
                # manipulate the cloned image
                pass

        :returns: the cloned new image
        :rtype: :class:`Image`

        .. versionadded:: 0.1.1

        """
        return Image(image=self)

    @manipulative
    @trap_exception
    def clut(self, image, method='undefined', channel=None):
        """Replace color values by referencing another image as a Color
        Look Up Table.

        :param image: Color Look Up Table image.
        :type image: :class:`wand.image.BaseImage`
        :param method: Pixel Interpolate method. Only available with
                       ImageMagick-7. See :const:`PIXEL_INTERPOLATE_METHODS`
        :type method: :class:`str`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument.
        """
        if not isinstance(image, BaseImage):
            raise TypeError('image must be a base image, not ' + repr(image))
        if MAGICK_VERSION_NUMBER < 0x700:
            if channel is None:
                r = library.MagickClutImage(self.wand, image.wand)
            else:
                channel_ch = self._channel_to_mask(channel)
                r = library.MagickClutImageChannel(self.wand,
                                                   channel_ch,
                                                   image.wand)
        else:  # pragma: no cover
            assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                      'wand.image.PIXEL_INTERPOLATE_METHODS',
                                      pixel_interpolate_method=method)
            method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
            if channel is None:
                r = library.MagickClutImage(self.wand, image.wand, method_idx)
            else:
                channel_ch = self._channel_to_mask(channel)
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickClutImage(self.wand, image.wand, method_idx)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def coalesce(self):
        """Rebuilds image sequence with each frame size the same as first
        frame, and composites each frame atop of previous.

        .. note::

            Only affects GIF, and other formats with multiple pages/layers.

        .. versionadded:: 0.5.0
        """
        r = library.MagickCoalesceImages(self.wand)
        if r:
            self.wand = r
            self.reset_sequence()
        return bool(r)

    @manipulative
    @trap_exception
    def color_decision_list(self, ccc):
        """Applies color correction from a Color Correction Collection (CCC)
        xml string. An example of xml:

        .. code-block:: xml

            <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
                <ColorCorrection id="cc03345">
                    <SOPNode>
                        <Slope> 0.9 1.2 0.5 </Slope>
                        <Offset> 0.4 -0.5 0.6 </Offset>
                        <Power> 1.0 0.8 1.5 </Power>
                    </SOPNode>
                    <SATNode>
                        <Saturation> 0.85 </Saturation>
                    </SATNode>
                </ColorCorrection>
            </ColorCorrectionCollection>

        :param ccc: A XML string of the CCC contents.
        :type ccc: :class:`str`

        .. versionadded:: 0.5.7
        """
        return library.MagickColorDecisionListImage(self.wand, binary(ccc))

    def color_map(self, index, color=None):
        """Get & Set a color at a palette index. If ``color`` is given,
        the color at the index location will be set & returned. Omitting the
        ``color`` argument will only return the color value at index.

        Valid indexes are between ``0`` and total :attr:`colors` of the image.

        .. note::

            Ensure the image type is set to ``'palette'`` before calling the
            :meth:`color_map` method. For example::

                with Image(filename='graph.png') as img:
                    img.type = 'palette'
                    palette = [img.color_map(idx) for idx in range(img.colors)]
                    # ...

        :param index: The color position of the image palette.
        :type index: :class:`numbers.Integral`
        :param color: Optional color to _set_ at the given index.
        :type color: :class:`wand.color.Color`
        :returns: Color at index.
        :rtype: :class:`wand.color.Color`

        .. versionadded:: 0.5.3
        """
        if not isinstance(index, numbers.Integral):
            raise TypeError('index most be an integer, not ' + repr(index))
        if index < 0 or index >= self.colors:
            raise ValueError('index is out of palette range')
        if color:
            if isinstance(color, str):
                color = Color(color)
            if not isinstance(color, Color):
                raise TypeError('expecting in instance of Color, not ' +
                                repr(color))
            with color:
                r = library.MagickSetImageColormapColor(self.wand,
                                                        index,
                                                        color.resource)
                if not r:  # pragma: no cover
                    self.raise_exception()
        else:
            color_ptr = library.NewPixelWand()
            r = library.MagickGetImageColormapColor(self.wand,
                                                    index,
                                                    color_ptr)
            if not r:  # pragma: no cover
                color_ptr = library.DestroyPixelWand(color_ptr)
                self.raise_exception()
            color = Color.from_pixelwand(color_ptr)
            color_ptr = library.DestroyPixelWand(color_ptr)
        return color

    @manipulative
    @trap_exception
    def color_matrix(self, matrix):
        """Adjust color values by applying a matrix transform per pixel.

        Matrix should be given as 2D list, with a max size of 6x6.

        An example of 3x3 matrix::

            matrix = [
                [1.0, 0.0, 0.0],
                [0.0, 1.0, 0.0],
                [0.0, 0.0, 1.0],
            ]

        Which would translate RGB color channels by calculating the
        following:

        .. math::

            \\begin{aligned}
            red' &= 1.0 * red + 0.0 * green + 0.0 * blue\\\\
            green' &= 0.0 * red + 1.0 * green + 0.0 * blue\\\\
            blue' &= 0.0 * red + 0.0 * green + 1.0 * blue\\\\
            \\end{aligned}

        For RGB colorspace images, the rows & columns are laid out as:

        +---------+-----+-------+------+------+-------+--------+
        |         | Red | Green | Blue | n/a  | Alpha | Offset |
        +=========+=====+=======+======+======+=======+========+
        | Red'    | 1   | 0     | 0    | 0    | 0     | 0      |
        +---------+-----+-------+------+------+-------+--------+
        | Green'  | 0   | 1     | 0    | 0    | 0     | 0      |
        +---------+-----+-------+------+------+-------+--------+
        | Blue'   | 0   | 0     | 1    | 0    | 0     | 0      |
        +---------+-----+-------+------+------+-------+--------+
        | n/a     | 0   | 0     | 0    | 0    | 0     | 0      |
        +---------+-----+-------+------+------+-------+--------+
        | Alpha'  | 0   | 0     | 0    | 0    | 0     | 0      |
        +---------+-----+-------+------+------+-------+--------+
        | Offset' | 0   | 0     | 0    | 0    | 0     | 0      |
        +---------+-----+-------+------+------+-------+--------+

        Or for a CMYK colorspace image:

        +----------+------+--------+---------+-------+-------+--------+
        |          | Cyan | Yellow | Magenta | Black | Alpha | Offset |
        +==========+======+========+=========+=======+=======+========+
        | Cyan'    | 1    | 0      | 0       | 0     | 0     | 0      |
        +----------+------+--------+---------+-------+-------+--------+
        | Yellow'  | 0    | 1      | 0       | 0     | 0     | 0      |
        +----------+------+--------+---------+-------+-------+--------+
        | Magenta' | 0    | 0      | 1       | 0     | 0     | 0      |
        +----------+------+--------+---------+-------+-------+--------+
        | Black'   | 0    | 0      | 0       | 1     | 0     | 0      |
        +----------+------+--------+---------+-------+-------+--------+
        | Alpha'   | 0    | 0      | 0       | 0     | 0     | 0      |
        +----------+------+--------+---------+-------+-------+--------+
        | Offset'  | 0    | 0      | 0       | 0     | 0     | 0      |
        +----------+------+--------+---------+-------+-------+--------+

        See `color-matrix`__ for examples.

        __ https://www.imagemagick.org/Usage/color_mods/#color-matrix

        :see: Example of :ref:`color_matrix`.

        :param matrix: 2D List of doubles.
        :type matrix: :class:`collections.abc.Sequence`

        .. versionadded:: 0.5.3
        """
        if not isinstance(matrix, abc.Sequence):
            raise TypeError('matrix must be a sequence, not ' + repr(matrix))
        rows = len(matrix)
        columns = None
        values = []
        for row in matrix:
            if not isinstance(row, abc.Sequence):
                raise TypeError('nested row must be a sequence, not ' +
                                repr(row))
            if columns is None:
                columns = len(row)
            elif columns != len(row):
                raise ValueError('rows have different column length')
            for column in row:
                values.append(str(column))
        kernel = binary('{0}x{1}:{2}'.format(columns,
                                             rows,
                                             ','.join(values)))
        exception_info = libmagick.AcquireExceptionInfo()
        if MAGICK_VERSION_NUMBER < 0x700:
            kernel_info = libmagick.AcquireKernelInfo(kernel)
        else:  # pragma: no cover
            kernel_info = libmagick.AcquireKernelInfo(kernel, exception_info)
        exception_info = libmagick.DestroyExceptionInfo(exception_info)
        r = library.MagickColorMatrixImage(self.wand, kernel_info)
        kernel_info = libmagick.DestroyKernelInfo(kernel_info)
        return r

    @manipulative
    @trap_exception
    def color_threshold(self, start=None, stop=None):
        """Forces all pixels in color range to white, and all other pixels to
        black.

        .. note::

            This method is only works with ImageMagick-7.0.10, or later.

        :param start: Color to begin color range.
        :type start: :class:`wand.color.Color`
        :param stop: Color to end color range.
        :type stop: :class:`wand.color.Color`

        .. versionadded:: 0.6.4
        """
        if isinstance(start, str):
            start = Color(start)
        if isinstance(stop, str):
            stop = Color(stop)
        assertions.assert_color(start=start, stop=stop)
        if library.MagickColorThresholdImage is None:
            msg = 'Method "color_threshold" not available.'
            raise WandLibraryVersionError(msg)
        with start:
            with stop:
                r = library.MagickColorThresholdImage(self.wand,
                                                      start.resource,
                                                      stop.resource)
        return r

    @manipulative
    @trap_exception
    def colorize(self, color=None, alpha=None):
        """Blends a given fill color over the image. The amount of blend is
        determined by the color channels given by the ``alpha`` argument.

        :see: Example of :ref:`colorize`.

        :param color: Color to paint image with.
        :type color: :class:`wand.color.Color`
        :param alpha: Defines how to blend color.
        :type alpha: :class:`wand.color.Color`

        .. versionadded:: 0.5.3
        """
        if isinstance(color, str):
            color = Color(color)
        if isinstance(alpha, str):
            alpha = Color(alpha)
        assertions.assert_color(color=color, alpha=alpha)
        with color:
            with alpha:
                r = library.MagickColorizeImage(self.wand,
                                                color.resource,
                                                alpha.resource)
        return r

    @manipulative
    @trap_exception
    def combine(self, channel='rgb_channels', colorspace='rgb'):
        """Creates an image where each color channel is assigned by a grayscale
        image in a sequence.

        .. warning::

            If your using ImageMagick-6, use ``channel`` argument to control
            the color-channel order.  With ImageMagick-7, the ``channel``
            argument has been replaced with ``colorspace``.

        For example::

            for wand.image import Image

            with Image() as img:
                img.read(filename='red_channel.png')
                img.read(filename='green_channel.png')
                img.read(filename='blue_channel.png')
                img.combine(colorspace='rgb')
                img.save(filename='output.png')

        :param channel: Determines the colorchannel ordering of the
                        sequence. Only used for ImageMagick-6.
                        See :const:`CHANNELS`.
        :type channel: :class:`str`
        :param colorspace: Determines the colorchannel ordering of the
                           sequence. Only used for ImageMagick-7.
                           See :const:`COLORSPACE_TYPES`.
        :type colorspace: :class:`str`

        .. versionadded:: 0.5.9
        """
        assertions.string_in_list(COLORSPACE_TYPES,
                                  'wand.image.COLORSPACE_TYPES',
                                  colorspace=colorspace)
        library.MagickResetIterator(self.wand)
        colorspace_c = COLORSPACE_TYPES.index(colorspace)
        channel_c = self._channel_to_mask(channel)
        if MAGICK_VERSION_NUMBER < 0x700:
            new_wand = library.MagickCombineImages(self.wand, channel_c)
        else:  # pragma: no-cover
            new_wand = library.MagickCombineImages(self.wand, colorspace_c)
        if new_wand:
            self.wand = new_wand
            self.reset_sequence()
        return bool(new_wand)

    @manipulative
    def compare(self, image, metric='undefined', highlight=None,
                lowlight=None):
        """Compares an image with another, and returns a reconstructed
        image & computed distortion. The reconstructed image will show the
        differences colored with ``highlight``, and similarities with
        ``lowlight``.

        If you need the computed distortion between to images without a
        image being reconstructed, use :meth:`get_image_distortion()` method.

        Set :attr:`fuzz` property to adjust pixel-compare thresholds.

        For example::

            from wand.image import Image

            with Image(filename='input.jpg') as base:
                with Image(filename='subject.jpg') as img:
                    base.fuzz = base.quantum_range * 0.20  # Threshold of 20%
                    result_image, result_metric = base.compare(img)
                    with result_image:
                        result_image.save(filename='diff.jpg')

        :param image: The reference image
        :type image: :class:`wand.image.Image`
        :param metric: The metric type to use for comparing. See
                       :const:`COMPARE_METRICS`
        :type metric: :class:`str`
        :param highlight: Set the color of the delta pixels in the resulting
                          difference image.
        :type highlight: :class:`~wand.color.Color` or :class:`str`
        :param lowlight: Set the color of the similar pixels in the resulting
                          difference image.
        :type lowlight: :class:`~wand.color.Color` or :class:`str`
        :returns: The difference image(:class:`wand.image.Image`),
                  the computed distortion between the images
                  (:class:`numbers.Real`)
        :rtype: :class:`tuple` ( :class:`Image`, :class:`numbers.Real` )

        .. versionadded:: 0.4.3

        .. versionchanged:: 0.5.3
           Added support for ``highlight`` & ``lowlight``.
        """
        assertions.string_in_list(COMPARE_METRICS,
                                  'wand.image.COMPARE_METRICS',
                                  metric=metric)
        if highlight:
            if isinstance(highlight, Color):
                highlight = highlight.string
            library.MagickSetImageArtifact(self.wand,
                                           b'compare:highlight-color',
                                           binary(highlight))
        if lowlight:
            if isinstance(lowlight, Color):
                lowlight = lowlight.string
            library.MagickSetImageArtifact(self.wand,
                                           b'compare:lowlight-color',
                                           binary(lowlight))
        metric = COMPARE_METRICS.index(metric)
        distortion = ctypes.c_double(0.0)
        compared_image = library.MagickCompareImages(self.wand, image.wand,
                                                     metric,
                                                     ctypes.byref(distortion))
        return Image(BaseImage(compared_image)), distortion.value

    @manipulative
    def complex(self, operator='undefined', snr=None):
        """Performs `complex`_ mathematics against two images in a sequence,
        and generates a new image with two results.

        .. seealso::

            :meth:`forward_fourier_transform` &
            :meth:`inverse_fourier_transform`

        .. code::

            from wand.image import Image

            with Image(filename='real_part.png') as imgA:
                with Image(filename='imaginary_part.png') as imgB:
                    imgA.sequence.append(imgB)
                with imgA.complex('conjugate') as results:
                    results.save(filename='output-%02d.png')

        .. _complex: https://en.wikipedia.org/wiki/Complex_number

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param operator: Define which mathematic operator to perform. See
                         :const:`COMPLEX_OPERATORS`.
        :type operator: :class:`str`
        :param snr: Optional ``SNR`` parameter for ``'divide'`` operator.
        :type snr: :class:`str`
        :raises WandLibraryVersionError: If ImageMagick library does not
                                         support this function.

        .. versionadded:: 0.5.5
        """
        if library.MagickComplexImages is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.string_in_list(COMPLEX_OPERATORS,
                                  'wand.image.COMPLEX_OPERATORS',
                                  operator=operator)
        if snr is not None:
            key = b'complex:snr=float'
            val = to_bytes(snr)
            library.MagickSetImageArtifact(self.wand, key, val)
        operator_idx = COMPLEX_OPERATORS.index(operator)
        wand = library.MagickComplexImages(self.wand, operator_idx)
        if not bool(wand):
            self.raise_exception()
        return Image(BaseImage(wand))

    @trap_exception
    def composite(self, image, left=None, top=None, operator='over',
                  arguments=None, gravity=None):
        """Places the supplied ``image`` over the current image, with the top
        left corner of ``image`` at coordinates ``left``, ``top`` of the
        current image.  The dimensions of the current image are not changed.

        :param image: the image placed over the current image
        :type image: :class:`wand.image.Image`
        :param left: the x-coordinate where `image` will be placed
        :type left: :class:`numbers.Integral`
        :param top: the y-coordinate where `image` will be placed
        :type top: :class:`numbers.Integral`
        :param operator: the operator that affects how the composite
                         is applied to the image.  available values
                         can be found in the :const:`COMPOSITE_OPERATORS`
                         list. Default is ``'over'``.
        :type operator: :class:`str`
        :param arguments: Additional numbers given as a geometry string, or
                         comma delimited values. This is needed for
                         ``'blend'``, ``'displace'``, ``'dissolve'``, and
                         ``'modulate'`` operators.
        :type arguments: :class:`str`
        :param gravity: Calculate the ``top`` & ``left`` values based on
                        gravity value from :const:`GRAVITY_TYPES`.
        :type: gravity: :class:`str`

        .. versionadded:: 0.2.0

        .. versionchanged:: 0.5.3
           The operator can be set, as well as additional composite arguments.

        .. versionchanged:: 0.5.3
           Optional ``gravity`` argument was added.

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        if top is None:
            top = 0
        if left is None:
            left = 0
        assertions.assert_integer(left=left, top=top)
        if gravity is None and self.gravity != 'forget':
            gravity = self.gravity
        if gravity is not None:
            gtop, gleft = self._gravity_to_offset(gravity,
                                                  image.width,
                                                  image.height)
            top += gtop
            left += gleft
        assertions.string_in_list(COMPOSITE_OPERATORS,
                                  'wand.image.COMPOSITE_OPERATORS',
                                  operator=operator)
        op = COMPOSITE_OPERATORS.index(operator)
        if arguments:
            assertions.assert_string(arguments=arguments)
            r = library.MagickSetImageArtifact(image.wand,
                                               b'compose:args',
                                               binary(arguments))
            if not r:
                self.raise_exception()
            r = library.MagickSetImageArtifact(self.wand,
                                               b'compose:args',
                                               binary(arguments))
            if not r:  # pragma: no cover
                self.raise_exception()
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickCompositeImage(self.wand, image.wand, op,
                                             int(left), int(top))
        else:  # pragma: no cover
            r = library.MagickCompositeImage(self.wand, image.wand, op, True,
                                             int(left), int(top))
        return r

    @manipulative
    @trap_exception
    def composite_channel(self, channel, image, operator, left=None, top=None,
                          arguments=None, gravity=None):
        """Composite two images using the particular ``channel``.

        :param channel: the channel type.  available values can be found
                        in the :const:`CHANNELS` mapping
        :param image: the composited source image.
                      (the receiver image becomes the destination)
        :type image: :class:`Image`
        :param operator: the operator that affects how the composite
                         is applied to the image.  available values
                         can be found in the :const:`COMPOSITE_OPERATORS`
                         list
        :type operator: :class:`str`
        :param left: the column offset of the composited source image
        :type left: :class:`numbers.Integral`
        :param top: the row offset of the composited source image
        :type top: :class:`numbers.Integral`
        :param arguments: Additional numbers given as a geometry string, or
                         comma delimited values. This is needed for
                         ``'blend'``, ``'displace'``, ``'dissolve'``, and
                         ``'modulate'`` operators.
        :type arguments: :class:`str`
        :param gravity: Calculate the ``top`` & ``left`` values based on
                        gravity value from :const:`GRAVITY_TYPES`.
        :type: gravity: :class:`str`
        :raises ValueError: when the given ``channel`` or
                            ``operator`` is invalid

        .. versionadded:: 0.3.0

        .. versionchanged:: 0.5.3
           Support for optional composite arguments has been added.

        .. versionchanged:: 0.5.3
           Optional ``gravity`` argument was added.

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        assertions.assert_string(operator=operator)
        ch_const = self._channel_to_mask(channel)
        if top is None:
            top = 0
        if left is None:
            left = 0
        assertions.assert_integer(left=left, top=top)
        if gravity is not None:
            gtop, gleft = self._gravity_to_offset(gravity,
                                                  image.width,
                                                  image.height)
            top += gtop
            left += gleft
        assertions.string_in_list(COMPOSITE_OPERATORS,
                                  'wand.image.COMPOSITE_OPERATORS',
                                  operator=operator)
        op = COMPOSITE_OPERATORS.index(operator)
        if arguments:
            assertions.assert_string(arguments=arguments)
            library.MagickSetImageArtifact(image.wand,
                                           b'compose:args',
                                           binary(arguments))
            library.MagickSetImageArtifact(self.wand,
                                           b'compose:args',
                                           binary(arguments))
        if library.MagickCompositeImageChannel:
            r = library.MagickCompositeImageChannel(self.wand, ch_const,
                                                    image.wand, op, left,
                                                    top)
        else:  # pragma: no cover
            ch_mask = library.MagickSetImageChannelMask(self.wand, ch_const)
            r = library.MagickCompositeImage(self.wand, image.wand, op, True,
                                             left, top)
            library.MagickSetImageChannelMask(self.wand, ch_mask)
        return r

    @manipulative
    @trap_exception
    def concat(self, stacked=False):
        """Concatenates images in stack into a single image. Left-to-right
        by default, top-to-bottom if ``stacked`` is True.

        :param stacked: stack images in a column, or in a row (default)
        :type stacked: :class:`bool`

        .. versionadded:: 0.5.0
        """
        assertions.assert_bool(stacked=stacked)
        r = library.MagickAppendImages(self.wand, stacked)
        if r:
            self.wand = r
            self.reset_sequence()
        return bool(r)

    def connected_components(self, **kwargs):
        """Evaluates binary image, and groups connected pixels into objects.
        This method will also return a list of
        :class:`ConnectedComponentObject` instances that will describe an
        object's features.

        .. code::

            from wand.image import Image

            with Image(filename='objects.gif') as img:
                objects = img.connected_components()
            for cc_obj in objects:
                print("{0._id}: {0.size} {0.offset}".format(cc_obj))

            #=> 0: (256, 171) (0, 0)
            #=> 2: (120, 135) (104, 18)
            #=> 3: (50, 36) (129, 44)
            #=> 4: (21, 23) (0, 45)
            #=> 1: (4, 10) (252, 0)

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        .. tip::

            Set :attr:`fuzz` property to increase pixel matching by reducing
            tolerance of color-value comparisons::

                from wand.image import Image
                from wand.version import QUANTUM_RANGE

                with Image(filename='objects.gif') as img:
                    img.fuzz = 0.1 * QUANTUM_RANGE  # 10%
                    objects = img.connected_components()

        :param angle_threshold: Optional argument that merges any region with
                                an equivalent ellipse smaller than a given
                                value. Requires ImageMagick-7.0.9-24, or
                                greater.
        :type angle_threshold: :class:`str`
        :param area_threshold: Optional argument to merge objects under an
                               area size.
        :type area_threshold: :class:`str`
        :param background_id: Optional argument to identify which object
                              should be the background. Requires
                              ImageMagick-7.0.9-24, or greater.
        :type background_id: :class:`str`
        :param circularity_threshold: Optional argument that merges any region
                                      smaller than value defined as:
                                      ``4*pi*area/perimeter^2``. Requires
                                      ImageMagick-7.0.9-24, or greater.
        :type circularity_threshold: :class:`str`
        :param connectivity: Either ``4``, or ``8``. A value of ``4`` will
                            evaluate each pixels top-bottom, & left-right
                            neighbors. A value of ``8`` will use the same
                            pixels as with ``4``, but will also include the
                            four corners of each pixel. Default value of ``4``.
        :type connectivity: :class:`numbers.Integral`
        :param diameter_threshold: Optional argument to merge any region under
                                   a given value. A region is defined as:
                                   ``sqr(4*area/pi)``. Requires
                                   ImageMagick-7.0.9-24.
        :type diameter_threshold: :class:`str`
        :param eccentricity_threshold: Optional argument to merge any region
                                       with ellipse eccentricity under a given
                                       value. Requires ImageMagick-7.0.9-24,
                                       or greater.
        :param keep: Comma separated list of object IDs to isolate, the reset
                     are converted to transparent.
        :type keep: :class:`str`
        :param keep_colors: Semicolon separated list of objects to keep by
                            their color value. Requires ImageMagick-7.0.9-24,
                            or greater.
        :type keep_colors: :class:`str`
        :param keep_top: Keeps only the top number of objects by area.
                         Requires ImageMagick-7.0.9-24, or greater.
        :type keep_top: :class:`str`
        :param major_axis_threshold: Optional argument to merge any ellipse
                                     with a major axis smaller then given
                                     value. Requires ImageMagick-7.0.9-24,
                                     or greater.
        :type major_axis_threshold: :class:`str`
        :param mean_color: Optional argument. Replace object color with mean
                           color of the source image.
        :type mean_color: :class:`bool`
        :param minor_axis_threshold: Optional argument to merge any ellipse
                                     with a minor axis smaller then given
                                     value. Requires ImageMagick-7.0.9-24,
                                     or greater.
        :type minor_axis_threshold: :class:`str`
        :param perimeter_threshold: Optional argument to merge any region with
                                    a perimeter smaller than the given value.
                                    Requires ImageMagick-7.0.9-24, or greater.
        :param remove: Comma separated list of object IDs to ignore, and
                       convert to transparent.
        :type remove: :class:`str`
        :param remove_colors: Semicolon separated list of objects to remove
                              by there color. Requires ImageMagick-7.0.9-24,
                              or greater.
        :type remove_colors: :class:`str`
        :returns: A list of :class:`ConnectedComponentObject`.
        :rtype: :class:`list` [:class:`ConnectedComponentObject`]
        :raises WandLibraryVersionError: If ImageMagick library
                                         does not support this method.

        .. versionadded:: 0.5.5

        .. versionchanged:: 0.5.6
           Added ``mean_color``, ``keep``, & ``remove`` optional arguments.

        .. versionchanged:: 0.6.4
           Added ``angle_threshold``, ``circularity_threshold``,
           ``diameter_threshold``, ``eccentricity_threshold``,
           ``keep_colors``, ``major_axis_threshold``, ``minor_axis_threshold``,
           ``perimeter_threshold``, and ``remove_colors`` optional arguments.
        """
        angle_threshold = kwargs.get('angle_threshold', None)
        area_threshold = kwargs.get('area_threshold', None)
        background_id = kwargs.get('background_id', None)
        circularity_threshold = kwargs.get('circularity_threshold', None)
        connectivity = kwargs.get('connectivity', 4)
        diameter_threshold = kwargs.get('diameter_threshold', None)
        eccentricity_threshold = kwargs.get('eccentricity_threshold', None)
        keep = kwargs.get('keep', None)
        keep_colors = kwargs.get('keep_colors', None)
        keep_top = kwargs.get('keep_top', None)
        major_axis_threshold = kwargs.get('major_axis_threshold', None)
        mean_color = kwargs.get('mean_color', False)
        minor_axis_threshold = kwargs.get('minor_axis_threshold', None)
        perimeter_threshold = kwargs.get('perimeter_threshold', None)
        remove = kwargs.get('remove', None)
        remove_colors = kwargs.get('remove_colors', None)
        if library.MagickConnectedComponentsImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        if connectivity not in (4, 8):
            raise ValueError('connectivity must be 4, or 8.')
        if angle_threshold is not None:
            key = b'connected-components:angle-threshold'
            val = to_bytes(angle_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if area_threshold is not None:
            key = b'connected-components:area-threshold'
            val = to_bytes(area_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if background_id is not None:
            key = b'connected-components:background-id'
            val = to_bytes(background_id)
            library.MagickSetImageArtifact(self.wand, key, val)
        if circularity_threshold is not None:
            key = b'connected-components:circularity-threshold'
            val = to_bytes(circularity_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if diameter_threshold is not None:
            key = b'connected-components:diameter-threshold'
            val = to_bytes(diameter_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if eccentricity_threshold is not None:
            key = b'connected-components:eccentricity-threshold'
            val = to_bytes(eccentricity_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if keep is not None:
            key = b'connected-components:keep'
            val = to_bytes(keep)
            library.MagickSetImageArtifact(self.wand, key, val)
        if keep_colors is not None:
            key = b'connected-components:keep-colors'
            val = to_bytes(keep_colors)
            library.MagickSetImageArtifact(self.wand, key, val)
        if keep_top is not None:
            key = b'connected-components:keep-top'
            val = to_bytes(keep_top)
            library.MagickSetImageArtifact(self.wand, key, val)
        if major_axis_threshold is not None:
            key = b'connected-components:major-axis-threshold'
            val = to_bytes(major_axis_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if mean_color:
            key = b'connected-components:mean-color'
            val = b'true'
            library.MagickSetImageArtifact(self.wand, key, b'true')
        if minor_axis_threshold is not None:
            key = b'connected-components:minor-axis-threshold'
            val = to_bytes(minor_axis_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if perimeter_threshold is not None:
            key = b'connected-components:perimeter-threshold'
            val = to_bytes(perimeter_threshold)
            library.MagickSetImageArtifact(self.wand, key, val)
        if remove is not None:
            key = b'connected-components:remove'
            val = to_bytes(remove)
            library.MagickSetImageArtifact(self.wand, key, val)
        if remove_colors is not None:
            key = b'connected-components:remove-colors'
            val = to_bytes(remove_colors)
            library.MagickSetImageArtifact(self.wand, key, val)
        objects_ptr = ctypes.c_void_p(0)
        CCObjectInfoStructure = CCObjectInfo
        if MAGICK_VERSION_NUMBER > 0x70B:
            CCObjectInfoStructure = CCObjectInfo710
        elif MAGICK_VERSION_NUMBER > 0x709:
            CCObjectInfoStructure = CCObjectInfo70A
        ccoi_mem_size = ctypes.sizeof(CCObjectInfoStructure)
        r = library.MagickConnectedComponentsImage(self.wand, connectivity,
                                                   ctypes.byref(objects_ptr))
        objects = []
        if r and objects_ptr.value:
            for i in range(self.colors):
                temp = CCObjectInfoStructure()
                src_addr = objects_ptr.value + (i * ccoi_mem_size)
                ctypes.memmove(ctypes.addressof(temp), src_addr, ccoi_mem_size)
                objects.append(ConnectedComponentObject(temp))
                del temp
            objects_ptr = libmagick.RelinquishMagickMemory(objects_ptr)
        else:
            self.raise_exception()
        return objects

    @manipulative
    @trap_exception
    def contrast(self, sharpen=True):
        """Enhances the difference between lighter & darker values of the
        image. Set ``sharpen`` to ``False`` to reduce contrast.

        :param sharpen: Increase, or decrease, contrast. Default is ``True``
                        for increased contrast.
        :type sharpen: :class:`bool`

        .. versionadded:: 0.5.7
        """
        assertions.assert_bool(sharpen=sharpen)
        return library.MagickContrastImage(self.wand, sharpen)

    @manipulative
    @trap_exception
    def contrast_stretch(self, black_point=0.0, white_point=None,
                         channel=None):
        """Enhance contrast of image by adjusting the span of the available
        colors.

        :param black_point: black point between 0.0 and 1.0.  default is 0.0
        :type black_point: :class:`numbers.Real`
        :param white_point: white point between 0.0 and 1.0.
                            Defaults to the same value given to the
                            ``black_point`` argument.
        :type white_point: :class:`numbers.Real`
        :param channel: optional color channel to apply contrast stretch
        :type channel: :const:`CHANNELS`
        :raises ValueError: if ``channel`` is not in :const:`CHANNELS`

        .. versionadded:: 0.4.1

        .. versionchanged:: 0.5.5
           The ``white_point`` argument will now default to the value given
           by the ``black_point`` argument.
        """
        assertions.assert_real(black_point=black_point)
        # If only black-point is given, match CLI behavior by
        # calculating white point
        if white_point is None:
            white_point = black_point
        assertions.assert_real(white_point=white_point)
        contrast_range = float(self.width * self.height)
        if 0.0 < black_point <= 1.0:
            black_point *= contrast_range
        if 0.0 < white_point <= 1.0:
            white_point *= contrast_range
        white_point = contrast_range - white_point
        if channel is None:
            r = library.MagickContrastStretchImage(self.wand,
                                                   black_point,
                                                   white_point)
        else:
            ch_const = self._channel_to_mask(channel)
            if library.MagickContrastStretchImageChannel:
                r = library.MagickContrastStretchImageChannel(self.wand,
                                                              ch_const,
                                                              black_point,
                                                              white_point)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_const)
                r = library.MagickContrastStretchImage(self.wand,
                                                       black_point,
                                                       white_point)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    def convex_hull(self, background=None):
        """Find the smallest convex polygon, and returns a list of points.

        .. note:: Requires ImageMagick-7.0.10 or greater.

        You can pass the list of points directly to
        :meth:`Drawing.polygon() <wand.drawing.Drawing.polygon>` method
        to draw the convex hull shape on the image.

        .. code::

            from wand.image import Image
            from wand.drawing import Drawing

            with Image(filename='kdf_black.png') as img:
              points = img.convex_hull()
              with Drawing() as ctx:
                ctx.fill_color = 'transparent'
                ctx.stroke_color = 'red'
                ctx.polygon(points=points)
                ctx(img)
            img.save(filename='kdf_black_convex_hull.png')

        .. image:: ../_images/wand/image/kdf_black.png
        .. image:: ../_images/wand/image/kdf_black_convex_hull.png

        :param background: Define which color value to evaluate as the
                           background.
        :type background: :class:`str` or :class:`~wand.color.Color`
        :returns: list of points
        :rtype: :class:`list` [ :class:`tuple` ( :class:`float`,
                :class:`float` ) ]

        .. versionadded:: 0.6.4
        """
        r = []
        if MAGICK_VERSION_NUMBER < 0x70A:
            msg = 'ImageMagick-7.0.10 is required to use convex_hull().'
            raise WandLibraryVersionError(msg)
        with self.clone() as tmp:
            if background is not None:
                if isinstance(background, Color):
                    background = background.string
                assertions.assert_string(background=background)
                key = b'convex-hull:background-color'
                val = to_bytes(background)
                library.MagickSetImageArtifact(tmp.wand, key, val)
            library.MagickSetOption(tmp.wand, b'format', b'%[convex-hull]')
            library.MagickSetImageFormat(tmp.wand, b'INFO')
            length = ctypes.c_size_t()
            blob_p = library.MagickGetImageBlob(tmp.wand,
                                                ctypes.byref(length))
            if blob_p:
                blob = ctypes.string_at(blob_p, length.value)
                blob_p = library.MagickRelinquishMemory(blob_p)
                pts = blob.decode('ascii', 'ignore').strip().split(' ')
                r = [tuple(map(lambda x: float(x), p.split(','))) for p in pts]
            else:
                self.raise_exception()
        return r

    @manipulative
    @trap_exception
    def crop(self, left=0, top=0, right=None, bottom=None,
             width=None, height=None, reset_coords=True,
             gravity=None):
        """Crops the image in-place.

        .. sourcecode:: text

           +--------------------------------------------------+
           |              ^                         ^         |
           |              |                         |         |
           |             top                        |         |
           |              |                         |         |
           |              v                         |         |
           | <-- left --> +-------------------+  bottom       |
           |              |             ^     |     |         |
           |              | <-- width --|---> |     |         |
           |              |           height  |     |         |
           |              |             |     |     |         |
           |              |             v     |     |         |
           |              +-------------------+     v         |
           | <--------------- right ---------->               |
           +--------------------------------------------------+

        :param left: x-offset of the cropped image. default is 0
        :type left: :class:`numbers.Integral`
        :param top: y-offset of the cropped image. default is 0
        :type top: :class:`numbers.Integral`
        :param right: second x-offset of the cropped image.
                      default is the :attr:`width` of the image.
                      this parameter and ``width`` parameter are exclusive
                      each other
        :type right: :class:`numbers.Integral`
        :param bottom: second y-offset of the cropped image.
                       default is the :attr:`height` of the image.
                       this parameter and ``height`` parameter are exclusive
                       each other
        :type bottom: :class:`numbers.Integral`
        :param width: the :attr:`width` of the cropped image.
                      default is the :attr:`width` of the image.
                      this parameter and ``right`` parameter are exclusive
                      each other
        :type width: :class:`numbers.Integral`
        :param height: the :attr:`height` of the cropped image.
                       default is the :attr:`height` of the image.
                       this parameter and ``bottom`` parameter are exclusive
                       each other
        :type height: :class:`numbers.Integral`
        :param reset_coords:
           optional flag. If set, after the rotation, the coordinate frame
           will be relocated to the upper-left corner of the new image.
           By default is `True`.
        :type reset_coords: :class:`bool`
        :param gravity: optional flag. If set, will calculate the :attr:`top`
                        and :attr:`left` attributes. This requires both
                        :attr:`width` and :attr:`height` parameters to be
                        included.
        :type gravity: :const:`GRAVITY_TYPES`
        :raises ValueError: when one or more arguments are invalid

        .. note::

           If you want to crop the image but not in-place, use slicing
           operator.

        .. versionadded:: 0.1.7

        .. versionchanged:: 0.1.8
           Made to raise :exc:`~exceptions.ValueError` instead of
           :exc:`~exceptions.IndexError` for invalid ``width``/``height``
           arguments.

        .. versionchanged:: 0.4.1
           Added ``gravity`` option. Using ``gravity`` along with
           ``width`` & ``height`` to auto-adjust ``left`` & ``top``
           attributes.

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        if not (right is None or width is None):
            raise TypeError('parameters right and width are exclusive each '
                            'other; use one at a time')
        elif not (bottom is None or height is None):
            raise TypeError('parameters bottom and height are exclusive each '
                            'other; use one at a time')

        def abs_(n, m, null=None):
            if n is None:
                return m if null is None else null
            elif not isinstance(n, numbers.Integral):
                raise TypeError('expected integer, not ' + repr(n))
            elif n > m:
                raise ValueError(repr(n) + ' > ' + repr(m))
            return m + n if n < 0 else n

        # Define left & top if gravity is given.
        ow, oh = self.size
        left = abs_(left, ow, 0)
        top = abs_(top, oh, 0)
        if width is None:
            right = abs_(right, ow)
            width = right - left
        if height is None:
            bottom = abs_(bottom, oh)
            height = bottom - top
        if gravity is not None:
            gtop, gleft = self._gravity_to_offset(gravity, width, height)
            top += gtop
            left += gleft
        assertions.assert_counting_number(width=width, height=height)
        if (
            left == top == 0 and
            width == ow and
            height == oh
        ):
            return True
        if self.animation:
            self.wand = library.MagickCoalesceImages(self.wand)
            self.reset_sequence()
            library.MagickSetLastIterator(self.wand)
            n = library.MagickGetIteratorIndex(self.wand)
            library.MagickResetIterator(self.wand)
            for i in range(0, n + 1):
                library.MagickSetIteratorIndex(self.wand, i)
                r = library.MagickCropImage(self.wand,
                                            width, height,
                                            left, top)
                if reset_coords:
                    self.reset_coords()
        else:
            r = library.MagickCropImage(self.wand, width, height, left, top)
            if reset_coords:
                self.reset_coords()
        return r

    @trap_exception
    def cycle_color_map(self, offset=1):
        """Shift the image color-map by a given offset.

        :param offset: number of steps to rotate index by.
        :type offset: :class:`numbers.Integral`

        .. versionadded:: 0.5.3
        """
        assertions.assert_integer(offset=offset)
        return library.MagickCycleColormapImage(self.wand, offset)

    @manipulative
    @trap_exception
    def decipher(self, passphrase):
        """Decrypt ciphered pixels into original values.

        .. note::

            :class:`~wand.exceptions.ImageError` will be thrown if the system's
            ImageMagick library was compiled without cipher support.

        :param passphrase: the secret passphrase to decrypt with.
        :type passphrase: :class:`str`

        .. versionadded:: 0.6.3
        """
        assertions.assert_string(passphrase=passphrase)
        return library.MagickDecipherImage(self.wand, binary(passphrase))

    @manipulative
    @trap_exception
    def deconstruct(self):
        """Iterates over internal image stack, and adjust each frame size to
        minimum bounding region of any changes from the previous frame.

        .. versionadded:: 0.5.0
        """
        r = library.MagickDeconstructImages(self.wand)
        if r:
            self.wand = r
            self.reset_sequence()
        return bool(r)

    @manipulative
    @trap_exception
    def deskew(self, threshold):
        """Attempts to remove skew artifacts common with most
        scanning & optical import devices.

        :params threshold: limit between foreground & background. Use a real
                           number between `0.0` & `1.0` to match CLI's percent
                           argument.
        :type threshold: :class:`numbers.Real`

        .. versionadded:: 0.5.0
        """
        assertions.assert_real(threshold=threshold)
        if 0 < threshold <= 1.0:
            threshold *= self.quantum_range
        return library.MagickDeskewImage(self.wand, threshold)

    @manipulative
    @trap_exception
    def despeckle(self):
        """Applies filter to reduce noise in image.

        :see: Example of :ref:`despeckle`.

        .. versionadded:: 0.5.0
        """
        return library.MagickDespeckleImage(self.wand)

    @manipulative
    @trap_exception
    def distort(self, method, arguments, best_fit=False, filter=None):
        """Distorts an image using various distorting methods.

        .. code:: python

            from wand.image import Image
            from wand.color import Color

            with Image(filename='checks.png') as img:
                img.virtual_pixel = 'background'
                img.background_color = Color('green')
                img.matte_color = Color('skyblue')
                arguments = (0, 0, 20, 60,
                             90, 0, 70, 63,
                             0, 90, 5, 83,
                             90, 90, 85, 88)
                img.distort('perspective', arguments)
                img.save(filename='checks_perspective.png')

        .. image:: ../_images/wand/image/checks.png
        .. image:: ../_images/wand/image/checks_perspective.png

        Use :attr:`virtual_pixel`, :attr:`background_color`, and
        :attr:`matte_color` properties to control the behavior of pixels
        rendered outside of the image boundaries.

        Use :attr:`interpolate_method` to control how images scale-up.

        Distortion viewport, and scale, can be defined by using
        :attr:`Image.artifacts` dictionary. For example::

            img.artifacts['distort:viewport'] = '44x44+15+0'
            img.artifacts['distort:scale'] = '10'

        :see: Additional examples of :ref:`distort`.

        :param method: Distortion method name from :const:`DISTORTION_METHODS`
        :type method: :class:`str`
        :param arguments: List of distorting float arguments
                          unique to distortion method
        :type arguments: :class:`collections.abc.Sequence`
        :param best_fit: Attempt to resize resulting image fit distortion.
                         Defaults False
        :type best_fit: :class:`bool`
        :param filter: Optional resampling filter used when calculating
                       pixel-value. Defaults to ``'mitchell'``, or
                       ``'lanczos'`` based on image type & operation.
        :type filter: :class:`str`

        .. versionadded:: 0.4.1
        .. versionchanged:: 0.6.11
           Included `filter=` parameter.
        """
        assertions.string_in_list(DISTORTION_METHODS,
                                  'wand.image.DISTORTION_METHODS',
                                  method=method)
        if not isinstance(arguments, abc.Sequence):
            raise TypeError('expected sequence of doubles, not ' +
                            repr(arguments))
        argc = len(arguments)
        argv = (ctypes.c_double * argc)(*arguments)
        method_idx = DISTORTION_METHODS.index(method)
        if filter is not None:
            assertions.string_in_list(FILTER_TYPES,
                                      'wand.image.FILTER_TYPES',
                                      filter=filter)
            ok = False
            if library.MagickSetImageFilter:
                filter_idx = FILTER_TYPES.index(filter)
                ok = library.MagickSetImageFilter(self.wand,
                                                  filter_idx)
            else:
                img_info_ptr = libmagick.AcquireImageInfo()
                exp_ptr = libmagick.AcquireExceptionInfo()
                img_ptr = library.GetImageFromMagickWand(self.wand)
                if all([img_info_ptr, exp_ptr, img_ptr]):
                    libmagick.SetImageOption(img_info_ptr,
                                             b'filter',
                                             filter.encode())
                    ok = libmagick.SyncImageSettings(img_info_ptr,
                                                     img_ptr,
                                                     exp_ptr)
                if img_info_ptr:
                    img_info_ptr = libmagick.DestroyImageInfo(img_info_ptr)
                if exp_ptr:
                    exp_ptr = libmagick.DestroyExceptionInfo(exp_ptr)
                if not ok:
                    raise AttributeError('Unable to set filter for ' +
                                         filter)
        return library.MagickDistortImage(self.wand, method_idx,
                                          argc, argv, bool(best_fit))

    @manipulative
    @trap_exception
    def edge(self, radius=0.0):
        """Applies convolution filter to detect edges.

        :see: Example of :ref:`edge`.

        :param radius: aperture of detection filter.
        :type radius: :class:`numbers.Real`

        .. versionadded:: 0.5.0
        """
        assertions.assert_real(radius=radius)
        return library.MagickEdgeImage(self.wand, radius)

    @manipulative
    @trap_exception
    def emboss(self, radius=0.0, sigma=0.0):
        """Applies convolution filter against Gaussians filter.

        .. note::

            The `radius` value should be larger than `sigma` for best results.

        :see: Example of :ref:`emboss`.

        :param radius: filter aperture size.
        :type radius: :class:`numbers.Real`
        :param sigma: standard deviation.
        :type sigma: :class:`numbers.Real`

        .. versionadded:: 0.5.0
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        return library.MagickEmbossImage(self.wand, radius, sigma)

    @manipulative
    @trap_exception
    def encipher(self, passphrase):
        """Encrypt plain pixels into ciphered values.

        .. note::

            :class:`~wand.exceptions.ImageError` will be thrown if the system's
            ImageMagick library was compiled without cipher support.

        :param passphrase: the secret passphrase to encrypt with.
        :type passphrase: :class:`str`

        .. versionadded:: 0.6.3
        .. versionchanged:: 0.6.8
           Fixed C-API call.
        """
        assertions.assert_string(passphrase=passphrase)
        return library.MagickEncipherImage(self.wand, binary(passphrase))

    @manipulative
    @trap_exception
    def enhance(self):
        """Applies digital filter to reduce noise.

        :see: Example of :ref:`enhance`.

        .. versionadded:: 0.5.0
        """
        return library.MagickEnhanceImage(self.wand)

    @manipulative
    @trap_exception
    def equalize(self, channel=None):
        """Equalizes the image histogram

        :param channel: Optional channel. See :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.3.10

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument.
        """
        if channel is None:
            r = library.MagickEqualizeImage(self.wand)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickEqualizeImageChannel(self.wand, channel_ch)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickEqualizeImage(self.wand)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def evaluate(self, operator=None, value=0.0, channel=None):
        """Apply arithmetic, relational, or logical expression to an image.

        Percent values must be calculated against the quantum range of the
        image::

            fifty_percent = img.quantum_range * 0.5
            img.evaluate(operator='set', value=fifty_percent)

        :see: Example of :ref:`evaluate`.

        :param operator: Type of operation to calculate
        :type operator: :const:`EVALUATE_OPS`
        :param value: Number to calculate with ``operator``
        :type value: :class:`numbers.Real`
        :param channel: Optional channel to apply operation on.
        :type channel: :const:`CHANNELS`
        :raises TypeError: When ``value`` is not numeric.
        :raises ValueError: When ``operator``, or ``channel`` are not defined
                            in constants.

        .. versionadded:: 0.4.1
        """
        assertions.string_in_list(EVALUATE_OPS, 'wand.image.EVALUATE_OPS',
                                  operator=operator)
        assertions.assert_real(value=value)
        idx_op = EVALUATE_OPS.index(operator)
        if channel is None:
            r = library.MagickEvaluateImage(self.wand, idx_op, value)
        else:
            ch_const = self._channel_to_mask(channel)
            # Use channel method if IM6, else create channel mask for IM7.
            if library.MagickEvaluateImageChannel:
                r = library.MagickEvaluateImageChannel(self.wand,
                                                       ch_const,
                                                       idx_op,
                                                       value)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_const)
                r = library.MagickEvaluateImage(self.wand, idx_op, value)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    def evaluate_images(self, operator=None):
        """Create a new image by applying arithmetic operation between all
        images loaded in the image-stack.

        For example::

           with Image() as img:
                img.read(filename='first.png')
                img.read(filename='second.png')
                with img.evaluate_images('add') as sum:
                    sum.save(filename='sum.png')

        :param operator: Type of operation to calculate.
        :type operator: :const:`EVALUATE_OPS`

        .. versionadded:: 0.7.0
        """
        assertions.string_in_list(EVALUATE_OPS, 'wand.image.EVALUATE_OPS',
                                  operator=operator)
        self.iterator_reset()
        idx_op = EVALUATE_OPS.index(operator)
        result = library.MagickEvaluateImages(self.wand, idx_op)
        if not result:
            self.raise_exception()
        return Image(image=BaseImage(result))

    def export_pixels(self, x=0, y=0, width=None, height=None,
                      channel_map="RGBA", storage='char'):
        """Export pixel data from a raster image to
        a list of values.

        The ``channel_map`` tells ImageMagick which color
        channels to export, and what order they should be
        written as -- per pixel. Valid entries for
        ``channel_map`` are:

        - ``'R'`` - Red channel
        - ``'G'`` - Green channel
        - ``'B'`` - Blue channel
        - ``'A'`` - Alpha channel (``0`` is transparent)
        - ``'O'`` - Alpha channel (``0`` is opaque)
        - ``'C'`` - Cyan channel
        - ``'Y'`` - Yellow channel
        - ``'M'`` - Magenta channel
        - ``'K'`` - Black channel
        - ``'I'`` - Intensity channel (only for grayscale)
        - ``'P'`` - Padding

        See :const:`STORAGE_TYPES` for a list of valid
        ``storage`` options. This tells ImageMagick
        what type of data it should calculate & write to.
        For example; a storage type of ``'char'`` will write
        a 8-bit value between 0 ~ 255,  a storage type
        of ``'short'`` will write a 16-bit value between
        0 ~ 65535, and a ``'integer'`` will write a
        32-bit value between 0 ~ 4294967295.

        .. note::

            By default, the entire image will be exported
            as ``'char'`` storage with each pixel mapping
            Red, Green, Blue, & Alpha channels.


        :param x: horizontal starting coordinate of raster.
        :type x: :class:`numbers.Integral`
        :param y: vertical starting coordinate of raster.
        :type y: :class:`numbers.Integral`
        :param width: horizontal length of raster.
        :type width: :class:`numbers.Integral`
        :param height: vertical length of raster.
        :type height: :class:`numbers.Integral`
        :param channel_map: a string listing the channel data
                            format for each pixel.
        :type channel_map: :class:`str`
        :param storage: what data type each value should
                        be calculated as.
        :type storage: :class:`str`
        :returns: list of values.
        :rtype: :class:`collections.abc.Sequence`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.6.11
           Update storage type size for `"long"` & `"quantum"` values.
        """
        _w, _h = self.size
        if width is None:
            width = _w
        if height is None:
            height = _h
        assertions.assert_integer(x=x, y=y, width=width, height=height)
        assertions.assert_string(channel_map=channel_map)
        assertions.string_in_list(STORAGE_TYPES, 'wand.image.STORAGE_TYPES',
                                  storage=storage)
        channel_map = channel_map.upper()
        valid_channels = 'RGBAOCYMKIP'
        for channel in channel_map:
            if channel not in valid_channels:
                raise ValueError('Unknown channel label: ' +
                                 repr(channel))
        c_storage_types = [
            None,                                # undefined
            ctypes.c_ubyte,                      # char
            ctypes.c_double,                     # double
            ctypes.c_float,                      # float
            ctypes.c_uint,                       # integer
            ctypes.c_uint64,                     # long
            library.PixelGetRedQuantum.restype,  # quantum
            ctypes.c_ushort                      # short
        ]
        s_index = STORAGE_TYPES.index(storage)
        c_storage = c_storage_types[s_index]
        total_pixels = width * height
        c_buffer_size = total_pixels * len(channel_map)
        c_buffer = (c_buffer_size * c_storage)()
        r = library.MagickExportImagePixels(self.wand,
                                            x, y, width, height,
                                            binary(channel_map),
                                            s_index,
                                            ctypes.byref(c_buffer))
        if not r:  # pragma: no cover
            self.raise_exception()
        return c_buffer[:c_buffer_size]

    @manipulative
    @trap_exception
    def extent(self, width=None, height=None, x=None, y=None, gravity=None):
        """Adjust the canvas size of the image. Use ``x`` & ``y`` to offset
        the image's relative placement in the canvas, or ``gravity`` helper
        for quick position placement.

        :param width: the target width of the extended image.
                      Default is the :attr:`width` of the image.
        :type width: :class:`numbers.Integral`
        :param height: the target height of the extended image.
                       Default is the :attr:`height` of the image.
        :type height: :class:`numbers.Integral`
        :param x: the x-axis offset of the extended image.
                      Default is 0, and can not be used with ``gravity``.
        :type x: :class:`numbers.Integral`
        :param y: the :attr:`y` offset of the extended image.
                       Default is 0, and can not be used with ``gravity``.
        :type y: :class:`numbers.Integral`
        :param gravity: position of the item extent when not using ``x`` &
                        ``y``. See :const:`GRAVITY_TYPES`.
        :type gravity: :class:`str`

        .. versionadded:: 0.4.5

        .. versionchanged:: 0.6.8
           Added ``gravity`` argument.

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        ow, oh = self.size
        if width is None or width == 0:
            width = ow
        if height is None or height == 0:
            height = oh
        assertions.assert_unsigned_integer(width=width, height=height)
        if x is None:
            x = 0
        if y is None:
            y = 0
        assertions.assert_integer(x=x, y=y)
        if gravity is not None:
            gy, gx = self._gravity_to_offset(gravity, width, height)
            x += gx
            y += gy
        return library.MagickExtentImage(self.wand, width, height, x, y)

    def features(self, distance):
        """Calculate directional image features for each color channel.
        Feature metrics including:

        - angular second moment
        - contrast
        - correlation
        - variance sum of squares
        - inverse difference moment
        - sum average
        - sum variance
        - sum entropy
        - entropy
        - difference variance
        - difference entropy
        - information measures of correlation 1
        - information measures of correlation 2
        - maximum correlation coefficient

        With each metric containing horizontal, vertical, left & right
        diagonal values.

        .. code::

            from wand.image import Image

            with Image(filename='rose:') as img:
                channel_features = img.features(distance=32)
                for channels, features in channel_features.items():
                    print(channels)
                    for feature, directions in features.items():
                        print('  ', feature)
                        for name, value in directions.items():
                            print('    ', name, value)

        :param distance: Define the distance if pixels to calculate.
        :type distance: :class:`numbers.Integral`
        :returns: a dict mapping each color channel with a dict of each
                  feature.
        :rtype: :class:`dict`

        .. versionadded:: 0.5.5
        """
        def build_channel(address, channel):
            feature = ChannelFeature()
            size = ctypes.sizeof(feature)
            ctypes.memmove(ctypes.addressof(feature),
                           feature_ptr + (CHANNELS[channel] * size),
                           size)
            keys = ('horizontal', 'vertical',
                    'left_diagonal', 'right_diagonal')
            feature_dict = {}
            for k in feature._fields_:
                a = k[0]
                feature_dict[a] = dict(zip(keys, getattr(feature, a)))
            return feature_dict
        if MAGICK_VERSION_NUMBER < 0x700:
            method = library.MagickGetImageChannelFeatures
        else:  # pragma: no cover
            method = library.MagickGetImageFeatures
        assertions.assert_unsigned_integer(distance=distance)
        feature_ptr = method(self.wand, distance)
        response = {}
        if feature_ptr:
            colorspace = self.colorspace
            if self.alpha_channel:
                response['alpha'] = build_channel(feature_ptr, 'alpha')
            if colorspace == 'gray':
                response['gray'] = build_channel(feature_ptr, 'gray')
            elif colorspace == 'cmyk':
                response['cyan'] = build_channel(feature_ptr, 'cyan')
                response['magenta'] = build_channel(feature_ptr, 'magenta')
                response['yellow'] = build_channel(feature_ptr, 'yellow')
                response['black'] = build_channel(feature_ptr, 'black')
            else:
                response['red'] = build_channel(feature_ptr, 'red')
                response['green'] = build_channel(feature_ptr, 'green')
                response['blue'] = build_channel(feature_ptr, 'blue')
            feature_ptr = library.MagickRelinquishMemory(feature_ptr)
        return response

    def fft(self, magnitude=True):
        """Alias for :meth:`forward_fourier_transform`.

        .. versionadded:: 0.5.7
        """
        return self.forward_fourier_transform(magnitude)

    @manipulative
    @trap_exception
    def flip(self):
        """Creates a vertical mirror image by reflecting the pixels around
        the central x-axis.  It manipulates the image in place.

        :see: Example of :ref:`flip_flop`.

        .. versionadded:: 0.3.0

        """
        return library.MagickFlipImage(self.wand)

    @manipulative
    @trap_exception
    def flop(self):
        """Creates a horizontal mirror image by reflecting the pixels around
        the central y-axis.  It manipulates the image in place.

        :see: Example of :ref:`flip_flop`.

        .. versionadded:: 0.3.0

        """
        return library.MagickFlopImage(self.wand)

    @manipulative
    @trap_exception
    def floodfill(self, fill='none', fuzz=0.1, bordercolor=None,
                  x=0, y=0, invert=False, channel='default_channels'):
        """Changes pixel value to ``fill`` color at (``x``, ``y``) coordinate,
        and repeats for all neighboring pixels until border is reached. Use
        ``fuzz`` and/or ``bordercolor`` to defined tolarance & border values.

        :param fill: New target color value.
        :type fill: :class:`wand.color.Color` or :class:`str`
        :param fuzz: Fill threashold. Values between `0.0` and `1.0`. Default
                     `0.1` (10%).
        :type fuzz: :class:`numbers.Real`
        :param bordercolor: Optional color to use as a border. Default
                            :const:`None`.
        :type bordercolor: :class:`wand.color.Color` or :class:`str`
        :param x: Starting point X coordinate. Default ``0``.
        :type x: :class:`number.Integral`
        :param y: Starting point Y coordinate. Default ``0``.
        :type y: :class:`number.Integral`
        :param invert: Only paint none-matching pixels. Default ``False``.
        :type invert: :class:`bool`
        :param channel: Optional channel mask. Default ``'default_channels'``.
        :type channel: :const:`CHANNELS`

        .. versionadded:: 0.7.0
        """
        assertions.assert_real(fuzz=fuzz)
        assertions.assert_integer(x=x, y=y)
        assertions.assert_bool(invert=invert)
        ch_mask = self._channel_to_mask(channel)
        fuzz = fuzz * self.quantum_range
        ok = False
        if not isinstance(fill, Color):
            fill = Color(fill)
        if bordercolor is not None and not isinstance(bordercolor, Color):
            bordercolor = Color(bordercolor)
        if MAGICK_VERSION_NUMBER < 0x700:
            with fill:
                if bordercolor is None:
                    ok = library.MagickFloodfillPaintImage(
                        self.wand,
                        ch_mask,
                        fill.resource,
                        fuzz,
                        None,
                        x,
                        y,
                        invert
                    )
                else:
                    with bordercolor:
                        ok = library.MagickFloodfillPaintImage(
                            self.wand,
                            ch_mask,
                            fill.resource,
                            fuzz,
                            bordercolor.resource,
                            x,
                            y,
                            invert
                        )
        else:
            mask = library.MagickSetImageChannelMask(self.wand, ch_mask)
            with fill:
                if bordercolor is None:
                    ok = library.MagickFloodfillPaintImage(
                        self.wand,
                        fill.resource,
                        fuzz,
                        None,
                        x,
                        y,
                        invert
                    )
                else:
                    with bordercolor:
                        ok = library.MagickFloodfillPaintImage(
                            self.wand,
                            fill.resource,
                            fuzz,
                            bordercolor.resource,
                            x,
                            y,
                            invert
                        )
            library.MagickSetImageChannelMask(self.wand, mask)
        return ok

    @trap_exception
    def forward_fourier_transform(self, magnitude=True):
        """Performs a discrete Fourier transform. The image stack is replaced
        with the results. Either a pair of magnitude & phase images, or
        real & imaginary (HDRI).

        .. code::

            from wand.image import Image
            from wand.version import QUANTUM_RANGE

            with Image(filename='source.png') as img:
                img.forward_fourier_transform()
                img.depth = QUANTUM_RANGE
                img.save(filename='fft_%02d.png')

        .. seealso:: :meth:`inverse_fourier_transform` & :meth:`complex`

        .. note::

            ImageMagick must have HDRI support to compute real & imaginary
            components (i.e. ``magnitude=False``).

        :param magnitude: If ``True``, generate magnitude & phase, else
                          real & imaginary. Default ``True``
        :type magnitude: :class:`bool`

        .. versionadded:: 0.5.5
        """
        assertions.assert_bool(magnitude=magnitude)
        return library.MagickForwardFourierTransformImage(self.wand, magnitude)

    @manipulative
    @trap_exception
    def frame(self, matte=None, width=1, height=1, inner_bevel=0,
              outer_bevel=0, compose='over'):
        """Creates a bordered frame around image.
        Inner & outer bevel can simulate a 3D effect.

        :param matte: color of the frame
        :type matte: :class:`wand.color.Color`
        :param width: total size of frame on x-axis
        :type width: :class:`numbers.Integral`
        :param height: total size of frame on y-axis
        :type height: :class:`numbers.Integral`
        :param inner_bevel: inset shadow length
        :type inner_bevel: :class:`numbers.Real`
        :param outer_bevel: outset highlight length
        :type outer_bevel: :class:`numbers.Real`
        :param compose: Optional composite operator. Default ``'over'``, and
                        only available with ImageMagick-7.
        :type compose: :class:`str`

        .. versionadded:: 0.4.1

        .. versionchanged:: 0.5.6
           Added optional ``compose`` parameter.
        """
        if matte is None:
            matte = Color('gray')
        if isinstance(matte, str):
            matte = Color(matte)
        assertions.assert_color(matte=matte)
        assertions.assert_integer(width=width, height=height)
        assertions.assert_real(inner_bevel=inner_bevel,
                               outer_bevel=outer_bevel)
        with matte:
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickFrameImage(self.wand,
                                             matte.resource,
                                             width, height,
                                             inner_bevel, outer_bevel)
            else:  # pragma: no cover
                assertions.string_in_list(COMPOSITE_OPERATORS,
                                          'wand.image.COMPOSITE_OPERATORS',
                                          compose=compose)
                op = COMPOSITE_OPERATORS.index(compose)
                r = library.MagickFrameImage(self.wand,
                                             matte.resource,
                                             width, height,
                                             inner_bevel, outer_bevel,
                                             op)
        return r

    @manipulative
    @trap_exception
    def function(self, function, arguments, channel=None):
        """Apply an arithmetic, relational, or logical expression to an image.

        Defaults entire image, but can isolate affects to single color channel
        by passing :const:`CHANNELS` value to ``channel`` parameter.

        .. note::

           Support for function methods added in the following versions
           of ImageMagick.

           - ``'polynomial'`` >= 6.4.8-8
           - ``'sinusoid'`` >= 6.4.8-8
           - ``'arcsin'`` >= 6.5.3-1
           - ``'arctan'`` >= 6.5.3-1

        :see: Example of :ref:`function`.

        :param function: a string listed in :const:`FUNCTION_TYPES`
        :type function: :class:`str`
        :param arguments: a sequence of doubles to apply against ``function``
        :type arguments: :class:`collections.abc.Sequence`
        :param channel: optional :const:`CHANNELS`, defaults all
        :type channel: :class:`str`
        :raises ValueError: when a ``function``, or ``channel`` is not
                            defined in there respected constant
        :raises TypeError: if ``arguments`` is not a sequence

        .. versionadded:: 0.4.1
        """
        assertions.string_in_list(FUNCTION_TYPES, 'wand.image.FUNCTION_TYPES',
                                  function=function)
        if not isinstance(arguments, abc.Sequence):
            raise TypeError('expecting sequence of arguments, not ' +
                            repr(arguments))
        argc = len(arguments)
        argv = (ctypes.c_double * argc)(*arguments)
        index = FUNCTION_TYPES.index(function)
        if channel is None:
            r = library.MagickFunctionImage(self.wand, index, argc, argv)
        else:
            ch_channel = self._channel_to_mask(channel)
            # Use channel method if IM6, else create channel mask for IM7.
            if library.MagickFunctionImageChannel:
                r = library.MagickFunctionImageChannel(self.wand,
                                                       ch_channel,
                                                       index,
                                                       argc,
                                                       argv)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_channel)
                r = library.MagickFunctionImage(self.wand, index, argc, argv)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    def fx(self, expression, channel=None):
        """Manipulate each pixel of an image by given expression.

        FX will preserver current wand instance, and return a new instance of
        :class:`Image` containing affected pixels.

        Defaults entire image, but can isolate affects to single color channel
        by passing :const:`CHANNELS` value to ``channel`` parameter.

        .. seealso:: The anatomy of FX expressions can be found at
                     http://www.imagemagick.org/script/fx.php


        :see: Example of :ref:`fx`.

        :param expression: The entire FX expression to apply
        :type expression: :class:`str`
        :param channel: Optional channel to target.
        :type channel: :const:`CHANNELS`
        :returns: A new instance of an image with expression applied
        :rtype: :class:`Image`

        .. versionadded:: 0.4.1

        .. versionchanged:: 0.6.9
           Will raise :class:`WandRuntimeError` if method is unable to generate
           a new image & doesn't throw an exception.
        """
        assertions.assert_string(expression=expression)
        c_expression = binary(expression)
        if channel is None:
            new_wand = library.MagickFxImage(self.wand, c_expression)
        else:
            ch_channel = self._channel_to_mask(channel)
            if library.MagickFxImageChannel:
                new_wand = library.MagickFxImageChannel(self.wand,
                                                        ch_channel,
                                                        c_expression)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_channel)
                new_wand = library.MagickFxImage(self.wand, c_expression)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        if new_wand:
            return Image(image=BaseImage(new_wand))
        else:  # pragma: no cover
            self.raise_exception()
            # If no exception is on the stack, then raise a generic run-time
            # error. This can happen naturally if the source image is null,
            # or the FX expression was unable to generate a new raster.
            raise WandRuntimeError('No FX Image was generated by expression.')

    @manipulative
    @trap_exception
    def gamma(self, adjustment_value=1.0, channel=None):
        """Gamma correct image.

        Specific color channels can be correct individual. Typical values
        range between 0.8 and 2.3.

        :see: Example of :ref:`gamma`.

        :param adjustment_value: value to adjust gamma level. Default `1.0`
        :type adjustment_value: :class:`numbers.Real`
        :param channel: optional channel to apply gamma correction
        :type channel: :class:`str`
        :raises TypeError: if ``gamma_point`` is not a :class:`numbers.Real`
        :raises ValueError: if ``channel`` is not in :const:`CHANNELS`

        .. versionadded:: 0.4.1

        """
        assertions.assert_real(adjustment_value=adjustment_value)
        if channel is None:
            r = library.MagickGammaImage(self.wand, adjustment_value)
        else:
            ch_const = self._channel_to_mask(channel)
            if library.MagickGammaImageChannel:
                r = library.MagickGammaImageChannel(self.wand,
                                                    ch_const,
                                                    adjustment_value)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_const)
                r = library.MagickGammaImage(self.wand, adjustment_value)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def gaussian_blur(self, radius=0.0, sigma=0.0, channel=None):
        """Blurs the image.  We convolve the image with a gaussian operator
        of the given ``radius`` and standard deviation (``sigma``).
        For reasonable results, the ``radius`` should be larger
        than ``sigma``.  Use a ``radius`` of 0 and :meth:`blur()` selects
        a suitable ``radius`` for you.

        :see: Example of :ref:`gaussian_blur`.

        :param radius: the radius of the, in pixels,
                       not counting the center pixel
        :type radius: :class:`numbers.Real`
        :param sigma: the standard deviation of the, in pixels
        :type sigma: :class:`numbers.Real`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.3.3

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        .. versionchanged:: 0.5.7
           Positional arguments ``radius`` & ``sigma`` have been converted
           to keyword arguments.
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        if channel is None:
            r = library.MagickGaussianBlurImage(self.wand, radius, sigma)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickGaussianBlurImageChannel(self.wand,
                                                           channel_ch,
                                                           radius,
                                                           sigma)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickGaussianBlurImage(self.wand, radius, sigma)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    def get_image_distortion(self, image, metric='undefined'):
        """Compares two images, and return the specified distortion metric.

        This method is faster than :meth:`compare()` method as ImageMagick
        will not need to reconstruct an image.

        :param image: Image to reference.
        :type image: :class:`wand.image.BaseImage`
        :param metric: Compare disortion metric to use. See
                       :const:`COMPARE_METRICS`.
        :type metric: :class:`str`
        :returns: Computed value of the distortion metric used.
        :rtype: :class:`numbers.Real`

        .. versionadded:: 0.6.6
        """
        if not isinstance(image, BaseImage):
            raise TypeError('expecting a base image, not ' + repr(image))
        assertions.string_in_list(COMPARE_METRICS,
                                  'wand.image.COMPARE_METRICS',
                                  metric=metric)
        metric_idx = COMPARE_METRICS.index(metric)
        dist = ctypes.c_double(0.0)
        ok = library.MagickGetImageDistortion(self.wand, image.wand,
                                              metric_idx, dist)
        if not ok:
            self.raise_exception()
        return dist.value

    @manipulative
    @trap_exception
    def hald_clut(self, image, channel=None):
        """Replace color values by referencing a Higher And Lower Dimension
        (HALD) Color Look Up Table (CLUT). You can generate a HALD image
        by using ImageMagick's `hald:` protocol. ::

            with Image(filename='rose:') as img:
                with Image(filename='hald:3') as hald:
                    hald.gamma(1.367)
                    img.hald_clut(hald)

        :param image: The HALD color matrix.
        :type image: :class:`wand.image.BaseImage`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        if not isinstance(image, BaseImage):
            raise TypeError('expecting a base image, not ' + repr(image))
        if channel is None:
            r = library.MagickHaldClutImage(self.wand, image.wand)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickHaldClutImageChannel(self.wand, channel_ch,
                                                       image.wand)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickHaldClutImage(self.wand, image.wand)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def hough_lines(self, width, height=None, threshold=40):
        """Identify lines within an image. Use :meth:`canny` to reduce image
        to a binary edge before calling this method.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param width: Local maxima of neighboring pixels.
        :type width: :class:`numbers.Integral`
        :param height: Local maxima of neighboring pixels.
        :type height: :class:`numbers.Integral`
        :param threshold: Line count to limit. Default to 40.
        :type threshold: :class:`numbers.Integral`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickHoughLineImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        if height is None:
            height = width
        assertions.assert_unsigned_integer(width=width, height=height,
                                           threshold=threshold)
        return library.MagickHoughLineImage(self.wand, width, height,
                                            threshold)

    def ift(self, phase, magnitude=True):
        """Alias for :meth:`inverse_fourier_transform`.

        .. versionadded:: 0.5.7
        """
        return self.inverse_fourier_transform(phase, magnitude)

    @trap_exception
    def implode(self, amount=0.0, method="undefined"):
        """Creates a "imploding" effect by pulling pixels towards the center
        of the image.

        :see: Example of :ref:`implode`.

        :param amount: Normalized degree of effect between `0.0` & `1.0`.
        :type amount: :class:`numbers.Real`
        :param method: Which interpolate method to apply to effected pixels.
                       See :const:`PIXEL_INTERPOLATE_METHODS` for a list of
                       options. Only available with ImageMagick-7.
        :type method: :class:`str`

        .. versionadded:: 0.5.2
        """
        assertions.assert_real(amount=amount)
        assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                  'wand.image.PIXEL_INTERPOLATE_METHODS',
                                  method=method)
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickImplodeImage(self.wand, amount)
        else:  # pragma: no cover
            method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
            r = library.MagickImplodeImage(self.wand, amount, method_idx)
        return r

    @trap_exception
    def import_pixels(self, x=0, y=0, width=None, height=None,
                      channel_map='RGB', storage='char', data=None):
        """Import pixel data from a byte-string to
        the image. The instance of :class:`Image` must already
        be allocated with the correct size.

        The ``channel_map`` tells ImageMagick which color
        channels to export, and what order they should be
        written as -- per pixel. Valid entries for
        ``channel_map`` are:

        - ``'R'`` - Red channel
        - ``'G'`` - Green channel
        - ``'B'`` - Blue channel
        - ``'A'`` - Alpha channel (``0`` is transparent)
        - ``'O'`` - Alpha channel (``0`` is opaque)
        - ``'C'`` - Cyan channel
        - ``'Y'`` - Yellow channel
        - ``'M'`` - Magenta channel
        - ``'K'`` - Black channel
        - ``'I'`` - Intensity channel (only for grayscale)
        - ``'P'`` - Padding

        See :const:`STORAGE_TYPES` for a list of valid
        ``storage`` options. This tells ImageMagick
        what type of data it should calculate & write to.
        For example; a storage type of ``'char'`` will write
        a 8-bit value between 0 ~ 255,  a storage type
        of ``'short'`` will write a 16-bit value between
        0 ~ 65535, and a ``'integer'`` will write a
        32-bit value between 0 ~ 4294967295.

        .. note::

            By default, the entire image will be exported
            as ``'char'`` storage with each pixel mapping
            Red, Green, Blue, & Alpha channels.


        :param x: horizontal starting coordinate of raster.
        :type x: :class:`numbers.Integral`
        :param y: vertical starting coordinate of raster.
        :type y: :class:`numbers.Integral`
        :param width: horizontal length of raster.
        :type width: :class:`numbers.Integral`
        :param height: vertical length of raster.
        :type height: :class:`numbers.Integral`
        :param channel_map: a string listing the channel data
                            format for each pixel.
        :type channel_map: :class:`str`
        :param storage: what data type each value should
                        be calculated as.
        :type storage: :class:`str`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.6.11
           Update storage type size for `"long"` & `"quantum"` values.
        """
        _w, _h = self.size
        if width is None:
            width = _w
        if height is None:
            height = _h
        assertions.assert_integer(x=x, y=y, width=width, height=height)
        assertions.string_in_list(STORAGE_TYPES, 'wand.image.STORAGE_TYPES',
                                  storage=storage)
        assertions.assert_string(channel_map=channel_map)
        channel_map = channel_map.upper()
        valid_channels = 'RGBAOCYMKIP'
        for channel in channel_map:
            if channel not in valid_channels:
                raise ValueError('Unknown channel label: ' +
                                 repr(channel))
        if not isinstance(data, abc.Sequence):
            raise TypeError('data must list of values, not' +
                            repr(data))
        # Ensure enough data was given.
        expected_len = width * height * len(channel_map)
        given_len = len(data)
        if expected_len != given_len:
            msg = 'data length should be {0}, not {1}.'.format(
                expected_len,
                given_len
            )
            raise ValueError(msg)
        c_storage_types = [
            None,                                # undefined
            ctypes.c_ubyte,                      # char
            ctypes.c_double,                     # double
            ctypes.c_float,                      # float
            ctypes.c_uint,                       # integer
            ctypes.c_uint64,                     # long
            library.PixelGetRedQuantum.restype,  # quantum
            ctypes.c_ushort                      # short
        ]
        s_index = STORAGE_TYPES.index(storage)
        c_type = c_storage_types[s_index]
        c_buffer = (len(data) * c_type)(*data)
        r = library.MagickImportImagePixels(self.wand,
                                            x, y, width, height,
                                            binary(channel_map),
                                            s_index,
                                            ctypes.byref(c_buffer))
        return r

    @trap_exception
    def inverse_fourier_transform(self, phase, magnitude=True):
        """Applies the inverse of a discrete Fourier transform. The image stack
        is replaced with the results. Either a pair of magnitude & phase
        images, or real & imaginary (HDRI).

        .. code::

            from wand.image import Image

            with Image(filename='magnitude.png') as img:
                with Image(filename='phase.png') as phase:
                    img.inverse_fourier_transform(phase)
                img.save(filename='output.png')

        .. seealso:: :meth:`forward_fourier_transform` & :meth:`complex`

        .. note::

            ImageMagick must have HDRI support to compute real & imaginary
            components (i.e. ``magnitude=False``).

        :param phase: Second part (image) of the transform. Either the phase,
                      or the imaginary part.
        :type phase: :class:`BaseImage`
        :param magnitude: If ``True``, accept magnitude & phase input, else
                          real & imaginary. Default ``True``
        :type magnitude: :class:`bool`

        .. versionadded:: 0.5.5
        """
        if not isinstance(phase, BaseImage):
            raise TypeError('phase must be an image, not ' + repr(phase))
        assertions.assert_bool(magnitude=magnitude)
        return library.MagickInverseFourierTransformImage(self.wand,
                                                          phase.wand,
                                                          magnitude)

    def iterator_first(self):
        """Sets the internal image-stack iterator to the first image.
        Useful for prepending an image at the start of the stack.

        .. versionadded:: 0.6.2
        """
        library.MagickSetFirstIterator(self.wand)

    def iterator_get(self):
        """Returns the position of the internal image-stack index.

        :rtype: :class:`int`

        .. versionadded:: 0.6.2
        """
        return library.MagickGetIteratorIndex(self.wand)

    def iterator_last(self):
        """Sets the internal image-stack iterator to the last image.
        Useful for appending an image to the end of the stack.

        .. versionadded:: 0.6.2
        """
        library.MagickSetLastIterator(self.wand)

    def iterator_length(self):
        """Get the count of images in the image-stack.

        :rtype: :class:`int`

        .. versionadded:: 0.6.2
        """
        return library.MagickGetNumberImages(self.wand)

    def iterator_next(self):
        """Steps the image-stack index forward by one

        :rtype: :class:`bool`

        .. versionadded:: 0.6.2
        """
        has_next = library.MagickHasNextImage(self.wand)
        if has_next:
            idx = library.MagickGetIteratorIndex(self.wand)
            has_next = library.MagickSetIteratorIndex(self.wand, idx + 1)
        return has_next

    def iterator_previous(self):
        """Steps the image-stack index back by one.

        :rtype: :class:`bool`

        .. versionadded:: 0.6.2
        """
        has_prev = library.MagickHasPreviousImage(self.wand)
        if has_prev:
            idx = library.MagickGetIteratorIndex(self.wand)
            has_prev = library.MagickSetIteratorIndex(self.wand, idx - 1)
        return has_prev

    def iterator_reset(self):
        """Reset internal image-stack iterator. Useful for iterating over the
        image-stack without allocating :class:`~wand.sequence.Sequence`.

        .. versionadded:: 0.6.2
        """
        library.MagickResetIterator(self.wand)

    def iterator_set(self, index):
        """Sets the index of the internal image-stack.

        :rtype: :class:`bool`

        .. versionadded:: 0.6.2
        """
        assertions.assert_integer(index=index)
        return library.MagickSetIteratorIndex(self.wand, index)

    @manipulative
    @trap_exception
    def kmeans(self, number_colors=None, max_iterations=100, tolerance=0.01):
        """Reduces the number of colors in an image by applying the K-means
        clustering algorithm.

        .. note::

            Requires ImageMagick-7.0.10-37, or later.

        :param number_colors: the target number of colors to use as seeds.
        :type number_colors: :class:`numbers.Integral`
        :param max_iterations: maximum number of iterations needed until
                               convergence. Default ``100``.
        :type max_iterations: :class:`numbers.Integral`
        :param tolerance: maximum tolerance between distrotion iterations.
                          Default ``0.01``
        :type tolerance: :class:`numbers.Real`

        .. versionadded:: 0.6.4
        """
        if MAGICK_VERSION_NUMBER < 0x70A or library.MagickKmeansImage is None:
            msg = "Kmeans requires ImageMagick-7.0.10-37 or later."
            raise WandLibraryVersionError(msg)
        assertions.assert_unsigned_integer(number_colors=number_colors,
                                           max_iterations=max_iterations)
        assertions.assert_real(tolerance=tolerance)
        return library.MagickKmeansImage(self.wand, number_colors,
                                         max_iterations, tolerance)

    def kurtosis_channel(self, channel='default_channels'):
        """Calculates the kurtosis and skewness of the image.

        .. code:: python

            from wand.image import Image

            with Image(filename='input.jpg') as img:
                kurtosis, skewness = img.kurtosis_channel()

        :param channel: Select which color channel to evaluate. See
                        :const:`CHANNELS`. Default ``'default_channels'``.
        :type channel: :class:`str`
        :returns: Tuple of :attr:`kurtosis` & :attr:`skewness`
                  values.
        :rtype: :class:`tuple`

        .. versionadded:: 0.5.3
        """
        ch_channel = self._channel_to_mask(channel)
        k = ctypes.c_double(0.0)
        s = ctypes.c_double(0.0)
        if MAGICK_VERSION_NUMBER < 0x700:
            library.MagickGetImageChannelKurtosis(self.wand, ch_channel,
                                                  ctypes.byref(k),
                                                  ctypes.byref(s))
        else:  # pragma: no cover
            # Set active channel, and capture mask to restore.
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             ch_channel)
            library.MagickGetImageKurtosis(self.wand,
                                           ctypes.byref(k),
                                           ctypes.byref(s))
            # Restore original state of channels
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return k.value, s.value

    @manipulative
    @trap_exception
    def kuwahara(self, radius=1.0, sigma=None):
        """Edge preserving noise reduction filter.

        https://en.wikipedia.org/wiki/Kuwahara_filter

        If ``sigma`` is not given, the value will be calculated as:

            sigma = radius - 0.5

        To match original algorithm's behavior, increase ``radius`` value by
        one:

            myImage.kuwahara(myRadius + 1, mySigma)

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :see: Example of :ref:`kuwahara`.

        :param radius: Size of the filter aperture.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of Gaussian filter.
        :type sigma: :class:`numbers.Real`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickKuwaharaImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        if sigma is None:
            sigma = radius - 0.5
        assertions.assert_real(radius=radius, sigma=sigma)
        return library.MagickKuwaharaImage(self.wand, radius, sigma)

    @manipulative
    def label(self, text, left=None, top=None, font=None, gravity=None,
              background_color='transparent'):
        """Writes a label ``text`` into the position on top of the existing
        canvas. This method doesn't autofit text like :meth:`caption`. Use
        ``left`` & ``top``, or ``gravity``, to position the text.

        :param text: text to write.
        :type text: :class:`str`
        :param left: x offset in pixels.
        :type left: :class:`numbers.Integral`
        :param top: y offset in pixels.
        :type top: :class:`numbers.Integral`
        :param font: font to use.  default is :attr:`font` of the image.
        :type font: :class:`wand.font.Font`
        :param gravity: text placement gravity.
        :type gravity: :class:`str`

        .. versionadded:: 0.6.8
        """
        if font is not None and not isinstance(font, Font):
            raise TypeError('font must be a wand.font.Font, not ' + repr(font))
        if gravity is not None:
            assertions.string_in_list(GRAVITY_TYPES,
                                      'wand.image.GRAVITY_TYPES',
                                      gravity=gravity)
        if font is None:
            try:
                font = self.font
                if font is None:
                    raise TypeError()
            except TypeError:
                raise TypeError('font must be specified or existing in image')
        with Image() as textboard:
            textboard.font = font
            textboard.background_color = background_color
            textboard.read(filename=b'label:' + text.encode('utf-8'))
            self.composite(textboard, left=left, top=top, gravity=gravity)

    @trap_exception
    def level(self, black=0.0, white=None, gamma=1.0, channel=None):
        """Adjusts the levels of an image by scaling the colors falling
        between specified black and white points to the full available
        quantum range.

        If only ``black`` is given, ``white`` will be adjusted inward.

        :see: Example of :ref:`level`.

        :param black: Black point, as a percentage of the system's quantum
                      range. Defaults to 0.
        :type black: :class:`numbers.Real`
        :param white: White point, as a percentage of the system's quantum
                      range. Defaults to 1.0.
        :type white: :class:`numbers.Real`
        :param gamma: Optional gamma adjustment. Values > 1.0 lighten the
                      image's midtones while values < 1.0 darken them.
        :type gamma: :class:`numbers.Real`
        :param channel: The channel type. Available values can be found
                        in the :const:`CHANNELS` mapping. If ``None``,
                        normalize all channels.
        :type channel: :const:`CHANNELS`

        .. note::
            Images may not be affected if the ``white`` value is equal to or
            less than the ``black`` value.

        .. versionadded:: 0.4.1

        """
        assertions.assert_real(black=black)
        # If white is not given, mimic CLI behavior by reducing top point
        if white is None:
            white = 1.0 - black
        assertions.assert_real(white=white, gamma=gamma)

        bp = float(self.quantum_range * black)
        wp = float(self.quantum_range * white)
        if MAGICK_HDRI:  # pragma: no cover
            bp -= 0.5  # TODO: Document why HDRI requires 0.5 adjustments.
            wp -= 0.5
        if channel is None:
            r = library.MagickLevelImage(self.wand, bp, gamma, wp)
        else:
            ch_const = self._channel_to_mask(channel)
            if library.MagickLevelImageChannel:
                r = library.MagickLevelImageChannel(self.wand,
                                                    ch_const,
                                                    bp,
                                                    gamma,
                                                    wp)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_const)
                r = library.MagickLevelImage(self.wand, bp, gamma, wp)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def level_colors(self, black_color, white_color, channel=None):
        """Maps given colors to "black" & "white" values.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-54, or
            greater.

        :param black_color: linearly map given color as "black" point.
        :type black_color: :class:`Color`
        :param white_color: linearly map given color as "white" point.
        :type white_color: :class:`Color`
        :param channel: target a specific color-channel to levelize.
        :type channel: :class:`str`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.6
        """
        if library.MagickLevelImageColors is None:
            msg = 'Method requires ImageMagick version 7.0.8-54 or greater.'
            raise WandLibraryVersionError(msg)
        if isinstance(black_color, str):
            black_color = Color(black_color)
        if isinstance(white_color, str):
            white_color = Color(white_color)
        assertions.assert_color(black_color=black_color,
                                white_color=white_color)
        channel_mask = None
        if channel is not None:
            ch_const = self._channel_to_mask(channel)
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             ch_const)
        with black_color:
            with white_color:
                r = library.MagickLevelImageColors(self.wand,
                                                   black_color.resource,
                                                   white_color.resource,
                                                   False)
        if channel is not None:
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def levelize(self, black=0.0, white=None, gamma=1.0, channel=None):
        """Reverse of :meth:`level()`, this method compresses the range of
        colors between ``black`` & ``white`` values.

        If only ``black`` is given, ``white`` will be adjusted inward.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param black: Black point, as a percentage of the system's quantum
                      range. Defaults to 0.
        :type black: :class:`numbers.Real`
        :param white: White point, as a percentage of the system's quantum
                      range. Defaults to 1.0.
        :type white: :class:`numbers.Real`
        :param gamma: Optional gamma adjustment. Values > 1.0 lighten the
                      image's midtones while values < 1.0 darken them.
        :type gamma: :class:`numbers.Real`
        :param channel: The channel type. Available values can be found
                        in the :const:`CHANNELS` mapping. If ``None``,
                        normalize all channels.
        :type channel: :const:`CHANNELS`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickLevelizeImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        if white is None:
            white = float(self.quantum_range)
        assertions.assert_real(black=black, white=white, gamma=gamma)
        if 0 < black <= 1.0:
            black *= self.quantum_range
        if 0 < white <= 1.0:
            white *= self.quantum_range
        if channel is None:
            r = library.MagickLevelizeImage(self.wand, black, gamma, white)
        else:
            ch_const = self._channel_to_mask(channel)
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             ch_const)
            r = library.MagickLevelizeImage(self.wand, black, gamma, white)
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def levelize_colors(self, black_color, white_color, channel=None):
        """Reverse of :meth:`level_colors()`, and creates a de-contrasting
        gradient of given colors. This works best with grayscale images.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-54, or
            greater.

        :param black_color: tint map given color as "black" point.
        :type black_color: :class:`Color`
        :param white_color: tint map given color as "white" point.
        :type white_color: :class:`Color`
        :param channel: target a specific color-channel to levelize.
        :type channel: :class:`str`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.6
        """
        if library.MagickLevelImageColors is None:
            msg = 'Method requires ImageMagick version 7.0.8-54 or greater.'
            raise WandLibraryVersionError(msg)
        if isinstance(black_color, str):
            black_color = Color(black_color)
        if isinstance(white_color, str):
            white_color = Color(white_color)
        assertions.assert_color(black_color=black_color,
                                white_color=white_color)
        channel_mask = None
        ch_const = None
        if channel is not None:
            ch_const = self._channel_to_mask(channel)
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             ch_const)
        with black_color:
            with white_color:
                r = library.MagickLevelImageColors(self.wand,
                                                   black_color.resource,
                                                   white_color.resource,
                                                   True)
        if channel is not None:
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def linear_stretch(self, black_point=0.0, white_point=1.0):
        """Enhance saturation intensity of an image.

        :param black_point: Black point between 0.0 and 1.0. Default 0.0
        :type black_point: :class:`numbers.Real`
        :param white_point: White point between 0.0 and 1.0. Default 1.0
        :type white_point: :class:`numbers.Real`

        .. versionadded:: 0.4.1
        """
        assertions.assert_real(black_point=black_point,
                               white_point=white_point)
        linear_range = float(self.width * self.height)
        return library.MagickLinearStretchImage(self.wand,
                                                linear_range * black_point,
                                                linear_range * white_point)

    @manipulative
    def liquid_rescale(self, width, height, delta_x=1, rigidity=0):
        """Rescales the image with `seam carving`_, also known as
        image retargeting, content-aware resizing, or liquid rescaling.

        :param width: the width in the scaled image
        :type width: :class:`numbers.Integral`
        :param height: the height in the scaled image
        :type height: :class:`numbers.Integral`
        :param delta_x: maximum seam transversal step.
                        0 means straight seams.  default is 0
        :type delta_x: :class:`numbers.Real`
        :param rigidity: introduce a bias for non-straight seams.
                         default is 1
        :type rigidity: :class:`numbers.Real`
        :raises wand.exceptions.MissingDelegateError:
           when ImageMagick isn't configured ``--with-lqr`` option.

        .. note::

           This feature requires ImageMagick to be configured
           ``--with-lqr`` option.  Or it will raise
           :exc:`~wand.exceptions.MissingDelegateError`:

        .. seealso::

           `Seam carving`_ --- Wikipedia
              The article which explains what seam carving is
              on Wikipedia.

        .. _Seam carving: http://en.wikipedia.org/wiki/Seam_carving


        .. versionchanged:: 0.7.0
           Updated default value of ``delta_x`` to ``1`` to match CLI.
        """
        assertions.assert_integer(width=width, height=height)
        assertions.assert_real(delta_x=delta_x, rigidity=rigidity)
        library.MagickLiquidRescaleImage(self.wand, width, height,
                                         delta_x, rigidity)
        try:
            self.raise_exception()
        except MissingDelegateError as e:  # pragma: no cover
            raise MissingDelegateError(
                str(e) + '\n\nImageMagick in the system is likely to be '
                'impossible to load liblqr.  You might not install liblqr, '
                'or ImageMagick may not compiled with liblqr.'
            )

    @manipulative
    @trap_exception
    def local_contrast(self, radius=10, strength=12.5):
        """Increase light-dark transitions within image.

        .. warning::

            This class method is only available with ImageMagick 6.9.3, or
            greater.

        :param radius: The size of the Gaussian operator. Default value is
                       ``10.0``.
        :type radius: :class:`numbers.Real`
        :param strength: Percentage of blur mask to apply. Values can be
                         between ``0.0`` and ``100`` with a default of
                         ``12.5``.
        :type strength: :class:`numbers.Real`

        .. versionadded:: 0.5.7
        """
        if library.MagickLocalContrastImage is None:  # pragma: no cover
            msg = 'Method requires ImageMagick version 6.9.3 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.assert_real(radius=radius, strength=strength)
        return library.MagickLocalContrastImage(self.wand, radius, strength)

    @manipulative
    @trap_exception
    def magnify(self):
        """Quickly double an image in size. This is a convenience method.
        Use :meth:`resize()`, :meth:`resample()`, or :meth:`sample()` for
        more control.

        .. versionadded:: 0.5.5
        """
        return library.MagickMagnifyImage(self.wand)

    def mean_channel(self, channel='default_channels'):
        """Calculates the mean and standard deviation of the image.

        .. code:: python

            from wand.image import Image

            with Image(filename='input.jpg') as img:
                mean, stddev = img.mean_channel()

        :param channel: Select which color channel to evaluate. See
                        :const:`CHANNELS`. Default ``'default_channels'``.
        :type channel: :class:`str`
        :returns: Tuple of :attr:`mean` & :attr:`standard_deviation`
                  values. The ``mean`` value will be between 0.0 &
                  :attr:`quantum_range`
        :rtype: :class:`tuple`

        .. versionadded:: 0.5.3
        """
        ch_channel = self._channel_to_mask(channel)
        m = ctypes.c_double(0.0)
        s = ctypes.c_double(0.0)
        if MAGICK_VERSION_NUMBER < 0x700:
            library.MagickGetImageChannelMean(self.wand, ch_channel,
                                              ctypes.byref(m),
                                              ctypes.byref(s))
        else:  # pragma: no cover
            # Set active channel, and capture mask to restore.
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             ch_channel)
            library.MagickGetImageMean(self.wand,
                                       ctypes.byref(m),
                                       ctypes.byref(s))
            # Restore original state of channels
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return m.value, s.value

    @manipulative
    @trap_exception
    def mean_shift(self, width, height, color_distance=0.1):
        """Recalculates pixel value by comparing neighboring pixels within a
        color distance, and replacing with a mean value. Works best with
        Gray, YCbCr, YIQ, or YUV colorspaces.

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param width: Size of the neighborhood window in pixels.
        :type width: :class:`numbers.Integral`
        :param height: Size of the neighborhood window in pixels.
        :type height: :class:`numbers.Integral`
        :param color_distance: Include pixel values within this color distance.
        :type color_distance: :class:`numbers.Real`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickMeanShiftImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.assert_counting_number(width=width, height=height)
        assertions.assert_real(color_distance=color_distance)
        if 0 < color_distance <= 1.0:
            color_distance *= self.quantum_range
        return library.MagickMeanShiftImage(self.wand, width, height,
                                            color_distance)

    @manipulative
    @trap_exception
    def merge_layers(self, method):
        """Composes all the image layers from the current given image onward
        to produce a single image of the merged layers.

        The initial canvas's size depends on the given ImageLayerMethod, and is
        initialized using the first images background color.  The images
        are then composited onto that image in sequence using the given
        composition that has been assigned to each individual image.
        The method must be set with a value from :const:`IMAGE_LAYER_METHOD`
        that is acceptable to this operation. (See ImageMagick documentation
        for more details.)

        :param method: the method of selecting the size of the initial canvas.
        :type method: :class:`str`

        .. versionadded:: 0.4.3

        """
        assertions.assert_string(method=method)
        if method not in ('merge', 'flatten', 'mosaic', 'trimbounds'):
            raise ValueError('method can only be \'merge\', \'flatten\', '
                             '\'mosaic\', or \'trimbounds\'')
        m = IMAGE_LAYER_METHOD.index(method)
        r = library.MagickMergeImageLayers(self.wand, m)
        if r:
            self.wand = r
            self.reset_sequence()
        return bool(r)

    def minimum_bounding_box(self, orientation=None):
        """Find the minimum bounding box within the image. Use
        properties :attr:`fuzz` & :attr:`background_color` to influence
        bounding box thresholds.

        .. code::

            from wand.image import Image
            from wand.drawing import Drawing

            with Image(filename='kdf_black.png') as img:
                img.fuzz = img.quantum_range * 0.1
                img.background_color = 'black'
                mbr = img.minimum_bounding_box()
                with Drawing() as ctx:
                    ctx.fill_color = 'transparent'
                    ctx.stroke_color = 'red'
                    ctx.polygon(points=mbr['points'])
                    ctx.fill_color = 'red'
                    ctx.stroke_color = 'transparent'
                    ctx.text(1, 10, '{0:.4g}\u00B0'.format(mbr['angle']))
                    ctx(img)
                img.save(filename='kdf_black_mbr.png')

        .. image:: ../_images/wand/image/kdf_black.png
        .. image:: ../_images/wand/image/kdf_black_mbr.png

        .. note::

            Requires ImageMagick-7.0.10 or later.

        :param orientation: sets the image orientation. Values can be
                            ``'landscape'``, or ``'portrait'``.
        :type orientation: :class:`str`
        :returns: a directory of MBR properties & corner points.
        :rtype: :class:`dict` { "points": :class:`list` [ :class:`tuple` (
                :class:`float`, :class:`float` ) ], "area": :class:`float`,
                "width": :class:`float`, "height": :class:`float`,
                "angle": :class:`float`, "unrotate": :class:`float` }

        .. versionadded:: 0.6.4
        """
        r = {}
        if MAGICK_VERSION_NUMBER < 0x70A:
            msg = 'ImageMagick-7.0.10 is required to use convex_hull().'
            raise WandLibraryVersionError(msg)
        with self.clone() as tmp:
            if orientation is not None:
                if orientation not in ('landscape', 'portrait'):
                    msg = 'orientation can only be landscape, or portrait, not'
                    msg += ' ' + repr(orientation)
                    raise ValueError(msg)
                key = b'minimum-bounding-box:orientation'
                val = to_bytes(orientation)
                library.MagickSetImageArtifact(tmp.wand, key, val)
            mbr_str = b'%[minimum-bounding-box]'
            mbr_str += b'|%[minimum-bounding-box:area]'
            mbr_str += b'|%[minimum-bounding-box:width]'
            mbr_str += b'|%[minimum-bounding-box:height]'
            mbr_str += b'|%[minimum-bounding-box:angle]'
            mbr_str += b'|%[minimum-bounding-box:unrotate]'
            library.MagickSetOption(tmp.wand, b'format', mbr_str)
            library.MagickSetImageFormat(tmp.wand, b'INFO')
            length = ctypes.c_size_t()
            blob_p = library.MagickGetImageBlob(tmp.wand,
                                                ctypes.byref(length))
            if blob_p:
                blob = ctypes.string_at(blob_p, length.value)
                blob_p = library.MagickRelinquishMemory(blob_p)
                parts = blob.decode('ascii', 'ignore').split('|')
                pts = parts[0].strip().split(' ')
                r = [tuple(map(lambda x: float(x), p.split(','))) for p in pts]
                attr = list(map(lambda x: float(x.strip()), parts[1:]))
                keys = ['area', 'width', 'height', 'angle', 'unrotate']
                r = dict(zip(keys, attr), points=r)
            else:
                self.raise_exception()
        return r

    @manipulative
    def mode(self, width, height=None):
        """Replace each pixel with the mathematical mode of the neighboring
        colors. This is an alias of the :meth:`statistic` method.

        :param width: Number of neighboring pixels to include in mode.
        :type width: :class:`numbers.Integral`
        :param height: Optional height of neighboring pixels, defaults to the
                       same value as ``width``.
        :type height: :class:`numbers.Integral`

        .. versionadded:: 0.5.4
        """
        if height is None:
            height = width
        assertions.assert_unsigned_integer(width=width, height=height)
        self.statistic('mode', width, height)

    @manipulative
    @trap_exception
    def modulate(self, brightness=100.0, saturation=100.0, hue=100.0):
        """Changes the brightness, saturation and hue of an image.
        We modulate the image with the given ``brightness``, ``saturation``
        and ``hue``.

        :param brightness: percentage of brightness
        :type brightness: :class:`numbers.Real`
        :param saturation: percentage of saturation
        :type saturation: :class:`numbers.Real`
        :param hue: percentage of hue rotation
        :type hue: :class:`numbers.Real`
        :raises ValueError: when one or more arguments are invalid

        .. versionadded:: 0.3.4

        """
        assertions.assert_real(brightness=brightness, saturation=saturation,
                               hue=hue)
        return library.MagickModulateImage(
            self.wand,
            brightness,
            saturation,
            hue
        )

    @manipulative
    @trap_exception
    def morphology(self, method=None, kernel=None, iterations=1, channel=None):
        """Manipulate pixels based on the shape of neighboring pixels.

        The ``method`` determines what type of effect to apply to matching
        ``kernel`` shapes. Common methods can be add/remove,
        or lighten/darken pixel values.

        The ``kernel`` describes the shape of the matching neighbors. Common
        shapes are provided as "built-in" kernels. See
        :const`KERNEL_INFO_TYPES` for examples. The format for built-in kernels
        is:

        .. sourcecode:: text

            label:geometry

        Where `label` is the kernel name defined in :const:`KERNEL_INFO_TYPES`,
        and `:geometry` is an optional geometry size. For example::

            with Image(filename='rose:') as img:
                img.morphology(method='dilate', kernel='octagon:3x3')
                # or simply
                img.morphology(method='edgein', kernel='octagon')

        Custom kernels can be applied by following a similar format:

        .. sourcecode:: text

            geometry:args

        Where `geometry` is the size of the custom kernel, and `args`
        list a comma separated list of values. For example::

            custom_kernel='5x3:nan,1,1,1,nan 1,1,1,1,1 nan,1,1,1,nan'
            with Image(filename='rose:') as img:
                img.morphology(method='dilate', kernel=custom_kernel)

        :param method: effect function to apply. See
                       :const:`MORPHOLOGY_METHODS` for a list of
                       methods.
        :type method: :class:`str`
        :param kernel: shape to evaluate surrounding pixels. See
                       :const:`KERNEL_INFO_TYPES` for a list of
                       built-in shapes.
        :type kernel: :class:`str`
        :param iterations: Number of times a morphology method should be
                           applied to the image. Default ``1``. Use ``-1`` for
                           unlimited iterations until the image is unchanged
                           by the method operator.
        :type iterations: :class:`numbers.Integral`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: `str`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        assertions.assert_string(method=method, kernel=kernel)
        assertions.assert_integer(iterations=iterations)
        builtin = None
        geometry = ''
        parts = kernel.split(':')
        if parts[0] in KERNEL_INFO_TYPES:
            builtin = parts[0]
            if len(parts) == 2:
                geometry = parts[1]
        exception_info = libmagick.AcquireExceptionInfo()
        if builtin:
            kernel_idx = KERNEL_INFO_TYPES.index(builtin)
            geometry_info = GeometryInfo()
            flags = libmagick.ParseGeometry(binary(geometry),
                                            ctypes.byref(geometry_info))
            if builtin in ('unity',):
                if (flags & geometry_info.RhoValue) == 0:
                    geometry_info.rho = 1.0
            elif builtin in ('square', 'diamond', 'octagon', 'disk',
                             'plus', 'cross'):
                if (flags & geometry_info.SigmaValue) == 0:
                    geometry_info.sigma = 1.0
            elif builtin in ('ring',):
                if (flags & geometry_info.XiValue) == 0:
                    geometry_info.xi = 1.0
            elif builtin in ('rectangle',):
                if (flags & geometry_info.RhoValue) == 0:
                    geometry_info.rho = geometry_info.sigma
                if geometry_info.rho < 1.0:
                    geometry_info.rho = 3.0
                if geometry_info.sigma < 1.0:
                    geometry_info.sigma = geometry_info.rho
                if (flags & geometry_info.XiValue) == 0:
                    geometry_info.xi = (geometry_info.rho - 1.0) / 2.0
                if (flags & geometry_info.PsiValue) == 0:
                    geometry_info.psi = (geometry_info.sigma - 1.0) / 2.0
            elif builtin in ('chebyshev', 'manhattan', 'octagonal',
                             'euclidean'):
                if (flags & geometry_info.SigmaValue) == 0:
                    geometry_info.sigma = 100.0
                elif (flags & geometry_info.AspectValue) != 0:
                    geometry_info.sigma = (float(self.quantum_range) /
                                           (geometry_info.sigma + 1.0))
                elif (flags & geometry_info.PercentValue) != 0:
                    geometry_info.sigma *= float(self.quantum_range) / 100.0
            if MAGICK_VERSION_NUMBER < 0x700:
                kernel_info = libmagick.AcquireKernelBuiltIn(
                    kernel_idx,
                    ctypes.byref(geometry_info)
                )
            else:  # pragma: no cover
                kernel_info = libmagick.AcquireKernelBuiltIn(
                    kernel_idx,
                    ctypes.byref(geometry_info),
                    exception_info
                )
        elif kernel:
            if MAGICK_VERSION_NUMBER < 0x700:
                kernel_info = libmagick.AcquireKernelInfo(
                    binary(kernel)
                )
            else:  # pragma: no cover
                kernel_info = libmagick.AcquireKernelInfo(
                    binary(kernel),
                    exception_info
                )
        r = None
        exception_info = libmagick.DestroyExceptionInfo(exception_info)
        if kernel_info:
            method_idx = MORPHOLOGY_METHODS.index(method)
            if channel is None:
                r = library.MagickMorphologyImage(self.wand, method_idx,
                                                  iterations, kernel_info)
            else:
                channel_ch = self._channel_to_mask(channel)
                if MAGICK_VERSION_NUMBER < 0x700:
                    r = library.MagickMorphologyImageChannel(self.wand,
                                                             channel_ch,
                                                             method_idx,
                                                             iterations,
                                                             kernel_info)
                else:  # pragma: no cover
                    mask = library.MagickSetImageChannelMask(self.wand,
                                                             channel_ch)
                    r = library.MagickMorphologyImage(self.wand, method_idx,
                                                      iterations, kernel_info)
                    library.MagickSetImageChannelMask(self.wand, mask)
            kernel_info = libmagick.DestroyKernelInfo(kernel_info)
        else:
            raise ValueError('Unable to parse kernel info for ' +
                             repr(kernel))
        return r

    @manipulative
    @trap_exception
    def motion_blur(self, radius=0.0, sigma=0.0, angle=0.0, channel=None):
        """Apply a Gaussian blur along an ``angle`` direction. This
        simulates motion movement.

        :see: Example of :ref:`motion_blur`.

        :param radius: Aperture size of the Gaussian operator.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of the Gaussian operator.
        :type sigma: :class:`numbers.Real`
        :param angle: Apply the effect along this angle.
        :type angle: :class:`numbers.Real`

        .. versionadded:: 0.5.4
        """
        assertions.assert_real(radius=radius, sigma=sigma, angle=angle)
        if channel is None:
            r = library.MagickMotionBlurImage(self.wand, radius, sigma, angle)
        else:
            ch_const = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickMotionBlurImageChannel(self.wand,
                                                         ch_const,
                                                         radius,
                                                         sigma,
                                                         angle)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_const)
                r = library.MagickMotionBlurImage(self.wand, radius, sigma,
                                                  angle)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def negate(self, grayscale=False, channel=None):
        """Negate the colors in the reference image.

        :param grayscale: if set, only negate grayscale pixels in the image.
        :type grayscale: :class:`bool`
        :param channel: the channel type.  available values can be found
                        in the :const:`CHANNELS` mapping.  If ``None``,
                        negate all channels.
        :type channel: :class:`str`

        .. versionadded:: 0.3.8

        """
        if channel is None:
            r = library.MagickNegateImage(self.wand, grayscale)
        else:
            ch_const = self._channel_to_mask(channel)
            if library.MagickNegateImageChannel:
                r = library.MagickNegateImageChannel(self.wand, ch_const,
                                                     grayscale)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_const)
                r = library.MagickNegateImage(self.wand, grayscale)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def noise(self, noise_type='uniform', attenuate=1.0, channel=None):
        """Adds noise to image.

        :see: Example of :ref:`noise`.

        :param noise_type: type of noise to apply. See :const:`NOISE_TYPES`.
        :type noise_type: :class:`str`
        :param attenuate: rate of distribution. Only available in
                          ImageMagick-7. Default is ``1.0``.
        :type attenuate: :class:`numbers.Real`
        :param channel: Optionally target a color channel to apply noise to.
                        See :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument.
        """
        assertions.string_in_list(NOISE_TYPES, 'wand.image.NOISE_TYPES',
                                  noise_type=noise_type)
        assertions.assert_real(attenuate=attenuate)
        noise_type_idx = NOISE_TYPES.index(noise_type)
        if MAGICK_VERSION_NUMBER < 0x700:
            if channel is None:
                r = library.MagickAddNoiseImage(self.wand, noise_type_idx)
            else:
                channel_ch = self._channel_to_mask(channel)
                r = library.MagickAddNoiseImageChannel(self.wand,
                                                       channel_ch,
                                                       noise_type_idx)
        else:  # pragma: no cover
            if channel is None:
                r = library.MagickAddNoiseImage(self.wand, noise_type_idx,
                                                attenuate)
            else:
                channel_ch = self._channel_to_mask(channel)
                mask = library.MagickSetImageChannelMask(self.wand,
                                                         channel_ch)
                r = library.MagickAddNoiseImage(self.wand, noise_type_idx,
                                                attenuate)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def normalize(self, channel=None):
        """Normalize color channels.

        :param channel: the channel type.  available values can be found
                        in the :const:`CHANNELS` mapping.  If ``None``,
                        normalize all channels.
        :type channel: :class:`str`

        """
        if channel is None:
            r = library.MagickNormalizeImage(self.wand)
        else:
            ch_const = self._channel_to_mask(channel)
            if library.MagickNormalizeImageChannel:
                r = library.MagickNormalizeImageChannel(self.wand, ch_const)
            else:  # pragma: no cover
                with Image(image=self) as mask:
                    # Set active channel, and capture mask to restore.
                    channel_mask = library.MagickSetImageChannelMask(mask.wand,
                                                                     ch_const)
                    r = library.MagickNormalizeImage(mask.wand)
                    # Restore original state of channels.
                    library.MagickSetImageChannelMask(mask.wand,
                                                      channel_mask)
                    # Copy adjusted mask over original value.
                    copy_mask = COMPOSITE_OPERATORS.index('copy_' + channel)
                    library.MagickCompositeImage(self.wand,
                                                 mask.wand,
                                                 copy_mask,
                                                 False,
                                                 0,
                                                 0)
        return r

    @manipulative
    @trap_exception
    def oil_paint(self, radius=0.0, sigma=0.0):
        """Simulates an oil painting by replace each pixel with most frequent
        surrounding color.

        :param radius: The size of the surrounding neighbors.
        :type radius: :class:`numbers.Real`
        :param sigma: The standard deviation used by the Gaussian operator.
                      This is only available with ImageMagick-7.
        :type sigma: :class:`numbers.Real`

        .. versionadded:: 0.5.4
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickOilPaintImage(self.wand, radius)
        else:  # pragma: no cover
            r = library.MagickOilPaintImage(self.wand, radius, sigma)
        return r

    @manipulative
    @trap_exception
    def opaque_paint(self, target=None, fill=None, fuzz=0.0, invert=False,
                     channel=None):
        """Replace any color that matches ``target`` with ``fill``. Use
        ``fuzz`` to control the threshold of the target match.
        The ``invert`` will replace all colors *but* the  pixels matching
        the ``target`` color.

        :param target: The color to match.
        :type target: :class:`wand.color.Color`
        :param fill: The color to paint with.
        :type fill: :class:`wand.color.Color`
        :param fuzz: Normalized real number between `0.0` and
                     :attr:`quantum_range`. Default is `0.0`.
        :type fuzz: class:`numbers.Real`
        :param invert: Replace all colors that do not match target.
                       Default is ``False``.
        :type invert: :class:`bool`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.5.4

        .. versionchanged:: 0.5.5
           Added ``channel`` parameter.
        """
        if isinstance(target, str):
            target = Color(target)
        if isinstance(fill, str):
            fill = Color(fill)
        assertions.assert_color(target=target, fill=fill)
        assertions.assert_real(fuzz=fuzz)
        assertions.assert_bool(invert=invert)
        with target:
            with fill:
                if channel is None:
                    r = library.MagickOpaquePaintImage(self.wand,
                                                       target.resource,
                                                       fill.resource,
                                                       fuzz,
                                                       invert)
                else:
                    channel_ch = self._channel_to_mask(channel)
                    if MAGICK_VERSION_NUMBER < 0x700:
                        r = library.MagickOpaquePaintImageChannel(
                            self.wand, channel_ch, target.resource,
                            fill.resource, fuzz, invert
                        )
                    else:  # pragma: no cover
                        mask = library.MagickSetImageChannelMask(self.wand,
                                                                 channel_ch)
                        r = library.MagickOpaquePaintImage(self.wand,
                                                           target.resource,
                                                           fill.resource,
                                                           fuzz,
                                                           invert)
                        library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def optimize_layers(self):
        """Attempts to crop each frame to the smallest image without altering
        the animation. For best results, call
        :meth:`Image.coalesce() <wand.image.BaseImage.coalesce>` before
        manipulating any frames. For timing accuracy, any
        :attr:`SingleImage.delay <wand.sequence.SingleImage.delay>` overwrites
        must be applied after optimizing layers.

        .. note::

            This will only affect ``GIF`` image formats.

        .. versionadded:: 0.5.0
        """
        r = library.MagickOptimizeImageLayers(self.wand)
        if r:
            self.wand = r
            self.reset_sequence()
        return bool(r)

    @manipulative
    @trap_exception
    def optimize_transparency(self):
        """Iterates over frames, and sets transparent values for each
        pixel unchanged by previous frame.

        .. note::

            This will only affect ``GIF`` image formats.

        .. versionadded:: 0.5.0
        """
        if library.MagickOptimizeImageTransparency:
            return library.MagickOptimizeImageTransparency(self.wand)
        else:  # pragma: no cover
            raise AttributeError('`MagickOptimizeImageTransparency\' not '
                                 'available on current version of MagickWand '
                                 'library.')

    @manipulative
    @trap_exception
    def ordered_dither(self, threshold_map='threshold', channel=None):
        """Executes a ordered-based dither operations based on predetermined
        threshold maps.

        +-----------+-------+-----------------------------+
        | Map       | Alias | Description                 |
        +===========+=======+=============================+
        | threshold | 1x1   | Threshold 1x1 (non-dither)  |
        +-----------+-------+-----------------------------+
        | checks    | 2x1   | Checkerboard 2x1 (dither)   |
        +-----------+-------+-----------------------------+
        | o2x2      | 2x2   | Ordered 2x2 (dispersed)     |
        +-----------+-------+-----------------------------+
        | o3x3      | 3x3   | Ordered 3x3 (dispersed)     |
        +-----------+-------+-----------------------------+
        | o4x4      | 4x4   | Ordered 4x4 (dispersed)     |
        +-----------+-------+-----------------------------+
        | o8x8      | 8x8   | Ordered 8x8 (dispersed)     |
        +-----------+-------+-----------------------------+
        | h4x4a     | 4x1   | Halftone 4x4 (angled)       |
        +-----------+-------+-----------------------------+
        | h6x6a     | 6x1   | Halftone 6x6 (angled)       |
        +-----------+-------+-----------------------------+
        | h8x8a     | 8x1   | Halftone 8x8 (angled)       |
        +-----------+-------+-----------------------------+
        | h4x4o     |       | Halftone 4x4 (orthogonal)   |
        +-----------+-------+-----------------------------+
        | h6x6o     |       | Halftone 6x6 (orthogonal)   |
        +-----------+-------+-----------------------------+
        | h8x8o     |       | Halftone 8x8 (orthogonal)   |
        +-----------+-------+-----------------------------+
        | h16x16o   |       | Halftone 16x16 (orthogonal) |
        +-----------+-------+-----------------------------+
        | c5x5b     | c5x5  | Circles 5x5 (black)         |
        +-----------+-------+-----------------------------+
        | c5x5w     |       | Circles 5x5 (white)         |
        +-----------+-------+-----------------------------+
        | c6x6b     | c6x6  | Circles 6x6 (black)         |
        +-----------+-------+-----------------------------+
        | c6x6w     |       | Circles 6x6 (white)         |
        +-----------+-------+-----------------------------+
        | c7x7b     | c7x7  | Circles 7x7 (black)         |
        +-----------+-------+-----------------------------+
        | c7x7w     |       | Circles 7x7 (white)         |
        +-----------+-------+-----------------------------+

        :param threshold_map: Name of threshold dither to use, followed by
                              optional arguments.
        :type threshold_map: :class:`str`
        :param channel: Optional argument to apply dither to specific color
                        channel. See :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.7
        """
        assertions.assert_string(threshold_map=threshold_map)
        bmap = binary(threshold_map)
        if MAGICK_VERSION_NUMBER <= 0x700:
            if channel is None:
                r = library.MagickOrderedPosterizeImage(self.wand, bmap)
            else:
                channel_ch = self._channel_to_mask(channel)
                r = library.MagickOrderedPosterizeImageChannel(self.wand,
                                                               channel_ch,
                                                               bmap)
        else:  # pragma: no cover
            if channel is None:
                r = library.MagickOrderedDitherImage(self.wand, bmap)
            else:
                channel_ch = self._channel_to_mask(channel)
                mask = library.MagickSetImageChannelMask(self.wand,
                                                         channel_ch)
                r = library.MagickOrderedDitherImage(self.wand, bmap)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    def parse_meta_geometry(self, geometry):
        """Helper method to translate geometry format, and calculate
        meta-characters against image dimensions.

        See "Image Geometry" definitions & examples for more info:
        https://imagemagick.org/script/command-line-processing.php#geometry

        :param geometry: user string following ImageMagick's geometry format.
        :type geometry: :class:`str`
        :returns: Calculated width, height, offset-x, & offset-y.
        :rtype: :class:`tuple`
        :raises ValueError: If given geometry can not be parsed.

        .. versionadded:: 0.5.6
        """
        assertions.assert_string(geometry=geometry)
        x = ctypes.c_ssize_t(0)
        y = ctypes.c_ssize_t(0)
        width = ctypes.c_size_t(self.width)
        height = ctypes.c_size_t(self.height)
        r = libmagick.ParseMetaGeometry(binary(geometry),
                                        ctypes.byref(x),
                                        ctypes.byref(y),
                                        ctypes.byref(width),
                                        ctypes.byref(height))
        if not bool(r):
            raise ValueError('Unable to parse geometry')
        return (width.value, height.value, x.value, y.value)

    def percent_escape(self, string_format):
        """Convenience method that expands ImageMagick's `Percent Escape`_
        characters into image attribute values.

        .. _Percent Escape: https://imagemagick.org/script/escape.php

        .. code::

            with wand.image import Image

            with Image(filename='tests/assets/sasha.jpg') as img:
                print(img.percent_escape('%f %wx%h'))
                #=> sasha.jpg 204x247

        .. note::

            Not all percent escaped values can be populated as I/O operations
            are managed by Python, and not the CLI utility.

        :param string_format: The precent escaped string to be translated.
        :type string_format: :class:`str`
        :returns: String of expanded values.
        :rtype: :class:`str`

        .. versionadded:: 0.5.6
        """
        local_overwrites = {
            '%m': self.format,
            '%[magick]': self.format
        }
        for k, v in local_overwrites.items():
            string_format = string_format.replace(k, v)
        self.options['format'] = string_format
        return text(self.make_blob('INFO'))

    @manipulative
    @trap_exception
    def polaroid(self, angle=0.0, caption=None, font=None, method='undefined'):
        """Creates a special effect simulating a Polaroid photo.

        :see: Example of :ref:`polaroid`.

        :param angle: applies a shadow effect along this angle.
        :type angle: :class:`numbers.Real`
        :param caption: Writes a message at the bottom of the photo's border.
        :type caption: :class:`str`
        :param font: Specify font style.
        :type font: :class:`wand.font.Font`
        :param method: Interpolation method. ImageMagick-7 only.
        :type method: :class:`str`

        .. versionadded:: 0.5.4
        """
        assertions.assert_real(angle=angle)
        assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                  'wand.image.PIXEL_INTERPOLATE_METHODS',
                                  method=method)
        ctx_ptr = library.NewDrawingWand()
        if caption:
            assertions.assert_string(caption=caption)
            caption = binary(caption)
            library.MagickSetImageProperty(self.wand, b'Caption',
                                           caption)
            if isinstance(font, Font):
                if font.path:
                    library.DrawSetFont(ctx_ptr, binary(font.path))
                if font.size:
                    library.DrawSetFontSize(ctx_ptr, font.size)
                if font.color:
                    with font.color:
                        library.DrawSetFillColor(ctx_ptr, font.color.resource)
                library.DrawSetTextAntialias(ctx_ptr, font.antialias)
                if font.stroke_color:
                    with font.stroke_color:
                        library.DrawSetStrokeColor(ctx_ptr,
                                                   font.stroke_color.resource)
                if font.stroke_width:
                    library.DrawSetStrokeWidth(ctx_ptr, font.stroke_width)
            elif font:
                raise TypeError('font must be in instance of '
                                'wand.font.Font, not ' + repr(font))
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickPolaroidImage(self.wand, ctx_ptr, angle)
        else:  # pragma: no cover
            method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
            r = library.MagickPolaroidImage(self.wand, ctx_ptr, caption, angle,
                                            method_idx)
        ctx_ptr = library.DestroyDrawingWand(ctx_ptr)
        return r

    @manipulative
    @trap_exception
    def polynomial(self, arguments):
        """Replace image with the sum of all images in a sequence by
        calculating the pixel values a coefficient-weight value, and a
        polynomial-exponent.

        For example::

            with Image(filename='rose:') as img:
                img.polynomial(arguments=[0.5, 1.0])

        The output image will be calculated as:

        .. math::

            output = 0.5 * image ^ {1.0}

        This can work on multiple images in a sequence by calculating across
        each frame in the image stack.

        .. code::

            with Image(filename='2frames.gif') as img:
                img.polynomial(arguments=[0.5, 1.0, 0.25, 1.25])

        Where the results would be calculated as:

        .. math::

            output = 0.5 * frame1 ^ {1.0} + 0.25 * frame2 ^ {1.25}

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param arguments: A list of real numbers where at least two numbers
                         (weight & exponent) are need for each image in the
                         sequence.
        :type arguments: :class:`collections.abc.Sequence`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickPolynomialImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        if not isinstance(arguments, abc.Sequence):
            raise TypeError('expected sequence of doubles, not ' +
                            repr(arguments))
        argc = len(arguments)
        argv = (ctypes.c_double * argc)(*arguments)
        return library.MagickPolynomialImage(self.wand, (argc >> 1), argv)

    @manipulative
    @trap_exception
    def posterize(self, levels=None, dither='no'):
        """Reduce color levels per channel.

        :param levels: Number of levels per channel.
        :type levels: :class:`numbers.Integral`
        :param dither: Dither method to apply.
                       See :const:`DITHER_METHODS`.
        :type dither: `str`

        .. versionadded:: 0.5.0
        """
        assertions.assert_integer(levels=levels)
        assertions.string_in_list(DITHER_METHODS, 'wand.image.DITHER_METHODS',
                                  dither=dither)
        dither_idx = DITHER_METHODS.index(dither)
        return library.MagickPosterizeImage(self.wand, levels, dither_idx)

    @manipulative
    @trap_exception
    def quantize(self, number_colors, colorspace_type='undefined',
                 treedepth=0, dither=False, measure_error=False):
        """`quantize` analyzes the colors within a sequence of images and
        chooses a fixed number of colors to represent the image. The goal of
        the algorithm is to minimize the color difference between the input and
        output image while minimizing the processing time.

        :param number_colors: The target number of colors to reduce the image.
        :type number_colors: :class:`numbers.Integral`
        :param colorspace_type: Available value can be found
                                in the :const:`COLORSPACE_TYPES`. Defaults
                                value ``"undefined"``.
        :type colorspace_type: :class:`str`
        :param treedepth: A value between ``0`` & ``8`` where ``0`` will
                         allow ImageMagick to calculate the optimal depth
                         with ``Log4(number_colors)``. Default value is ``0``.
        :type treedepth: :class:`numbers.Integral`
        :param dither: Perform dither operation between neighboring pixel
                       values. If using ImageMagick-6, this can be a value
                       of :const:`True`, or :const:`False`. With ImageMagick-7,
                       use a string from :const:`DITHER_METHODS`. Default
                       :const:`False`.
        :type dither: :class:`bool`, or :class:`str`
        :param measure_error: Include total quantization error of all pixels
                              in an image & quantized value.
        :type measure_error: :class:`bool`

        .. versionadded:: 0.4.2

        .. versionchanged:: 0.5.9
           Fixed ImageMagick-7 ``dither`` argument, and added keyword defaults.

        .. versionchanged:: 0.7.0
           The default value for ``colorspace_type`` argument
           is now set to ``"undefeined"`` to match CLI behavior.
        """
        assertions.assert_integer(number_colors=number_colors)
        assertions.string_in_list(COLORSPACE_TYPES,
                                  'wand.image.COLORSPACE_TYPES',
                                  colorspace_type=colorspace_type)
        colorspace_type = COLORSPACE_TYPES.index(colorspace_type)
        assertions.assert_integer(treedepth=treedepth)
        if MAGICK_VERSION_NUMBER < 0x700:
            assertions.assert_bool(dither=dither)
        else:
            if dither is False:
                dither = 'no'
            elif dither is True:
                dither = 'riemersma'
            assertions.string_in_list(DITHER_METHODS,
                                      'wand.image.DITHER_METHODS',
                                      dither=dither)
            dither = DITHER_METHODS.index(dither)
        assertions.assert_bool(measure_error=measure_error)
        return library.MagickQuantizeImage(
            self.wand, number_colors, colorspace_type,
            treedepth, dither, measure_error
        )

    @manipulative
    @trap_exception
    def random_threshold(self, low=0.0, high=1.0, channel=None):
        """Performs a random dither to force a pixel into a binary black &
        white state. Each color channel operarates independently from each
        other.

        :param low: bottom threshold. Any pixel value below the given value
                    will be rendered "0", or no value. Given threshold value
                    can be between ``0.0`` & ``1.0``, or ``0`` &
                    :attr:`quantum_range`.
        :type low: :class:`numbers.Real`
        :param high: top threshold. Any pixel value above the given value
                     will be rendered as max quantum value. Given threshold
                     value can be between ``0.0`` & ``1.0``, or ``0`` &
                     :attr:`quantum_range`.
        :type high: :class:`numbers.Real`
        :param channel: Optional argument to apply dither to specific color
                        channel. See :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.7
        """
        assertions.assert_real(low=low, high=high)
        if 0 < low <= 1.0:
            low *= self.quantum_range
        if 0 < high <= 1.0:
            high *= self.quantum_range
        if channel is None:
            r = library.MagickRandomThresholdImage(self.wand, low, high)
        else:
            ch_channel = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickRandomThresholdImageChannel(self.wand,
                                                              ch_channel,
                                                              low,
                                                              high)
            else:  # pragma: no cover
                # Set active channel, and capture mask to restore.
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 ch_channel)
                r = library.MagickRandomThresholdImage(self.wand, low, high)
                # Restore original state of channels
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    def range_channel(self, channel='default_channels'):
        """Calculate the minimum and maximum of quantum values in image.

        .. code:: python

            from wand.image import Image

            with Image(filename='input.jpg') as img:
                minima, maxima = img.range_channel()

        :param channel: Select which color channel to evaluate. See
                        :const:`CHANNELS`. Default ``'default_channels'``.
        :type channel: :class:`str`
        :returns: Tuple of :attr:`minima` & :attr:`maxima`
                  values. Each value will be between 0.0 &
                  :attr:`quantum_range`.
        :rtype: :class:`tuple`

        .. versionadded:: 0.5.3
        """
        ch_channel = self._channel_to_mask(channel)
        min_color = ctypes.c_double(0.0)
        max_color = ctypes.c_double(0.0)
        if MAGICK_VERSION_NUMBER < 0x700:
            library.MagickGetImageChannelRange(self.wand, ch_channel,
                                               ctypes.byref(min_color),
                                               ctypes.byref(max_color))
        else:  # pragma: no cover
            # Set active channel, and capture mask to restore.
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             ch_channel)
            library.MagickGetImageRange(self.wand,
                                        ctypes.byref(min_color),
                                        ctypes.byref(max_color))
            # Restore original state of channels
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return min_color.value, max_color.value

    @manipulative
    @trap_exception
    def range_threshold(self, low_black=0.0, low_white=None, high_white=None,
                        high_black=None):
        """Applies soft & hard thresholding.

        For a soft thresholding, parameters should be monotonically increasing:

            with Image(filename='text.png') as img:
                img.range_threshold(0.2, 0.4, 0.6, 0.8)

        For a hard thresholding, parameters should be the same:

            with Image(filename='text.png') as img:
                img.range_threshold(0.4, 0.4, 0.6, 0.6)

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :param low_black: Define the minimum threshold value.
        :type low_black: :class:`numbers.Real`
        :param low_white: Define the minimum threshold value.
        :type low_white: :class:`numbers.Real`
        :param high_white: Define the maximum threshold value.
        :type high_white: :class:`numbers.Real`
        :param high_black: Define the maximum threshold value.
        :type high_black: :class:`numbers.Real`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickRangeThresholdImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        # Populate defaults to follow CLI behavior
        if low_white is None:
            low_white = low_black
        if high_white is None:
            high_white = low_white
        if high_black is None:
            high_black = high_white
        assertions.assert_real(low_black=low_black, low_white=low_white,
                               high_white=high_white, high_black=high_black)
        if 0 < low_black <= 1.0:
            low_black *= self.quantum_range
        if 0 < low_white <= 1.0:
            low_white *= self.quantum_range
        if 0 < high_white <= 1.0:
            high_white *= self.quantum_range
        if 0 < high_black <= 1.0:
            high_black *= self.quantum_range
        return library.MagickRangeThresholdImage(self.wand,
                                                 low_black, low_white,
                                                 high_white, high_black)

    @trap_exception
    def read_mask(self, clip_mask=None):
        """Sets the read mask where the gray values of the clip mask
        are used to blend during composite operations. Call this method with
        a ``None`` argument to clear any previously set masks.

        This method is also useful for :meth:`compare` method for limiting
        region of interest.

        .. warning::
           This method is only available with ImageMagick-7.

        :param clip_mask: Image to reference as blend mask.
        :type clip_mask: :class:`BaseImage`

        .. versionadded:: 0.5.7
        """
        r = False
        ReadPixelMask = 0x000001
        if library.MagickSetImageMask is None:
            raise WandLibraryVersionError('Method requires ImageMagick-7.')
        else:  # pragma: no cover
            if clip_mask is None:
                r = library.MagickSetImageMask(self.wand, ReadPixelMask, None)
            elif isinstance(clip_mask, BaseImage):
                r = library.MagickSetImageMask(self.wand, ReadPixelMask,
                                               clip_mask.wand)
        return r

    def region(self, width=None, height=None, x=None, y=None, gravity=None):
        """Extract an area of the image. This is the same as :meth:`crop`,
        but returns a new instance of :class:`Image` without altering the
        original source image.

        .. code-block:: python

            from wand.image import Image

            with Image(filename='input.jpg') as img:
                with img.region(width=100, height=100, x=0, y=0) as area:
                    pass

        :param width: Area size on the X-axis. Default value is
                      the image's :attr:`page_width`.
        :type width: :class:`numbers.Integral`
        :param height: Area size on the  Y-axis.Default value is
                       the image's :attr:`page_height`.
        :type height: :class:`numbers.Integral`
        :param x: X-axis offset. This number can be negative. Default value is
                  the image's :attr:`page_x`.
        :type x: :class:`numbers.Integral`
        :param y: Y-axis offset. This number can be negative. Default value is
                  the image's :attr:`page_y`.
        :type y: :class:`numbers.Integral`
        :param region: Helper attribute to set ``x`` & ``y`` offset. See
                       :const:`GRAVITY_TYPES`.
        :type region: :class:`str`
        :returns: New instance of Wand.
        :rtype: :class:`Image`

        .. versionadded:: 0.6.8

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        ow, oh, ox, oy = self.page
        if width is None:
            width = ow
        if height is None:
            height = oh
        assertions.assert_unsigned_integer(width=width, height=height)
        if x is None:
            x = ox
        if y is None:
            y = oy
        assertions.assert_integer(x=x, y=y)
        if gravity is not None:
            gy, gx = self._gravity_to_offset(gravity, width, height)
            y += gy
            x += gx
        new_wand = library.MagickGetImageRegion(self.wand, width, height, x, y)
        if not new_wand:
            self.raise_exception()
        return Image(BaseImage(new_wand))

    @manipulative
    @trap_exception
    def remap(self, affinity=None, method='no'):
        """Rebuild image palette with closest color from given affinity image.

        :see: Example of :ref:`remap`.

        :param affinity: reference image.
        :type affinity: :class:`BaseImage`
        :param method: dither method. See :const:`DITHER_METHODS`.
                       Default is ``'no'`` dither.
        :type method: :class:`str`

        .. versionadded:: 0.5.3
        """
        if not isinstance(affinity, BaseImage):
            raise TypeError('Expecting affinity to be a BaseImage, not ' +
                            repr(affinity))
        assertions.string_in_list(DITHER_METHODS, 'wand.image.DITHER_METHODS',
                                  method=method)
        method_idx = DITHER_METHODS.index(method)
        return library.MagickRemapImage(self.wand, affinity.wand, method_idx)

    @manipulative
    @trap_exception
    def resample(self, x_res=None, y_res=None, filter='undefined', blur=1):
        """Adjust the number of pixels in an image so that when displayed at
        the given Resolution or Density the image will still look the same size
        in real world terms.

        .. note::

            This method will automatically :meth:`coalesce` & resample all
            frames in a GIF animation. For other image formats,
            :meth:`resample` will only effect the current image in the stack.
            Use :meth:`iterator_reset` & :meth:`iterator_next` to traverse
            the image stack to resample all images in a multi-layer document.

            .. code::

                with Image(filename='input.tiff') as img:
                    img.iterator_reset()
                    while True:
                        img.resample(128, 128)
                        if not img.iterator_next():
                            break

        :param x_res: the X resolution (density) in the scaled image. default
                      is  the original resolution.
        :type x_res: :class:`numbers.Real`
        :param y_res: the Y resolution (density) in the scaled image. default
                      is the original resolution.
        :type y_res: :class:`numbers.Real`
        :param filter: a filter type to use for resizing. choose one in
                       :const:`FILTER_TYPES`. default is ``'undefined'``
                       which means IM will try to guess best one to use.
        :type filter: :class:`str`, :class:`numbers.Integral`
        :param blur: the blur factor where > 1 is blurry, < 1 is sharp.
                     default is 1
        :type blur: :class:`numbers.Real`

        .. versionadded:: 0.4.5
        """
        if x_res is None:
            x_res, _ = self.resolution
        if y_res is None:
            _, y_res = self.resolution
        assertions.assert_real(x_res=x_res, y_res=y_res, blur=blur)
        if x_res < 1:
            raise ValueError('x_res must be a Real number, not ' +
                             repr(x_res))
        elif y_res < 1:
            raise ValueError('y_res must be a Real number, not ' +
                             repr(y_res))
        elif not isinstance(filter, (str, numbers.Integral)):
            raise TypeError('filter must be one string defined in wand.image.'
                            'FILTER_TYPES or an integer, not ' + repr(filter))
        if isinstance(filter, str):
            try:
                filter = FILTER_TYPES.index(filter)
            except IndexError:
                raise ValueError(repr(filter) + ' is an invalid filter type; '
                                 'choose on in ' + repr(FILTER_TYPES))
        elif (isinstance(filter, numbers.Integral) and
              not (0 <= filter < len(FILTER_TYPES))):
            raise ValueError(repr(filter) + ' is an invalid filter type')
        blur = ctypes.c_double(float(blur))
        if self.animation:
            self.wand = library.MagickCoalesceImages(self.wand)
            self.reset_sequence()
            library.MagickSetLastIterator(self.wand)
            n = library.MagickGetIteratorIndex(self.wand)
            library.MagickResetIterator(self.wand)
            for i in range(n + 1):
                library.MagickSetIteratorIndex(self.wand, i)
                r = library.MagickResampleImage(self.wand, x_res, y_res,
                                                filter, blur)
        else:
            r = library.MagickResampleImage(self.wand, x_res, y_res,
                                            filter, blur)
        return r

    def reset_coords(self):
        """Reset the coordinate frame of the image so to the upper-left corner
        is (0, 0) again (crop and rotate operations change it).

        .. versionadded:: 0.2.0

        """
        library.MagickResetImagePage(self.wand, None)

    def reset_sequence(self):
        """Abstract method prototype.
        See :meth:`wand.image.Image.reset_sequence()`.

        .. versionadded:: 0.6.0
        """
        pass

    @manipulative
    @trap_exception
    def resize(self, width=None, height=None, filter='undefined', blur=1):
        """Resizes the image.

        :param width: the width in the scaled image. default is the original
                      width
        :type width: :class:`numbers.Integral`
        :param height: the height in the scaled image. default is the original
                       height
        :type height: :class:`numbers.Integral`
        :param filter: a filter type to use for resizing. choose one in
                       :const:`FILTER_TYPES`. default is ``'undefined'``
                       which means IM will try to guess best one to use
        :type filter: :class:`str`, :class:`numbers.Integral`
        :param blur: the blur factor where > 1 is blurry, < 1 is sharp.
                     default is 1
        :type blur: :class:`numbers.Real`

        .. versionchanged:: 0.2.1
           The default value of ``filter`` has changed from ``'triangle'``
           to ``'undefined'`` instead.

        .. versionchanged:: 0.1.8
           The ``blur`` parameter changed to take :class:`numbers.Real`
           instead of :class:`numbers.Rational`.

        .. versionadded:: 0.1.1

        """
        if width is None:
            width = self.width
        if height is None:
            height = self.height
        assertions.assert_counting_number(width=width, height=height)
        assertions.assert_real(blur=blur)
        if not isinstance(filter, (str, numbers.Integral)):
            raise TypeError('filter must be one string defined in wand.image.'
                            'FILTER_TYPES or an integer, not ' + repr(filter))
        if isinstance(filter, str):
            try:
                filter = FILTER_TYPES.index(filter)
            except IndexError:
                raise ValueError(repr(filter) + ' is an invalid filter type; '
                                 'choose on in ' + repr(FILTER_TYPES))
        elif (isinstance(filter, numbers.Integral) and
              not (0 <= filter < len(FILTER_TYPES))):
            raise ValueError(repr(filter) + ' is an invalid filter type')
        blur = ctypes.c_double(float(blur))
        if self.animation:
            self.wand = library.MagickCoalesceImages(self.wand)
            self.reset_sequence()
            library.MagickSetLastIterator(self.wand)
            n = library.MagickGetIteratorIndex(self.wand)
            library.MagickResetIterator(self.wand)
            for i in range(n + 1):
                library.MagickSetIteratorIndex(self.wand, i)
                r = library.MagickResizeImage(self.wand, width, height,
                                              filter, blur)
            library.MagickSetSize(self.wand, width, height)
        else:
            r = library.MagickResizeImage(self.wand, width, height,
                                          filter, blur)
            library.MagickSetSize(self.wand, width, height)
        return r

    @manipulative
    @trap_exception
    def roll(self, x=0, y=0):
        """Shifts all pixels over by an X/Y offset.

        :param x: Number of columns to roll over. Negative value will roll
                  pixels from right-to-left, and positive value will roll
                  pixels from left-to-right. Default value: ``0``.
        :type x: :class:`numbers.Integral`
        :param y: Number of rows to roll over. Negative value will roll
                  pixels from bottom-to-top, and positive value will roll
                  pixels from top-to-bottm. Default value: ``0``.
        :type y: :class:`numbers.Integral`

        .. versionadded:: 0.6.8
        """
        assertions.assert_integer(x=x, y=y)
        return library.MagickRollImage(self.wand, x, y)

    @manipulative
    @trap_exception
    def rotate(self, degree, background=None, reset_coords=True):
        """Rotates the image right.  It takes a ``background`` color
        for ``degree`` that isn't a multiple of 90.

        :see: Example of :ref:`rotate`.

        :param degree: a degree to rotate. multiples of 360 affect nothing
        :type degree: :class:`numbers.Real`
        :param background: an optional background color.
                           default is transparent
        :type background: :class:`wand.color.Color`
        :param reset_coords: optional flag. If set, after the rotation, the
            coordinate frame will be relocated to the upper-left corner of
            the new image. By default is `True`.
        :type reset_coords: :class:`bool`

        .. versionadded:: 0.2.0
           The ``reset_coords`` parameter.

        .. versionadded:: 0.1.8

        """
        if background is None:
            background = Color('transparent')
        elif isinstance(background, str):
            background = Color(background)
        assertions.assert_color(background=background)
        assertions.assert_real(degree=degree)
        with background:
            if self.animation:
                self.wand = library.MagickCoalesceImages(self.wand)
                self.reset_sequence()
                library.MagickSetLastIterator(self.wand)
                n = library.MagickGetIteratorIndex(self.wand)
                library.MagickResetIterator(self.wand)
                for i in range(0, n + 1):
                    library.MagickSetIteratorIndex(self.wand, i)
                    result = library.MagickRotateImage(self.wand,
                                                       background.resource,
                                                       degree)
                    if reset_coords:
                        library.MagickResetImagePage(self.wand, None)
            else:
                result = library.MagickRotateImage(self.wand,
                                                   background.resource,
                                                   degree)
                if reset_coords:
                    self.reset_coords()
        return result

    @manipulative
    @trap_exception
    def rotational_blur(self, angle=0.0, channel=None):
        """Blur an image in a radius around the center of an image.

        .. warning:: Requires ImageMagick-6.8.8 or greater.

        :see: Example of :ref:`rotational_blur`.

        :param angle: Degrees of rotation to blur with.
        :type angle: :class:`numbers.Real`
        :param channel: Optional channel to apply the effect against. See
                        :const:`CHANNELS` for a list of possible values.
        :type channel: :class:`str`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.4
        """
        if not library.MagickRotationalBlurImage:  # pragma: no cover
            msg = ("Method `rotational_blur` not available on installed "
                   "version of ImageMagick library. ")
            raise WandLibraryVersionError(msg)
        assertions.assert_real(angle=angle)
        if channel:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickRotationalBlurImageChannel(self.wand,
                                                             channel_ch,
                                                             angle)
            else:  # pragma: no cover
                channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                                 channel_ch)
                r = library.MagickRotationalBlurImage(self.wand, angle)
                library.MagickSetImageChannelMask(self.wand, channel_mask)
        else:
            r = library.MagickRotationalBlurImage(self.wand, angle)
        return r

    @manipulative
    @trap_exception
    def sample(self, width=None, height=None):
        """Resizes the image by sampling the pixels.  It's basically quicker
        than :meth:`resize()` except less quality as a trade-off.

        :param width: the width in the scaled image. default is the original
                      width
        :type width: :class:`numbers.Integral`
        :param height: the height in the scaled image. default is the original
                       height
        :type height: :class:`numbers.Integral`

        .. versionadded:: 0.3.4

        """
        if width is None:
            width = self.width
        if height is None:
            height = self.height
        assertions.assert_counting_number(width=width, height=height)
        if self.animation:
            self.wand = library.MagickCoalesceImages(self.wand)
            self.reset_sequence()
            library.MagickSetLastIterator(self.wand)
            n = library.MagickGetIteratorIndex(self.wand)
            library.MagickResetIterator(self.wand)
            for i in range(n + 1):
                library.MagickSetIteratorIndex(self.wand, i)
                r = library.MagickSampleImage(self.wand, width, height)
            library.MagickSetSize(self.wand, width, height)
        else:
            r = library.MagickSampleImage(self.wand, width, height)
            library.MagickSetSize(self.wand, width, height)
        return bool(r)

    @manipulative
    @trap_exception
    def scale(self, columns=1, rows=1):
        """Increase image size by scaling each pixel value by given ``columns``
        and ``rows``.

        :param columns: The number of columns, in pixels, to scale the image
                        horizontally.
        :type columns: :class:`numbers.Integral`
        :param rows: The number of rows, in pixels, to scale the image
                        vertically.
        :type rows: :class:`numbers.Integral`

        .. versionadded:: 0.5.7
        """
        assertions.assert_counting_number(columns=columns, rows=rows)
        return library.MagickScaleImage(self.wand, columns, rows)

    @manipulative
    @trap_exception
    def selective_blur(self, radius=0.0, sigma=0.0, threshold=0.0,
                       channel=None):
        """Blur an image within a given threshold.

        For best effects, use a value between 10% and 50% of
        :attr:`quantum_range`

        .. code::

            from wand.image import Image

            with Image(filename='photo.jpg') as img:
                # Apply 8x3 blur with a 10% threshold
                img.selective_blur(8.0, 3.0, 0.1 * img.quantum_range)

        :see: Example of :ref:`selective_blur`.

        :param radius: Size of gaussian aperture.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of gaussian operator.
        :type sigma: :class:`numbers.Real`
        :param threshold: Only pixels within contrast threshold are effected.
                          Value should be between ``0.0`` and
                          :attr:`quantum_range`.
        :type threshold: :class:`numbers.Real`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        assertions.assert_real(radius=radius, sigma=sigma, threshold=threshold)
        if channel is None:
            r = library.MagickSelectiveBlurImage(self.wand,
                                                 radius,
                                                 sigma,
                                                 threshold)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickSelectiveBlurImageChannel(self.wand,
                                                            channel_ch,
                                                            radius,
                                                            sigma,
                                                            threshold)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickSelectiveBlurImage(self.wand,
                                                     radius,
                                                     sigma,
                                                     threshold)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def sepia_tone(self, threshold=0.8):
        """Creates a Sepia Tone special effect similar to a darkroom chemical
        toning.

        :see: Example of :ref:`sepia_tone`.

        :param threshold: The extent of the toning. Value can be between ``0``
                          & :attr:`quantum_range`, or ``0`` & ``1.0``.
                          Default value is ``0.8`` or "80%".
        :type threshold: :class:`numbers.Real`

        .. versionadded:: 0.5.7
        """
        assertions.assert_real(threshold=threshold)
        if 0.0 < threshold <= 1.0:
            threshold *= self.quantum_range
        return library.MagickSepiaToneImage(self.wand, threshold)

    @manipulative
    @trap_exception
    def shade(self, gray=False, azimuth=0.0, elevation=0.0):
        """Creates a 3D effect by simulating a light from an
        elevated angle.

        :see: Example of :ref:`shade`.

        :param gray: Isolate the effect on pixel intensity.
                     Default is False.
        :type gray: :class:`bool`
        :param azimuth: Angle from x-axis.
        :type azimuth: :class:`numbers.Real`
        :param elevation: Amount of pixels from the z-axis.
        :type elevation: :class:`numbers.Real`

        .. versionadded:: 0.5.0
        """
        assertions.assert_real(azimuth=azimuth, elevation=elevation)
        return library.MagickShadeImage(self.wand, gray,
                                        azimuth, elevation)

    @manipulative
    @trap_exception
    def shadow(self, alpha=0.0, sigma=0.0, x=0, y=0):
        """Generates an image shadow.

        :param alpha: Ratio of transparency.
        :type alpha: :class:`numbers.Real`
        :param sigma: Standard deviation of the gaussian filter.
        :type sigma: :class:`numbers.Real`
        :param x: x-offset.
        :type x: :class:`numbers.Integral`
        :param y: y-offset.
        :type y: :class:`numbers.Integral`

        .. versionadded:: 0.5.0
        """
        assertions.assert_real(alpha=alpha, sigma=sigma)
        assertions.assert_integer(x=x, y=y)
        return library.MagickShadowImage(self.wand, alpha, sigma, x, y)

    @manipulative
    @trap_exception
    def sharpen(self, radius=0.0, sigma=0.0, channel=None):
        """Applies a gaussian effect to enhance the sharpness of an
        image.

        .. note::

            For best results, ensure ``radius`` is larger than
            ``sigma``.

            Defaults values of zero will have ImageMagick attempt
            to auto-select suitable values.

        :see: Example of :ref:`sharpen`.

        :param radius: size of gaussian aperture.
        :type radius: :class:`numbers.Real`
        :param sigma: Standard deviation of the gaussian filter.
        :type sigma: :class:`numbers.Real`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.0

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        if channel is None:
            r = library.MagickSharpenImage(self.wand, radius, sigma)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickSharpenImageChannel(self.wand,
                                                      channel_ch,
                                                      radius, sigma)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickSharpenImage(self.wand, radius, sigma)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def shave(self, columns=0, rows=0):
        """Remove pixels from the edges.

        :param columns: amount to shave off both sides of the x-axis.
        :type columns: :class:`numbers.Integral`
        :param rows: amount to shave off both sides of the y-axis.
        :type rows: :class:`numbers.Integral`

        .. versionadded:: 0.5.0
        """
        assertions.assert_integer(columns=columns, row=rows)
        return library.MagickShaveImage(self.wand, columns, rows)

    @manipulative
    @trap_exception
    def shear(self, background='WHITE', x=0.0, y=0.0):
        """Shears the image to create a parallelogram, and fill the space
        created with a ``background`` color.

        :param background: Color to fill the void created by shearing the
                           image.
        :type background: :class:`wand.color.Color`
        :param x: Slide the image along the X-axis.
        :type x: :class:`numbers.Real`
        :param y: Slide the image along the Y-axis.
        :type y: :class:`numbers.Real`

        .. versionadded:: 0.5.4
        """
        if isinstance(background, str):
            background = Color(background)
        assertions.assert_color(background=background)
        assertions.assert_real(x=x, y=y)
        with background:
            r = library.MagickShearImage(self.wand, background.resource, x, y)
        return r

    @manipulative
    @trap_exception
    def sigmoidal_contrast(self, sharpen=True, strength=0.0, midpoint=0.0,
                           channel=None):
        """Modifies the contrast of the image by applying non-linear sigmoidal
        algorithm.

        .. code:: python

            with Image(filename='photo.jpg') as img:
                img.sigmoidal_contrast(sharpen=True,
                                       strength=3,
                                       midpoint=0.65 * img.quantum_range)

        :param sharpen: Increase the contrast when ``True`` (default), else
                        reduces contrast.
        :type sharpen: :class:`bool`
        :param strength: How much to adjust the contrast. Where a value of
                         ``0.0`` has no effect, ``3.0`` is typical, and
                         ``20.0`` is extreme.
        :type strength: :class:`numbers.Real`
        :param midpoint: Normalized value between `0.0` & :attr:`quantum_range`
        :type midpoint: :class:`numbers.Real`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`.
        :type channel: :class:`str`

        .. versionadded:: 0.5.4

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        assertions.assert_bool(sharpen=sharpen)
        assertions.assert_real(strength=strength, midpoint=midpoint)
        if channel is None:
            r = library.MagickSigmoidalContrastImage(self.wand,
                                                     sharpen,
                                                     strength,
                                                     midpoint)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickSigmoidalContrastImageChannel(
                    self.wand, channel_ch, sharpen, strength, midpoint
                )
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickSigmoidalContrastImage(self.wand,
                                                         sharpen,
                                                         strength,
                                                         midpoint)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    def similarity(self, reference, threshold=0.0,
                   metric='undefined'):
        """Scan image for best matching ``reference`` image, and
        return location & similarity.

        Use parameter ``threshold`` to stop subimage scanning if the matching
        similarity value is below the given value. This is the same as the CLI
        ``-similarity-threshold`` option.

        This method will always return a location & the lowest computed
        similarity value. Users are responsible for checking the similarity
        value to determine if a matching location is valid. Traditionally, a
        similarity value greater than `0.3183099` is considered dissimilar.

        .. code:: python

            from wand.image import Image

            dissimilarity_threshold = 0.318
            similarity_threshold = 0.05
            with Image(filename='subject.jpg') as img:
                with Image(filename='object.jpg') as reference:
                    location, diff = img.similarity(reference,
                                                    similarity_threshold)
                    if diff > dissimilarity_threshold:
                        print('Images too dissimilar to match')
                    elif diff <= similarity_threshold:
                        print('First match @ {left}x{top}'.format(**location))
                    else:
                        print('Best match @ {left}x{top}'.format(**location))

        .. warning::

            This operation can be slow to complete.

        :param reference: Image to search for.
        :type reference: :class:`wand.image.Image`
        :param threshold: Stop scanning if reference similarity is
                          below given threshold. Value can be between ``0.0``
                          and :attr:`quantum_range`. Default is ``0.0``.
        :type threshold: :class:`numbers.Real`
        :param metric: specify which comparison algorithm to use. See
                       :const:`COMPARE_METRICS` for a list of values.
                       Only used by ImageMagick-7.
        :type metric: :class:`str`
        :returns: List of location & similarity value. Location being a
                  dictionary of ``width``, ``height``, ``left``, & ``top``.
                  The similarity value is the compare distance, so a value of
                  ``0.0`` means an exact match.
        :rtype: :class:`tuple` (:class:`dict`, :class:`numbers.Real`)

        .. versionadded:: 0.5.4

           has been added.
        """
        assertions.assert_real(threshold=threshold)
        if not isinstance(reference, BaseImage):
            raise TypeError('reference must be in instance of '
                            'wand.image.Image, not ' + repr(reference))
        rio = RectangleInfo(0, 0, 0, 0)
        diff = ctypes.c_double(0.0)
        if MAGICK_VERSION_NUMBER < 0x700:
            artifact_value = binary(str(threshold))  # FIXME
            library.MagickSetImageArtifact(self.wand,
                                           b'compare:similarity-threshold',
                                           artifact_value)
            r = library.MagickSimilarityImage(self.wand,
                                              reference.wand,
                                              ctypes.byref(rio),
                                              ctypes.byref(diff))
        else:  # pragma: no cover
            assertions.string_in_list(COMPARE_METRICS,
                                      'wand.image.COMPARE_METRICS',
                                      metric=metric)
            metric_idx = COMPARE_METRICS.index(metric)
            r = library.MagickSimilarityImage(self.wand,
                                              reference.wand,
                                              metric_idx,
                                              threshold,
                                              ctypes.byref(rio),
                                              ctypes.byref(diff))
        if not r:  # pragma: no cover
            self.raise_exception()
        else:
            r = library.DestroyMagickWand(r)
        location = dict(width=rio.width, height=rio.height,
                        top=rio.y, left=rio.x)
        return (location, diff.value)

    @manipulative
    @trap_exception
    def sketch(self, radius=0.0, sigma=0.0, angle=0.0):
        """Simulates a pencil sketch effect. For best results, ``radius``
        value should be larger than ``sigma``.

        :see: Example of :ref:`sketch`.

        :param radius: size of Gaussian aperture.
        :type radius: :class:`numbers.Real`
        :param sigma: standard deviation of the Gaussian operator.
        :type sigma: :class:`numbers.Real`
        :param angle: direction of blur.
        :type angle: :class:`numbers.Real`

        .. versionadded:: 0.5.3
        """
        assertions.assert_real(radius=radius, sigma=sigma, angle=angle)
        return library.MagickSketchImage(self.wand, radius, sigma, angle)

    @trap_exception
    def smush(self, stacked=False, offset=0):
        """Appends all images together. Similar behavior to :meth:`concat`,
        but with an optional offset between images.

        :param stacked: If True, will join top-to-bottom. If False, join images
                        from left-to-right (default).
        :type stacked: :class:`bool`
        :param offset: Minimum space (in pixels) between each join.
        :type offset: :class:`numbers.Integral`

        .. versionadded:: 0.5.3
        """
        assertions.assert_integer(offset=offset)
        library.MagickResetIterator(self.wand)
        result = library.MagickSmushImages(self.wand, bool(stacked), offset)
        if result:
            self.wand = result
            self.reset_sequence()
        return bool(result)

    @manipulative
    @trap_exception
    def solarize(self, threshold=0.0, channel=None):
        """Simulates extreme overexposure.

        :see: Example of :ref:`solarize`.

        :param threshold: between ``0.0`` and :attr:`quantum_range`.
        :type threshold: :class:`numbers.Real`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.5
           Added ``channel`` argument.
        """
        assertions.assert_real(threshold=threshold)
        if channel is None:
            r = library.MagickSolarizeImage(self.wand, threshold)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickSolarizeImageChannel(self.wand,
                                                       channel_ch,
                                                       threshold)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickSolarizeImage(self.wand, threshold)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def sparse_color(self, method='barycentric', colors=None,
                     channel_mask=0x7):
        """Interpolates color values between points on an image.

        The ``colors`` argument should be a list of tuples containing the
        point's x, y coordinate & :class:`~wand.color.Color` value.

        For example::

            from wand.image import Image

            colors = [
                (10, 50, 'RED'),
                (174, 32, 'YELLOW'),
                (74, 123, 'ORANGE')
            ]
            with Image(filename='input.png') as img:
                img.sparse_color('bilinear', colors)

        The available interpolate methods are:

        - ``'barycentric'``
        - ``'bilinear'``
        - ``'shepards'``
        - ``'voronoi'``
        - ``'inverse'``
        - ``'manhattan'``

        You can control which color channels are effected by building a custom
        channel mask. For example::

            from wand.image import Image, CHANNELS

            with Image(filename='input.png') as img:
                colors = [
                    (50, 50, img[50, 50]),
                    (100, 50, img[100, 50]),
                    (50, 75, img[50, 75]),
                    (100, 100, img[100, 100])
                ]
                # Only apply Voronoi to Red & Alpha channels
                mask = CHANNELS['red'] | CHANNELS['alpha']
                img.sparse_color('voronoi', colors, channel_mask=mask)

        :param method: Interpolate method. See :const:`SPARSE_COLOR_METHODS`
        :type method: :class:`str`
        :param colors: A list of (x, y, color) tuples.
        :type colors: :class:`abc.Iterable`
                      [ int, int, :class:`~wand.color.Color` ]
        :param channel_mask: Isolate specific color channels to apply
                             interpolation. Default to RGB channels.
        :type channel_mask: :class:`numbers.Integral`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.7.0
           The ``colors`` argument switch from a :class:`dict` to
           :class:`list` to allow two, or more, points of the same
           :class:`~wand.color.Color`.
        """
        def __part2arg__(x, y, color):
            assertions.assert_real(x=x)
            assertions.assert_real(y=y)
            arg = [x, y]
            if isinstance(color, str):
                color = Color(color)
            with color as c:
                if channel_mask & CHANNELS['red']:
                    arg.append(c.red)
                if channel_mask & CHANNELS['green']:
                    arg.append(c.green)
                if channel_mask & CHANNELS['blue']:
                    arg.append(c.blue)
                if channel_mask & CHANNELS['alpha']:
                    arg.append(c.alpha)
            return arg
        assertions.string_in_list(SPARSE_COLOR_METHODS,
                                  'wand.image.SPARSE_COLOR_METHODS',
                                  method=method)
        assertions.assert_unsigned_integer(channel_mask=channel_mask)
        method_idx = SPARSE_COLOR_METHODS[method]
        arguments = list()
        if isinstance(colors, abc.Mapping):
            for color, point in colors.items():
                x, y = point
                arguments.extend(__part2arg__(x, y, color))
        elif isinstance(colors, abc.Iterable):
            for part in colors:
                if len(part) != 3:
                    raise ValueError('Expecting color part as (X, Y, color), '
                                     'not ' + repr(part))
                x, y, color = part
                arguments.extend(__part2arg__(x, y, color))
        else:
            raise TypeError(
                'Colors must be a list or dict, not' + repr(colors)
            )
        argc = len(arguments)
        args = (ctypes.c_double * argc)(*arguments)
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickSparseColorImage(self.wand,
                                               channel_mask,
                                               method_idx,
                                               argc,
                                               args)
        else:  # pragma: no cover
            # Set active channel, and capture mask to restore.
            channel_mask = library.MagickSetImageChannelMask(self.wand,
                                                             channel_mask)
            r = library.MagickSparseColorImage(self.wand,
                                               method_idx,
                                               argc,
                                               args)
            # Restore original state of channels
            library.MagickSetImageChannelMask(self.wand, channel_mask)
        return r

    @manipulative
    @trap_exception
    def splice(self, width=None, height=None, x=None, y=None, gravity=None):
        """Partitions image by splicing a ``width`` x ``height`` rectangle at
        (``x``, ``y``) offset coordinate. The space inserted will be replaced
        by the :attr:`background_color` value.

        :param width: number of pixel columns.
        :type width: :class:`numbers.Integral`
        :param height: number of pixel rows.
        :type height: :class:`numbers.Integral`
        :param x: offset on the X-axis.
        :type x: :class:`numbers.Integral`
        :param y: offset on the Y-axis.
        :type y: :class:`numbers.Integral`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.7.0
           Allow ``x`` & ``y`` offset to apply relative to ``gravity``.
        """
        ow, oh = self.size
        if width is None:
            width = ow
        if height is None:
            height = oh
        assertions.assert_unsigned_integer(width=width, height=height)
        if x is None:
            x = 0
        if y is None:
            y = 0
        assertions.assert_integer(x=x, y=y)
        if gravity is not None:
            gy, gx = self._gravity_to_offset(gravity, width, height)
            x += gx
            y += gy
        return library.MagickSpliceImage(self.wand, width, height, x, y)

    @manipulative
    @trap_exception
    def spread(self, radius=0.0, method='undefined'):
        """Randomly displace pixels within a defined radius.

        :see: Example of :ref:`spread`.

        :param radius: Distance a pixel can be displaced from source. Default
                       value is ``0.0``, which will allow ImageMagick to auto
                       select a radius.
        :type radius: :class:`numbers.Real`
        :param method: Interpolation method. Only available with ImageMagick-7.
                       See :const:`PIXEL_INTERPOLATE_METHODS`.

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.7
           Added default value to ``radius``.
        """
        assertions.assert_real(radius=radius)
        assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                  'wand.image.PIXEL_INTERPOLATE_METHODS',
                                  method=method)
        method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickSpreadImage(self.wand, radius)
        else:  # pragma: no cover
            r = library.MagickSpreadImage(self.wand, method_idx, radius)
        return r

    @manipulative
    @trap_exception
    def statistic(self, stat='undefined', width=None, height=None,
                  channel=None):
        """Replace each pixel with the statistic results from neighboring pixel
        values. The ``width`` & ``height`` defines the size, or aperture, of
        the neighboring pixels.

        :see: Example of :ref:`statistic`.

        :param stat: The type of statistic to calculate. See
                     :const:`STATISTIC_TYPES`.
        :type stat: :class:`str`
        :param width: The size of neighboring pixels on the X-axis.
        :type width: :class:`numbers.Integral`
        :param height: The size of neighboring pixels on the Y-axis.
        :type height: :class:`numbers.Integral`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.5.3

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument.
        """
        assertions.string_in_list(STATISTIC_TYPES,
                                  'wand.image.STATISTIC_TYPES',
                                  statistic=stat)
        assertions.assert_integer(width=width, height=height)
        stat_idx = STATISTIC_TYPES.index(stat)
        if channel is None:
            r = library.MagickStatisticImage(self.wand, stat_idx,
                                             width, height)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickStatisticImageChannel(self.wand,
                                                        channel_ch,
                                                        stat_idx,
                                                        width,
                                                        height)
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand,
                                                         channel_ch)
                r = library.MagickStatisticImage(self.wand, stat_idx,
                                                 width, height)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def stegano(self, watermark, offset=0):
        """Hide a digital watermark of an image within the image.

        .. code-block:: python

            from wand.image import Image

            # Embed watermark
            with Image(filename='source.png') as img:
                with Image(filename='gray_watermark.png') as watermark:
                    print('watermark size (for recovery)', watermark.size)
                    img.stegano(watermark)
                img.save(filename='public.png')

            # Recover watermark
            with Image(width=w, height=h, pseudo='stegano:public.png') as img:
                img.save(filename='recovered_watermark.png')

        :param watermark: Image to hide within image.
        :type watermark: :class:`wand.image.Image`
        :param offset: Start embedding image after a number of pixels.
        :type offset: :class:`numbers.Integral`

        .. versionadded:: 0.5.4
        """
        if not isinstance(watermark, BaseImage):
            raise TypeError('Watermark image must be in instance of '
                            'wand.image.Image, not ' + repr(watermark))
        assertions.assert_integer(offset=offset)
        new_wand = library.MagickSteganoImage(self.wand, watermark.wand,
                                              offset)
        if new_wand:
            self.wand = new_wand
            self.reset_sequence()
        return bool(new_wand)

    @trap_exception
    def strip(self):
        """Strips an image of all profiles and comments.

        .. versionadded:: 0.2.0
        """
        return library.MagickStripImage(self.wand)

    @manipulative
    @trap_exception
    def swirl(self, degree=0.0, method="undefined"):
        """Swirls pixels around the center of the image. The larger the degree
        the more pixels will be effected.

        :see: Example of :ref:`swirl`.

        :param degree: Defines the amount of pixels to be effected. Value
                       between ``-360.0`` and ``360.0``.
        :type degree: :class:`numbers.Real`
        :param method: Controls interpolation of the effected pixels. Only
                       available for ImageMagick-7. See
                       :const:`PIXEL_INTERPOLATE_METHODS`.
        :type method: :class:`str`

        .. versionadded:: 0.5.7
        """
        assertions.assert_real(degree=degree)
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickSwirlImage(self.wand, degree)
        else:  # pragma: no cover
            assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                      'wand.image.PIXEL_INTERPOLATE_METHODS',
                                      method=method)
            method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
            r = library.MagickSwirlImage(self.wand, degree, method_idx)
        return r

    @manipulative
    @trap_exception
    def texture(self, tile):
        """Repeat tile-image across the width & height of the image.

        .. code:: python

            from wand.image import Image

            with Image(width=100, height=100) as canvas:
                with Image(filename='tile.png') as tile:
                    canvas.texture(tile)
                canvas.save(filename='output.png')

        :param tile: image to repeat across canvas.
        :type tile: :class:`Image <wand.image.BaseImage>`

        .. versionadded:: 0.5.4
        """
        if not isinstance(tile, BaseImage):
            raise TypeError('Tile image must be an instance of '
                            'wand.image.Image, not ' + repr(tile))
        r = library.MagickTextureImage(self.wand, tile.wand)
        if r:
            self.wand = r
        return bool(r)

    @manipulative
    @trap_exception
    def threshold(self, threshold=0.5, channel=None):
        """Changes the value of individual pixels based on the intensity
        of each pixel compared to threshold. The result is a high-contrast,
        two color image. It manipulates the image in place.

        :param threshold: threshold as a factor of quantum. A normalized float
                          between ``0.0`` and ``1.0``.
        :type threshold: :class:`numbers.Real`
        :param channel: the channel type.  available values can be found
                        in the :const:`CHANNELS` mapping.  If ``None``,
                        threshold all channels.
        :type channel: :class:`str`

        .. versionadded:: 0.3.10

        """
        assertions.assert_real(threshold=threshold)
        threshold *= self.quantum_range + 1
        if channel is None:
            r = library.MagickThresholdImage(self.wand, threshold)
        else:
            ch_const = self._channel_to_mask(channel)
            r = library.MagickThresholdImageChannel(
                self.wand, ch_const,
                threshold
            )
        return r

    @manipulative
    @trap_exception
    def thumbnail(self, width=None, height=None):
        """Changes the size of an image to the given dimensions and removes any
        associated profiles.  The goal is to produce small low cost thumbnail
        images suited for display on the web.

        :param width: the width in the scaled image. default is the original
                      width
        :type width: :class:`numbers.Integral`
        :param height: the height in the scaled image. default is the original
                       height
        :type height: :class:`numbers.Integral`

        .. versionadded:: 0.5.4
        """
        if width is None:
            width = self.width
        if height is None:
            height = self.height
        assertions.assert_unsigned_integer(width=width, height=height)
        return library.MagickThumbnailImage(self.wand, width, height)

    @manipulative
    @trap_exception
    def tint(self, color=None, alpha=None):
        """Applies a color vector to each pixel in the image.

        :see: Example of :ref:`tint`.

        :param color: Color to calculate midtone.
        :type color: :class:`~wand.color.Color`
        :param alpha: Determine how to blend.
        :type alpha: :class:`~wand.color.Color`

        .. versionadded:: 0.5.3
        """
        if isinstance(color, str):
            color = Color(color)
        if isinstance(alpha, str):
            alpha = Color(alpha)
        assertions.assert_color(color=color, alpha=alpha)
        with color:
            with alpha:
                r = library.MagickTintImage(self.wand,
                                            color.resource,
                                            alpha.resource)
        return r

    @manipulative
    @trap_exception
    def transform(self, crop='', resize=''):
        """Transforms the image using :c:func:`MagickTransformImage`,
        which is a convenience function accepting geometry strings to
        perform cropping and resizing.  Cropping is performed first,
        followed by resizing.  Either or both arguments may be omitted
        or given an empty string, in which case the corresponding action
        will not be performed. Geometry specification strings are
        defined as follows:

        A geometry string consists of a size followed by an optional offset.
        The size is specified by one of the options below,
        where **bold** terms are replaced with appropriate integer values:

        **scale**\\ ``%``
          Height and width both scaled by specified percentage.

        **scale-x**\\ ``%x``\\ \\ **scale-y**\\ ``%``
          Height and width individually scaled by specified percentages.
          Only one % symbol is needed.

        **width**
          Width given, height automagically selected to preserve aspect ratio.

        ``x``\\ \\ **height**
          Height given, width automagically selected to preserve aspect ratio.

        **width**\\ ``x``\\ **height**
          Maximum values of width and height given; aspect ratio preserved.

        **width**\\ ``x``\\ **height**\\ ``!``
          Width and height emphatically given; original aspect ratio ignored.

        **width**\\ ``x``\\ **height**\\ ``>``
          Shrinks images with dimension(s) larger than the corresponding
          width and/or height dimension(s).

        **width**\\ ``x``\\ **height**\\ ``<``
          Enlarges images with dimensions smaller than the corresponding
          width and/or height dimension(s).

        **area**\\ ``@``
          Resize image to have the specified area in pixels.
          Aspect ratio is preserved.

        **X**\\ ``:``\\ **Y**
          Resize at a given aspect ratio. Common aspect ratios may
          include ``4:3`` for video/tv, ``3:2`` for 35mm film, ``16:9`` for
          HDTV, and ``2.39:1`` for cinema. Aspect ratio can be used with the
          crop parameter, but is only available with ImageMagick version 7.0.8
          or greater.

        The offset, which only applies to the cropping geometry string,
        is given by ``{+-}``\\ **x**\\ ``{+-}``\\ **y**\\ , that is,
        one plus or minus sign followed by an **x** offset,
        followed by another plus or minus sign, followed by a **y** offset.
        Offsets are in pixels from the upper left corner of the image.
        Negative offsets will cause the corresponding number of pixels to
        be removed from the right or bottom edge of the image, meaning the
        cropped size will be the computed size minus the absolute value
        of the offset.

        For example, if you want to crop your image to 300x300 pixels
        and then scale it by 2x for a final size of 600x600 pixels,
        you can call::

            image.transform('300x300', '200%')

        This method is a fairly thin wrapper for the C API, and does not
        perform any additional checking of the parameters except insofar as
        verifying that they are of the correct type.  Thus, like the C
        API function, the method is very permissive in terms of what
        it accepts for geometry strings; unrecognized strings and
        trailing characters will be ignored rather than raising an error.

        :param crop: A geometry string defining a subregion of the image
                     to crop to
        :type crop: :class:`str`
        :param resize: A geometry string defining the final size of the image
        :type resize: :class:`str`

        .. seealso::

           `ImageMagick Geometry Specifications`__
              Cropping and resizing geometry for the ``transform`` method are
              specified according to ImageMagick's geometry string format.
              The ImageMagick documentation provides more information about
              geometry strings.

           __ http://www.imagemagick.org/script/command-line-processing.php#geometry

        .. versionadded:: 0.2.2
        .. versionchanged:: 0.5.0
           Will call :meth:`crop()` followed by :meth:`resize()` in the event
           that :c:func:`MagickTransformImage` is not available.
        .. deprecated:: 0.6.0
           Use :meth:`crop()` and :meth:`resize()` instead.
        """  # noqa
        # Check that the values given are the correct types.  ctypes will do
        # this automatically, but we can make the error message more friendly
        # here.
        assertions.assert_string(crop=crop, resize=resize)
        # Also verify that only ASCII characters are included
        try:
            crop = crop.encode('ascii')
        except UnicodeEncodeError:
            raise ValueError('crop must only contain ascii-encodable ' +
                             'characters.')
        try:
            resize = resize.encode('ascii')
        except UnicodeEncodeError:
            raise ValueError('resize must only contain ascii-encodable ' +
                             'characters.')
        if not library.MagickTransformImage:  # pragma: no cover
            # Method removed from ImageMagick-7.
            if crop:
                x = ctypes.c_ssize_t(0)
                y = ctypes.c_ssize_t(0)
                width = ctypes.c_size_t(self.width)
                height = ctypes.c_size_t(self.height)
                if b':' in crop:  # For "4:3" aspect cropping.
                    libmagick.ParseMetaGeometry(crop,
                                                ctypes.byref(x),
                                                ctypes.byref(y),
                                                ctypes.byref(width),
                                                ctypes.byref(height))
                else:
                    libmagick.GetGeometry(crop,
                                          ctypes.byref(x),
                                          ctypes.byref(y),
                                          ctypes.byref(width),
                                          ctypes.byref(height))
                self.crop(top=y.value,
                          left=x.value,
                          width=width.value,
                          height=height.value,
                          reset_coords=False)
            if resize:
                x = ctypes.c_ssize_t(0)
                y = ctypes.c_ssize_t(0)
                width = ctypes.c_size_t(self.width)
                height = ctypes.c_size_t(self.height)
                libmagick.ParseMetaGeometry(resize,
                                            ctypes.byref(x),
                                            ctypes.byref(y),
                                            ctypes.byref(width),
                                            ctypes.byref(height))
                self.resize(width=width.value,
                            height=height.value)
            # Both `BaseImage.crop` & `BaseImage.resize` will handle
            # animation & error handling, so we can stop here.
            return True
        if self.animation:
            new_wand = library.NewMagickWand()
            src_wand = library.MagickCoalesceImages(self.wand)
            length = library.MagickGetNumberImages(self.wand)
            for i in range(length):
                library.MagickSetIteratorIndex(src_wand, i)
                tmp_wand = library.MagickTransformImage(src_wand,
                                                        crop,
                                                        resize)
                library.MagickAddImage(new_wand, tmp_wand)
                if bool(tmp_wand):
                    library.DestroyMagickWand(tmp_wand)
            if bool(src_wand):
                library.DestroyMagickWand(src_wand)
            self.reset_sequence()
        else:
            new_wand = library.MagickTransformImage(self.wand, crop, resize)
        if new_wand:
            self.wand = new_wand
        return bool(new_wand)

    @manipulative
    @trap_exception
    def transform_colorspace(self, colorspace_type):
        """Transform image's colorspace.

        :param colorspace_type: colorspace_type. available value can be found
                                in the :const:`COLORSPACE_TYPES`
        :type colorspace_type: :class:`str`

        .. versionadded:: 0.4.2

        """
        assertions.string_in_list(COLORSPACE_TYPES,
                                  'wand.image.COLORSPACE_TYPES',
                                  colorspace=colorspace_type)
        return library.MagickTransformImageColorspace(
            self.wand,
            COLORSPACE_TYPES.index(colorspace_type)
        )

    @manipulative
    @trap_exception
    def transparent_color(self, color, alpha, fuzz=0, invert=False):
        """Makes the color ``color`` a transparent color with a tolerance of
        fuzz. The ``alpha`` parameter specify the transparency level and the
        parameter ``fuzz`` specify the tolerance.

        :param color: The color that should be made transparent on the image,
                      color object
        :type color: :class:`wand.color.Color`
        :param alpha: the level of transparency: 1.0 is fully opaque
                      and 0.0 is fully transparent.
        :type alpha: :class:`numbers.Real`
        :param fuzz: By default target must match a particular pixel color
                     exactly. However, in many cases two colors may differ
                     by a small amount. The fuzz member of image defines how
                     much tolerance is acceptable to consider two colors as the
                     same. For example, set fuzz to 10 and the color red at
                     intensities of 100 and 102 respectively are now
                     interpreted as the same color for the color.
        :type fuzz: :class:`numbers.Real`
        :param invert: Boolean to tell to paint the inverse selection.
        :type invert: :class:`bool`

        .. versionadded:: 0.3.0

        .. versionchanged:: 0.6.3

            Parameter ``fuzz`` type switched from Integral to Real.

        """
        assertions.assert_real(alpha=alpha)
        assertions.assert_integer(fuzz=fuzz)
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(color=color)
        with color:
            r = library.MagickTransparentPaintImage(self.wand, color.resource,
                                                    alpha, fuzz, invert)
        return r

    @manipulative
    def transparentize(self, transparency):
        """Makes the image transparent by subtracting some percentage of
        the black color channel.  The ``transparency`` parameter specifies the
        percentage.

        :param transparency: the percentage fade that should be performed on
                             the image, from 0.0 to 1.0
        :type transparency: :class:`numbers.Real`

        .. versionadded:: 0.2.0

        """
        if transparency:
            t = ctypes.c_double(float(self.quantum_range *
                                      float(transparency)))
            if t.value > self.quantum_range or t.value < 0:
                raise ValueError('transparency must be a numbers.Real value ' +
                                 'between 0.0 and 1.0')
            # Set the wand to image zero, in case there are multiple images
            # in it
            library.MagickSetIteratorIndex(self.wand, 0)
            # Change the pixel representation of the image
            # to RGB with an alpha channel
            if MAGICK_VERSION_NUMBER < 0x700:
                image_type = 'truecolormatte'
            else:  # pragma: no cover
                image_type = 'truecoloralpha'
            library.MagickSetImageType(self.wand,
                                       IMAGE_TYPES.index(image_type))
            # Perform the black channel subtraction
            self.evaluate(operator='subtract',
                          value=t.value,
                          channel='opacity')
            self.raise_exception()

    @manipulative
    @trap_exception
    def transpose(self):
        """Creates a vertical mirror image by reflecting the pixels around
        the central x-axis while rotating them 90-degrees.

        .. versionadded:: 0.4.1
        """
        return library.MagickTransposeImage(self.wand)

    @manipulative
    @trap_exception
    def transverse(self):
        """Creates a horizontal mirror image by reflecting the pixels around
        the central y-axis while rotating them 270-degrees.

        .. versionadded:: 0.4.1
        """
        return library.MagickTransverseImage(self.wand)

    @manipulative
    @trap_exception
    def trim(self, color=None, fuzz=0.0, reset_coords=False,
             percent_background=None, background_color=None):
        """Remove solid border from image. Uses top left pixel as a guide
        by default, or you can also specify the ``color`` to remove.

        :param color: the border color to remove.
                      if it's omitted top left pixel is used by default
        :type color: :class:`~wand.color.Color`
        :param fuzz: Defines how much tolerance is acceptable to consider
                     two colors as the same. Value can be between ``0.0``,
                     and :attr:`quantum_range`.
        :type fuzz: :class:`numbers.Real`
        :param reset_coords: Reset coordinates after trimming image. Default
                             ``False``.
        :type reset_coords: :class:`bool`
        :param percent_background: Sets how aggressive the trim operation will
                                   be. A value of `0.0` will trim to the
                                   minimal bounding box of all matching color,
                                   and `1.0` to the most outer edge.
        :type percent_background: :class:`numbers.Real`
        :param background_color: Local alias to :attr:`background_color`,
                                 and has the same effect as defining ``color``
                                 parameter -- but much faster.


        .. versionadded:: 0.2.1

        .. versionchanged:: 0.3.0
           Optional ``color`` and ``fuzz`` parameters.

        .. versionchanged:: 0.5.2
           The ``color`` parameter may accept color-compliant strings.

        .. versionchanged:: 0.6.0
           Optional ``reset_coords`` parameter added.

        .. versionchanged:: 0.6.4
           Optional ``percent_background`` & ``background_color`` parameters
           have been added.
        """
        use_border = background_color is None
        if use_border:
            if color is None:
                color = self[0, 0]
            elif isinstance(color, str):
                color = Color(color)
            assertions.assert_color(color=color)
            with color:
                self.border(color, 1, 1, compose='copy')
        assertions.assert_real(fuzz=fuzz)
        assertions.assert_bool(reset_coords=reset_coords)
        if percent_background is not None:
            assertions.assert_real(percent_background=percent_background)
            percent_background = max(min(percent_background, 1.0), 0.0) * 100.0
            str_pb = '{0:g}%'.format(percent_background)
            library.MagickSetImageArtifact(self.wand,
                                           binary('trim:percent-background'),
                                           binary(str_pb))
        if not use_border:
            if isinstance(background_color, str):
                background_color = Color(background_color)
            assertions.assert_color(background_color=background_color)
            bc_key = 'trim:background-color'
            bc_val = background_color.string
            library.MagickSetImageArtifact(self.wand,
                                           binary(bc_key),
                                           binary(bc_val))
        r = library.MagickTrimImage(self.wand, fuzz)
        if reset_coords:
            self.reset_coords()
        elif use_border:
            # Re-calculate page coordinates as we added a 1x1 border before
            # applying the trim.
            adjusted_coords = list(self.page)
            # Width & height are unsigned.
            adjusted_coords[0] = max(adjusted_coords[0] - 2, 0)
            adjusted_coords[1] = max(adjusted_coords[1] - 2, 0)
            # X & Y are signed. It's common for page offsets to be negative.
            adjusted_coords[2] -= 1
            adjusted_coords[3] -= 1
            self.page = adjusted_coords
        return r

    @manipulative
    @trap_exception
    def unique_colors(self):
        """Discards all duplicate pixels, and rebuilds the image
        as a single row.

        .. versionadded:: 0.5.0
        """
        return library.MagickUniqueImageColors(self.wand)

    @manipulative
    @trap_exception
    def unsharp_mask(self, radius=0.0, sigma=1.0, amount=1.0, threshold=0.0,
                     channel=None):
        """Sharpens the image using unsharp mask filter. We convolve the image
        with a Gaussian operator of the given ``radius`` and standard deviation
        (``sigma``). For reasonable results, ``radius`` should be larger than
        ``sigma``. Use a radius of 0 and :meth:`unsharp_mask()` selects
        a suitable radius for you.

        :see: Example of :ref:`unsharp_mask`.

        :param radius: the radius of the Gaussian, in pixels,
                       not counting the center pixel
        :type radius: :class:`numbers.Real`
        :param sigma: the standard deviation of the Gaussian, in pixels
        :type sigma: :class:`numbers.Real`
        :param amount: the percentage of the difference between the original
                       and the blur image that is added back into the original
        :type amount: :class:`numbers.Real`
        :param threshold: the threshold in pixels needed to apply
                          the difference amount.
        :type threshold: :class:`numbers.Real`
        :param channel: Optional color channel to target. See
                        :const:`CHANNELS`
        :type channel: :class:`str`

        .. versionadded:: 0.3.4

        .. versionchanged:: 0.5.5
           Added optional ``channel`` argument.

        .. versionchanged:: 0.5.7
           Added default values to match CLI behavior.
        """
        assertions.assert_real(radius=radius, sigma=sigma,
                               amount=amount, threshold=threshold)
        if channel is None:
            r = library.MagickUnsharpMaskImage(self.wand, radius, sigma,
                                               amount, threshold)
        else:
            channel_ch = self._channel_to_mask(channel)
            if MAGICK_VERSION_NUMBER < 0x700:
                r = library.MagickUnsharpMaskImageChannel(
                    self.wand, channel_ch, radius, sigma, amount, threshold
                )
            else:  # pragma: no cover
                mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
                r = library.MagickUnsharpMaskImage(self.wand, radius, sigma,
                                                   amount, threshold)
                library.MagickSetImageChannelMask(self.wand, mask)
        return r

    @manipulative
    @trap_exception
    def vignette(self, radius=0.0, sigma=0.0, x=0, y=0):
        """Creates a soft vignette style effect on the image.

        :see: Example of :ref:`vignette`.

        :param radius: the radius of the Gaussian blur effect.
        :type radius: :class:`numbers.Real`
        :param sigma: the standard deviation of the Gaussian effect.
        :type sigma: :class:`numbers.Real`
        :param x: Number of pixels to offset inward from the top & bottom of
                  the image before drawing effect.
        :type x: :class:`numbers.Integral`
        :param y: Number of pixels to offset inward from the left & right of
                  the image before drawing effect.
        :type y: :class:`numbers.Integral`

        .. versionadded:: 0.5.2
        """
        assertions.assert_real(radius=radius, sigma=sigma)
        return library.MagickVignetteImage(self.wand, radius, sigma, x, y)

    @manipulative
    def watermark(self, image, transparency=0.0, left=0, top=0):
        """Transparentized the supplied ``image`` and places it over the
        current image, with the top left corner of ``image`` at coordinates
        ``left``, ``top`` of the current image.  The dimensions of the
        current image are not changed.

        :param image: the image placed over the current image
        :type image: :class:`wand.image.Image`
        :param transparency: the percentage fade that should be performed on
                             the image, from 0.0 to 1.0
        :type transparency: :class:`numbers.Real`
        :param left: the x-coordinate where `image` will be placed
        :type left: :class:`numbers.Integral`
        :param top: the y-coordinate where `image` will be placed
        :type top: :class:`numbers.Integral`

        .. versionadded:: 0.2.0

        """
        with image.clone() as watermark_image:
            watermark_image.transparentize(transparency)
            watermark_image.clamp()
            self.composite(watermark_image, left=left, top=top)
        self.raise_exception()

    @manipulative
    @trap_exception
    def wave(self, amplitude=0.0, wave_length=0.0, method='undefined'):
        """Creates a ripple effect within the image.

        :see: Example of :ref:`wave`.

        :param amplitude: height of wave form.
        :type amplitude: :class:`numbers.Real`
        :param wave_length: width of wave form.
        :type wave_length: :class:`numbers.Real`
        :param method: pixel interpolation method. Only available with
                       ImageMagick-7. See :const:`PIXEL_INTERPOLATE_METHODS`
        :type method: :class:`str`

        .. versionadded:: 0.5.2
        """
        assertions.assert_real(amplitude=amplitude, wave_length=wave_length)
        assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
                                  'wand.image.PIXEL_INTERPOLATE_METHODS',
                                  method=method)
        if MAGICK_VERSION_NUMBER < 0x700:
            r = library.MagickWaveImage(self.wand, amplitude, wave_length)
        else:  # pragma: no cover
            method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
            r = library.MagickWaveImage(self.wand, amplitude, wave_length,
                                        method_idx)
        return r

    @manipulative
    @trap_exception
    def wavelet_denoise(self, threshold=0.0, softness=0.0):
        """Removes noise by applying a `wavelet transform`_.

        .. _`wavelet transform`:
           https://en.wikipedia.org/wiki/Wavelet_transform

        .. warning::

            This class method is only available with ImageMagick 7.0.8-41, or
            greater.

        :see: Example of :ref:`wavelet_denoise`.

        :param threshold: Smoothing limit.
        :type threshold: :class:`numbers.Real`
        :param softness: Attenuate of the smoothing threshold.
        :type softness: :class:`numbers.Real`
        :raises WandLibraryVersionError: If system's version of ImageMagick
                                         does not support this method.

        .. versionadded:: 0.5.5
        """
        if library.MagickWaveletDenoiseImage is None:
            msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
            raise WandLibraryVersionError(msg)
        assertions.assert_real(threshold=threshold, softness=softness)
        if 0.0 < threshold <= 1.0:
            threshold *= self.quantum_range
        if 0.0 < softness <= 1.0:
            softness *= self.quantum_range
        return library.MagickWaveletDenoiseImage(self.wand, threshold,
                                                 softness)

    @manipulative
    @trap_exception
    def white_balance(self):
        """Uses LAB colorspace to apply a white balance to the image.

        .. note::

            Requires ImageMagick-7.0.10-37 or later.

        .. versionadded:: 0.6.4
        """
        msg = 'Requires ImageMagick-7.0.10-37, or later.'
        if MAGICK_VERSION_NUMBER < 0x70A:
            raise WandLibraryVersionError(msg)
        elif library.MagickWhiteBalanceImage is None:
            raise WandLibraryVersionError(msg)
        return library.MagickWhiteBalanceImage(self.wand)

    @manipulative
    @trap_exception
    def white_threshold(self, threshold):
        """Forces all pixels above a given color as white. Leaves pixels
        below threshold unaltered.

        :param threshold: Color to be referenced as a threshold.
        :type threshold: :class:`Color`

        .. versionadded:: 0.5.2
        """
        if isinstance(threshold, str):
            threshold = Color(threshold)
        assertions.assert_color(threshold=threshold)
        with threshold:
            r = library.MagickWhiteThresholdImage(self.wand,
                                                  threshold.resource)
        return r

    @trap_exception
    def write_mask(self, clip_mask=None):
        """Sets the write mask which prevents pixel-value updates to the image.
        Call this method with a ``None`` argument to clear any previously set
        masks.

        .. warning::
           This method is only available with ImageMagick-7.

        :param clip_mask: Image to reference as blend mask.
        :type clip_mask: :class:`BaseImage`

        .. versionadded:: 0.5.7
        """
        r = False
        WritePixelMask = 0x000002
        if library.MagickSetImageMask is None:
            raise WandLibraryVersionError('Method requires ImageMagick-7.')
        else:  # pragma: no cover
            if clip_mask is None:
                w, h = self.size
                with Image(width=w, height=h, pseudo='xc:none') as clear:
                    r = library.MagickSetImageMask(self.wand,
                                                   WritePixelMask,
                                                   clear.wand)
            elif isinstance(clip_mask, BaseImage):
                r = library.MagickSetImageMask(self.wand, WritePixelMask,
                                               clip_mask.wand)
        return r


class Image(BaseImage):
    """An image object.

    :param image: makes an exact copy of the ``image``
    :type image: :class:`Image`
    :param blob: opens an image of the ``blob`` byte array
    :type blob: :class:`bytes`
    :param file: opens an image of the ``file`` object
    :type file: file object
    :param filename: opens an image of the ``filename`` string. Additional
                     :ref:`read_mods` are supported.
    :type filename: :class:`str`
    :param format: forces filename to  buffer. ``format`` to help
                   ImageMagick detect the file format. Used only in
                   ``blob`` or ``file`` cases
    :type format: :class:`str`
    :param width: the width of new blank image or an image loaded from raw
                  data.
    :type width: :class:`numbers.Integral`
    :param height: the height of new blank image or an image loaded from
                   raw data.
    :type height: :class:`numbers.Integral`
    :param depth: the depth used when loading raw data.
    :type depth: :class:`numbers.Integral`
    :param background: an optional background color.
                       default is transparent
    :type background: :class:`wand.color.Color`
    :param resolution: set a resolution value (dpi),
                       useful for vectorial formats (like pdf)
    :type resolution: :class:`collections.abc.Sequence`,
                      :Class:`numbers.Integral`
    :param colorspace: sets the stack's default colorspace value before
                       reading any images.
                       See :const:`COLORSPACE_TYPES`.
    :type colorspace: :class:`str`,
    :param units: paired with ``resolution`` for defining an image's pixel
                  density. See :const:`UNIT_TYPES`.
    :type units: :class:`str`

    .. versionadded:: 0.1.5
       The ``file`` parameter.

    .. versionadded:: 0.1.1
       The ``blob`` parameter.

    .. versionadded:: 0.2.1
       The ``format`` parameter.

    .. versionadded:: 0.2.2
       The ``width``, ``height``, ``background`` parameters.

    .. versionadded:: 0.3.0
       The ``resolution`` parameter.

    .. versionadded:: 0.4.2
       The ``depth`` parameter.

    .. versionchanged:: 0.4.2
       The ``depth``, ``width`` and ``height`` parameters can be used
       with the ``filename``, ``file`` and ``blob`` parameters to load
       raw pixel data.

    .. versionadded:: 0.5.0
       The ``pseudo`` parameter.

    .. versionchanged:: 0.5.4
       Read constructor no longer sets "transparent" background by default.
       Use the ``background`` parameter to specify canvas color when reading
       in image.

    .. versionchanged:: 0.5.7
       Added the ``colorspace`` & ``units`` parameter.

    .. versionchanged:: 0.6.3
       Added ``sampling_factors`` parameter for working with YUV streams.

    .. describe:: [left:right, top:bottom]

       Crops the image by its ``left``, ``right``, ``top`` and ``bottom``,
       and then returns the cropped one. ::

           with img[100:200, 150:300] as cropped:
               # manipulated the cropped image
               pass

       Like other subscriptable objects, default is 0 or its width/height::

           img[:, :]        #--> just clone
           img[:100, 200:]  #--> equivalent to img[0:100, 200:img.height]

       Negative integers count from the end (width/height)::

           img[-70:-50, -20:-10]
           #--> equivalent to img[width-70:width-50, height-20:height-10]

       :returns: the cropped image
       :rtype: :class:`Image`

       .. versionadded:: 0.1.2

    """

    #: (:class:`ArtifactTree`) A dict mapping to image artifacts.
    #: Similar to :attr:`metadata`, but used to alter behavior of various
    #: internal operations.
    #:
    #: .. versionadded:: 0.5.0
    artifacts = None

    #: (:class:`ChannelImageDict`) The mapping of separated channels
    #: from the image. ::
    #:
    #:     with image.channel_images['red'] as red_image:
    #:         display(red_image)
    channel_images = None

    #: (:class:`ChannelDepthDict`) The mapping of channels to their depth.
    #: Read only.
    #:
    #: .. versionadded:: 0.3.0
    channel_depths = None

    #: (:class:`Metadata`) The metadata mapping of the image.  Read only.
    #:
    #: .. versionadded:: 0.3.0
    metadata = None

    #: (:class:`ProfileDict`) The mapping of image profiles.
    #:
    #: .. versionadded:: 0.5.1
    profiles = None

    def __init__(self, image=None, blob=None, file=None, filename=None,
                 pseudo=None, background=None, colorspace=None, depth=None,
                 extract=None, format=None, height=None, interlace=None,
                 resolution=None, sampling_factors=None, units=None,
                 width=None):
        new_args = width, height, background, depth
        open_args = blob, file, filename
        if any(a is not None for a in new_args) and image is not None:
            raise TypeError("blank image parameters can't be used with image "
                            'parameter')
        if sum(a is not None for a in open_args + (image,)) > 1:
            raise TypeError(', '.join(open_args) +
                            ' and image parameters are exclusive each other; '
                            'use only one at once')
        with self.allocate():
            if image is None:
                wand = library.NewMagickWand()
                super().__init__(wand)
            if image is not None:
                if not isinstance(image, BaseImage):
                    raise TypeError('image must be a wand.image.Image '
                                    'instance, not ' + repr(image))
                wand = library.CloneMagickWand(image.wand)
                super().__init__(wand)
            elif any(a is not None for a in open_args):
                self._preamble_read(
                    background=background, colorspace=colorspace, depth=depth,
                    extract=extract, format=format, height=height,
                    interlace=interlace, resolution=resolution,
                    sampling_factors=sampling_factors, width=width
                )
                if file is not None:
                    self.read(file=file)
                elif blob is not None:
                    self.read(blob=blob)
                elif filename is not None:
                    self.read(filename=filename)
                # clear the wand format, otherwise any subsequent call to
                # MagickGetImageBlob will silently change the image to this
                # format again.
                library.MagickSetFormat(self.wand, b'')
            elif width is not None and height is not None:
                if pseudo is None:
                    self.blank(width, height, background)
                else:
                    self.pseudo(width, height, pseudo)
                if depth:
                    r = library.MagickSetImageDepth(self.wand, depth)
                    if not r:
                        raise self.raise_exception()
            if units is not None:
                self.units = units
            self.metadata = Metadata(self)
            self.artifacts = ArtifactTree(self)
            from .sequence import Sequence
            self.sequence = Sequence(self)
            self.profiles = ProfileDict(self)
        self.raise_exception()

    def __repr__(self):
        return super().__repr__(
            extra_format=' {self.format!r} ({self.width}x{self.height})'
        )

    def _preamble_read(self, background=None, colorspace=None, depth=None,
                       extract=None, format=None, height=None, interlace=None,
                       resolution=None, sampling_factors=None, units=None,
                       width=None):
        """Set-up MagickWand properties before reading an image file. The
        properties are unique to the image decoder.

        :param background: Defines the default background color.
        :type background: :class:`Color`, :class:`str`
        :param colorspace: Defines what colorspace the decoder should operate
                           in. See :const:`COLORSPACE_TYPES`.
        :type colorspace: :class:`str`
        :param depth: Bits per color sample.
        :type depth: :class:`numbers.Integral`
        :param extract: Only decode a sub-region of the image.
        :type extract: :class:`str`
        :param format: Defines the decoder image format.
        :type format: :class:`str`
        :param height: Defines how high a blank canvas should be. Only used if
                       ``width`` is also defined.
        :type height: :class:`numbers.Integral`
        :param interlace: Defines the interlacing scheme for raw data streams.
                          See :const:`INTERLACE_TYPES`.
        :type interlace: :class:`str`
        :param resolution: Defines the pixel density of a scalable formats.
                           PDF & SVG as examples.
        :type resolution: :class:`collections.abc.Sequence`,
                          :class:`numbers.Integral`
        :param sampling_factors: Defines how a YUV might be upsampled.
        :type sampling_factors: :class:`collections.abc.Sequence`,
                                :class:`str`
        :param units: Unused.
        :type units: :class:`numbers.Integral`
        :param width: Defines how wide a blank canvas should be. Only used if
                      ``height`` is also defined.
        :type width: :class:`numbers.Intragal`

        .. versionadded:: 0.6.3
        """
        if background:
            if isinstance(background, str):
                background = Color(background)
            assertions.assert_color(background=background)
            with background:
                library.MagickSetBackgroundColor(self.wand,
                                                 background.resource)
        if colorspace is not None:
            assertions.string_in_list(
                COLORSPACE_TYPES,
                'wand.image.COLORSPACE_TYPES',
                colorspace=colorspace
            )
            colorspace_idx = COLORSPACE_TYPES.index(colorspace)
            library.MagickSetColorspace(self.wand, colorspace_idx)
        if depth is not None:
            assertions.assert_counting_number(depth=depth)
            library.MagickSetDepth(self.wand, depth)
        if extract is not None:
            assertions.assert_string(extract=extract)
            library.MagickSetExtract(self.wand, binary(extract))
        if format is not None:
            assertions.assert_string(format=format)
            library.MagickSetFormat(self.wand, binary(format))
            library.MagickSetFilename(self.wand, b'buffer.' + binary(format))
        if interlace is not None:
            assertions.string_in_list(
                INTERLACE_TYPES,
                'wand.image.INTERLACE_TYPES',
                interlace=interlace
            )
            c_interlace = INTERLACE_TYPES.index(interlace)
            library.MagickSetInterlaceScheme(self.wand, c_interlace)
        if resolution is not None:
            if (isinstance(resolution, abc.Sequence) and
                    len(resolution) == 2):
                library.MagickSetResolution(self.wand, *resolution)
            elif isinstance(resolution, numbers.Real):
                library.MagickSetResolution(self.wand, resolution, resolution)
            else:
                raise TypeError('resolution must be a (x, y) pair or an '
                                'real number of the same x/y')
        if sampling_factors is not None:
            self.sampling_factors = sampling_factors
        if width is not None and height is not None:
            assertions.assert_counting_number(width=width, height=height)
            library.MagickSetSize(self.wand, width, height)

    def _repr_png_(self):
        with self.convert('png') as cloned:
            return cloned.make_blob()

    @classmethod
    def from_array(cls, array, channel_map=None, storage=None):
        """Create an image instance from a :mod:`numpy` array, or any other
        datatype that implements `__array_interface__`__ protocol.

        .. code::

            import numpy
            from wand.image import Image

            matrix = numpy.random.rand(100, 100, 3)
            with Image.from_array(matrix) as img:
                img.save(filename='noise.png')

        Use the optional ``channel_map`` & ``storage`` arguments to specify
        the order of color channels & data size. If ``channel_map`` is omitted,
        this method will will guess ``"RGB"``, or ``"CMYK"`` based on
        array shape. If ``storage`` is omitted, this method will reference the
        array's ``typestr`` value, and raise a :class:`ValueError` if
        storage-type can not be mapped.

        Float values must be normalized between `0.0` and `1.0`, and signed
        integers should be converted to unsigned values between `0` and
        max value of type.

        Instances of :class:`Image` can also be exported to numpy arrays::

            with Image(filename='rose:') as img:
                matrix = numpy.array(img)

        __ https://docs.scipy.org/doc/numpy/reference/arrays.interface.html

        :param array: Numpy array of pixel values.
        :type array: :class:`numpy.array`
        :param channel_map: Color channel layout.
        :type channel_map: :class:`str`
        :param storage: Datatype per pixel part.
        :type storage: :class:`str`
        :returns: New instance of an image.
        :rtype: :class:`~wand.image.Image`

        .. versionadded:: 0.5.3
        .. versionchanged:: 0.6.0
           Input ``array`` now expects the :attr:`shape` property to be defined
           as ```( 'height', 'width', 'channels' )```.
        """
        arr_itr = array.__array_interface__
        typestr = arr_itr['typestr']  # Required by interface.
        shape = arr_itr['shape']  # Required by interface.
        if storage is None:
            # Attempt to guess storage
            storage_map = dict(u1='char', i1='char',
                               u2='short', i2='short',
                               u4='integer', i4='integer',
                               u8='long', i8='integer',
                               f4='float', f8='double')
            for token in storage_map:
                if token in typestr:
                    storage = storage_map[token]
                    break
            if storage is None:
                raise ValueError('Unable to determine storage type.')
        if channel_map is None:
            # Attempt to guess channel map
            if len(shape) == 3:
                if shape[2] < 5:
                    channel_map = 'RGBA'[0:shape[2]]
                else:
                    channel_map = 'CMYKA'[0:shape[2]]
            else:
                channel_map = 'R'
        strides = arr_itr.get('strides', None)
        if hasattr(array, 'ctypes') and strides is None:
            data_ptr = array.ctypes.data_as(ctypes.c_void_p)
        elif hasattr(array, 'tobytes'):
            data_ptr = array.tobytes()
        elif hasattr(array, 'tostring'):
            data_ptr = array.tostring()
        else:
            data_ptr, _ = arr_itr.get('data')
        storage_idx = STORAGE_TYPES.index(storage)
        height, width = shape[:2]
        genesis()
        wand = library.NewMagickWand()
        instance = cls(BaseImage(wand))
        r = library.MagickConstituteImage(instance.wand,
                                          width,
                                          height,
                                          binary(channel_map),
                                          storage_idx,
                                          data_ptr)
        if not r:
            instance.raise_exception(cls)
        return instance

    @classmethod
    def ping(cls, file=None, filename=None, blob=None, **kwargs):
        """Ping image header into Image() object, but without any pixel data.
        This is useful for inspecting image meta-data without decoding the
        whole image.

        :param blob: reads an image from the ``blob`` byte array
        :type blob: :class:`bytes`
        :param file: reads an image from the ``file`` object
        :type file: file object
        :param filename: reads an image from the ``filename`` string
        :type filename: :class:`str`
        :param resolution: set a resolution value (DPI),
                           useful for vector formats (like PDF)
        :type resolution: :class:`collections.abc.Sequence`,
                          :class:`numbers.Integral`
        :param format: suggest image file format when reading from a ``blob``,
                       or ``file`` property.
        :type format: :class:`str`

        .. versionadded:: 0.5.6

        """
        r = None
        instance = cls()
        instance._preamble_read(**kwargs)
        if file is not None:
            is_fd = (isinstance(file, IOBase),
                     hasattr(libc, 'fdopen'),
                     callable(getattr(file, 'fileno', None)),
                     hasattr(file, 'mode'))
            if all(is_fd):
                fd = libc.fdopen(file.fileno(), binary(file.mode))
                r = library.MagickPingImageFile(instance.wand, fd)
            elif not callable(getattr(file, 'read', None)):
                raise TypeError('file must be a readable file object'
                                ', but the given object does not '
                                'have read() method')
            else:
                blob = file.read()
                file = None
        if blob is not None:
            if not isinstance(blob, abc.Iterable):
                raise TypeError('blob must be iterable, not ' +
                                repr(blob))
            if not isinstance(blob, bytes):
                blob = b''.join(blob)
            r = library.MagickPingImageBlob(instance.wand, blob, len(blob))
        elif filename is not None:
            filename = encode_filename(filename)
            r = library.MagickPingImage(instance.wand, filename)
        if not r:
            instance.raise_exception()
            msg = ('MagickPingImage returns false, but did raise ImageMagick '
                   'exception. This can occur when a delegate is missing, or '
                   'returns EXIT_SUCCESS without generating a raster.')
            raise WandRuntimeError(msg)
        else:
            units = kwargs.get('units')
            if units is not None:
                instance.units = units
            instance.metadata = Metadata(instance)
            instance.artifacts = ArtifactTree(instance)
            from .sequence import Sequence
            instance.sequence = Sequence(instance)
            instance.profiles = ProfileDict(instance)
        return instance

    @classmethod
    def stereogram(cls, left, right):
        """Create a new stereogram image from two existing images.

        :see: Example of :ref:`stereogram`.

        :param left: Left-eye image.
        :type left: :class:`wand.image.Image`
        :param right: Right-eye image.
        :type right: :class:`wand.image.Image`

        .. versionadded:: 0.5.4
        """
        if not isinstance(left, BaseImage):
            raise TypeError('Left image must be in instance of '
                            'wand.image.Image, not ' + repr(left))
        if not isinstance(right, BaseImage):
            raise TypeError('Right image must be in instance of '
                            'wand.image.Image, not ' + repr(right))
        wand = library.MagickStereoImage(left.wand, right.wand)
        if not wand:  # pragma: no cover
            left.raise_exception()
        return cls(BaseImage(wand))

    @property
    def animation(self):
        is_gif = self.mimetype in ('image/gif', 'image/x-gif')
        frames = library.MagickGetNumberImages(self.wand)
        return is_gif and frames > 1

    @property
    def mimetype(self):
        """(:class:`str`) The MIME type of the image
        e.g. ``'image/jpeg'``, ``'image/png'``.

        .. versionadded:: 0.1.7

        """
        mtype = None
        rp = libmagick.MagickToMime(binary(self.format))
        if rp:
            mtype = text(ctypes.string_at(rp))
            rp = libmagick.DestroyString(rp)
        return mtype

    def blank(self, width, height, background=None):
        """Creates blank image.

        :param width: the width of new blank image.
        :type width: :class:`numbers.Integral`
        :param height: the height of new blank image.
        :type height: :class:`numbers.Integral`
        :param background: an optional background color.
                           default is transparent
        :type background: :class:`wand.color.Color`
        :returns: blank image
        :rtype: :class:`Image`

        .. versionadded:: 0.3.0

        """
        assertions.assert_counting_number(width=width, height=height)
        if background is None:
            background = Color('transparent')
        elif isinstance(background, str):
            background = Color(background)
        assertions.assert_color(background=background)
        with background:
            r = library.MagickNewImage(self.wand, width, height,
                                       background.resource)
            if not r:
                self.raise_exception()
        return self

    def channel_fx(self, expression):
        """Generate a new image by applying the Channel-FX expression. Useful
        for exchanging, copying, or extracting image channels.

        For example::

            # Swap the red and green channels of an image.
            with Image(filename='input.png') as img:
                with img.channel_fx('red <=> green') as swapped:
                    swapped.save(filename='swapped_output.png')

        .. seealso::

            `Channel FX`__ for more details.

            __ https://imagemagick.org/script/command-line-options.php\
               #channel-fx

        .. warning::

            This class method is only available with ImageMagick 7.x.x, or
            greater.

        :param expression: The Channel-FX expression to apply.
        :type expression: :class:`str`
        :returns: A new image with Channel-FX expression applied.
        :rtype: :class:`Image`

        .. versionadded:: 0.7.0
        """
        if library.MagickChannelFxImage is None:  # pragma: no cover
            raise AttributeError('MagickChannelFxImage method '
                                 'not available on system.')
        assertions.assert_string(expression=expression)
        c_exp = binary(expression)
        new_wand = library.MagickChannelFxImage(self.wand, c_exp)
        if not new_wand:
            self.raise_exception()
        return Image(image=BaseImage(new_wand))

    def clear(self):
        """Clears resources associated with the image, leaving the image blank,
        and ready to be used with new image.

        .. versionadded:: 0.3.0

        """
        library.ClearMagickWand(self.wand)

    def close(self):
        """Closes the image explicitly. If you use the image object in
        :keyword:`with` statement, it was called implicitly so don't have to
        call it.

        .. note::

           It has the same functionality of :attr:`destroy()` method.

        """
        self.destroy()

    def compare_layers(self, method):
        """Generates new images showing the delta pixels between
        layers. Similar pixels are converted to transparent.
        Useful for debugging complex animations. ::

            with img.compare_layers('compareany') as delta:
                delta.save(filename='framediff_%02d.png')

        .. note::

            May not work as expected if animations are already
            optimized.

        :param method: Can be ``'compareany'``,
                       ``'compareclear'``, or ``'compareoverlay'``
        :type method: :class:`str`
        :returns: new image stack.
        :rtype: :class:`Image`

        .. versionadded:: 0.5.0
        """
        if not isinstance(method, str):
            raise TypeError('method must be a string from IMAGE_LAYER_METHOD, '
                            'not ' + repr(method))
        if method not in ('compareany', 'compareclear', 'compareoverlay'):
            raise ValueError('method can only be \'compareany\', '
                             '\'compareclear\', or \'compareoverlay\'')
        r = None
        m = IMAGE_LAYER_METHOD.index(method)
        if MAGICK_VERSION_NUMBER >= 0x700:  # pragma: no cover
            r = library.MagickCompareImagesLayers(self.wand, m)
        elif library.MagickCompareImageLayers:
            r = library.MagickCompareImageLayers(self.wand, m)
        elif library.MagickCompareImagesLayers:  # pragma: no cover
            r = library.MagickCompareImagesLayers(self.wand, m)
        else:
            raise AttributeError('MagickCompareImageLayers method '
                                 'not available on system.')
        if not r:
            self.raise_exception()
        return Image(image=BaseImage(r))

    def convert(self, format):
        """Converts the image format with the original image maintained.
        It returns a converted image instance which is new. ::

            with img.convert('png') as converted:
                converted.save(filename='converted.png')

        :param format: image format to convert to
        :type format: :class:`str`
        :returns: a converted image
        :rtype: :class:`Image`
        :raises ValueError: when the given ``format`` is unsupported

        .. versionadded:: 0.1.6

        .. versionchanged:: 0.6.11
           Call :c:func:`MagickSetFormat` method after
           :c:func:`MagickSetImageFormat`. This will ensure image info, magick,
           and filename properties are aligned.
        """
        cloned = self.clone()
        cloned.format = format
        library.MagickSetFormat(cloned.wand,
                                binary(format.strip().upper()))
        return cloned

    def data_url(self):
        """Generate a base64 `data-url`_ string from the loaded image.
        Useful for converting small graphics into ASCII strings for HTML/CSS
        web development.

        .. _data-url: https://en.wikipedia.org/wiki/Data_URI_scheme

        :returns: a data-url formatted string.
        :rtype: :class:`str`

        .. versionadded:: 0.6.3
        """
        from base64 import b64encode
        mime_type = self.mimetype
        base_bytes = b64encode(self.make_blob())
        return "data:{0};base64,{1}".format(mime_type, base_bytes.decode())

    @trap_exception
    def image_add(self, image):
        """Copies a given image on to the image stack. By default, the added
        image will be append at the end of the stack, or immediately after
        the current image iterator defined by :meth:`~BaseImage.iterator_set`.
        Use :meth:`~BaseImage.iterator_reset` before calling this method to
        insert the new image before existing images on the stack.

        :param image: raster to add.
        :type image: :class:`Image`

        .. versionadded:: 0.6.7
        """
        if not isinstance(image, Image):
            raise TypeError('image must be instance of wand.image.Image')
        return library.MagickAddImage(self.wand, image.wand)

    def image_get(self):
        """Generate & return a clone of a single image at the current
        image-stack index.

        .. versionadded:: 0.6.7
        """
        r = library.MagickGetImage(self.wand)
        if not r:
            self.raise_exception()
            return None  # noqa - Safety if exception isn't thrown.
        return Image(BaseImage(r))

    @trap_exception
    def image_remove(self):
        """Remove an image from the image-stack at the current index.

        .. versionadded:: 0.6.7
        """
        return library.MagickRemoveImage(self.wand)

    @trap_exception
    def image_set(self, image):
        """Overwrite current image on the image-stack with given image.

        :param image: Wand instance of images to write to stack.
        :type image: :class:`wand.image.Image`

        .. versionadded:: 0.6.7
        """
        if not isinstance(image, Image):
            raise TypeError('image must be an instance of wand.image.Image,',
                            ' not ' + repr(image))
        return library.MagickSetImage(self.wand, image.wand)

    @trap_exception
    def image_swap(self, i, j):
        """Swap two images on the image-stack.

        :param i: image index to replace with ``j``
        :type i: :class:`numbers.Integral`
        :param j: image index to replace with ``i``
        :type j: :class:`numbers.Integral`

        .. versionadded:: 0.6.7
        """
        assertions.assert_integer(i=i)
        assertions.assert_integer(j=j)
        op = self.iterator_get()
        self.iterator_set(i)
        with self.image_get() as a:
            self.iterator_set(j)
            with self.image_get() as b:
                self.image_set(a)
                self.iterator_set(i)
                self.image_set(b)
        self.iterator_set(op)

    def make_blob(self, format=None):
        """Makes the binary string of the image.

        :param format: the image format to write e.g. ``'png'``, ``'jpeg'``.
                       it is omittable
        :type format: :class:`str`
        :returns: a blob (bytes) string
        :rtype: :class:`bytes`
        :raises ValueError: when ``format`` is invalid

        .. versionchanged:: 0.1.6
           Removed a side effect that changes the image :attr:`format`
           silently.

        .. versionadded:: 0.1.5
           The ``format`` parameter became optional.

        .. versionadded:: 0.1.1

        """
        if format is not None:
            with self.convert(format) as converted:
                return converted.make_blob()
        library.MagickResetIterator(self.wand)
        length = ctypes.c_size_t()
        blob_p = None
        if library.MagickGetNumberImages(self.wand) > 1:
            blob_p = library.MagickGetImagesBlob(self.wand,
                                                 ctypes.byref(length))
        else:
            blob_p = library.MagickGetImageBlob(self.wand,
                                                ctypes.byref(length))
        if blob_p and length.value:
            blob = ctypes.string_at(blob_p, length.value)
            blob_p = library.MagickRelinquishMemory(blob_p)
            return blob
        else:  # pragma: no cover
            self.raise_exception()

    @trap_exception
    def montage(self, font=None, tile=None, thumbnail=None, mode='unframe',
                frame=None):
        """Generates a new image containing thumbnails if each previous image
        read. ::

            with Image() as img:
                for file_path in ['first.png', 'second.png', 'third.png']:
                    with Image(filename=file_path) as item:
                        img.options['label'] = file_path
                        img.image_add(item)
                style = Font('monospace', 24, 'green')
                img.montage(font=style, tile='3x1', thumbnail='15x15')
                img.save(filename='montage.png')

        :param font: Define font style to use when labeling each thumbnail.
                     Thumbnail labeling will only be rendered if ``'label'``
                     value in :attr:`options` dict is defined.
        :type font: :class:`~wand.font.Font`
        :param tile: The number of thunbnails per rows & column on a page.
                     Example: ``"6x4"``.
        :type tile: :class:`str`
        :param thumbnail: Preferred image size. Montage will attempt to
                          generate a thumbnail to match the geometry. This
                          can also define the border size on each thumbnail.
                          Example: ``"120x120x+4+3>"``.
        :type thumbnail: :class:`str`
        :param mode: Which effect to render. Options include ``"frame"``,
                     ``"unframe"``, and ``"concatenate"``. Default ``"frame"``.
        :type mode: :class:`str`
        :param frame: Define ornamental boarder around each thrumbnail.
                      The color of the frame is defined by the image's matte
                      color. Example: ``"15x15+3+3"``.
        :type frame: :class:`str`

        .. versionadded:: 0.6.8
        """
        if font is not None:
            if not isinstance(font, Font):
                msg = 'font must be an instance of wand.font.Font'
                raise TypeError(msg)
        else:
            font = Font('helvetica', 16, 'black')
        if tile is not None:
            assertions.assert_string(tile=tile)
            tile = binary(tile)
        if thumbnail is not None:
            assertions.assert_string(thumbnail=thumbnail)
            thumbnail = binary(thumbnail)
        assertions.in_list(MONTAGE_MODES,
                           'wand.image.MONTAGE_MODES',
                           mode=mode)
        mode_idx = MONTAGE_MODES.index(mode)
        if frame is not None:
            assertions.assert_string(frame=frame)
            frame = binary(frame)
        ctx_ptr = library.NewDrawingWand()
        if font.path:
            library.DrawSetFont(ctx_ptr, binary(font.path))
            library.DrawSetFontFamily(ctx_ptr, binary(font.path))
        if font.size:
            library.DrawSetFontSize(ctx_ptr, font.size)
        if font.color:
            with font.color:
                library.DrawSetFillColor(ctx_ptr, font.color.resource)
        if font.stroke_color:
            with font.stroke_color:
                library.DrawSetStrokeColor(ctx_ptr, font.stroke_color.resource)
        new_wand = library.MagickMontageImage(self.wand, ctx_ptr, tile,
                                              thumbnail, mode_idx, frame)
        ctx_ptr = library.DestroyDrawingWand(ctx_ptr)
        ok = bool(new_wand)
        if ok:
            self.wand = new_wand
            self.reset_sequence()
        return ok

    def morph(self, number_frames=5):
        """Creates a new image by linearly interpolating pixels from the image
        stack.

        .. code::

            from wand.image import Image

            with Image() as img:
                img.read(filename='start.png')
                img.read(filename='end.png')
                with img.morph(32) as morph:
                    morph.save(filename='animation.gif')


        :param number_frames: The number of in-between images to generate.
        :type number_frames: :class:`numbers.Integral`
        :returns: New :class:`Image` instance.
        :rtype: :class:`Image`

        .. versionadded:: 0.7.0
        """
        assertions.assert_counting_number(number_frames=number_frames)
        self.iterator_reset()
        r = library.MagickMorphImages(self.wand, number_frames)
        if r is None:
            self.raise_exception()
        return self.__class__(BaseImage(r))

    def pseudo(self, width, height, pseudo='xc:'):
        """Creates a new image from ImageMagick's internal protocol coders.

        :param width: Total columns of the new image.
        :type width: :class:`numbers.Integral`
        :param height: Total rows of the new image.
        :type height: :class:`numbers.Integral`
        :param pseudo: The protocol & arguments for the pseudo image.
        :type pseudo: :class:`str`

        .. versionadded:: 0.5.0
        """
        assertions.assert_counting_number(width=width, height=height)
        assertions.assert_string(pseudo=pseudo)
        r = library.MagickSetSize(self.wand, width, height)
        if not r:
            self.raise_exception()
        r = library.MagickReadImage(self.wand, encode_filename(pseudo))
        if not r:
            self.raise_exception()

    def read(self, file=None, filename=None, blob=None, background=None,
             colorspace=None, depth=None, extract=None, format=None,
             height=None, interlace=None, resolution=None,
             sampling_factors=None, units=None, width=None):
        """Read new image into Image() object.

        :param blob: reads an image from the ``blob`` byte array
        :type blob: :class:`bytes`
        :param file: reads an image from the ``file`` object
        :type file: file object
        :param filename: reads an image from the ``filename`` string.
                         Additional :ref:`read_mods` are supported.
        :type filename: :class:`str`
        :param background: set default background color.
        :type background: :class:`Color`, :class:`str`
        :param colorspace: set default colorspace.
                           See :const:`COLORSPACE_TYPES`.
        :type colorspace: :class:`str`
        :param depth: sets bits per color sample. Usually ``8``, or ``16``.
        :type depth: :class:`numbers.Integral`
        :param format: sets which image decoder to read with. Helpful when
                       reading ``blob`` data with ambiguous headers.
        :type format: :class:`str`
        :param height: used with ``width`` to define the canvas size. Useful
                       for reading image streams.
        :type height: :class:`numbers.Integral`
        :param interlace: Defines the interlacing scheme for raw data streams.
                          See :const:`INTERLACE_TYPES`.
        :type interlace: :class:`str`
        :param resolution: set a resolution value (DPI),
                           useful for vectorial formats (like PDF)
        :type resolution: :class:`collections.abc.Sequence`,
                          :class:`numbers.Integral`
        :param sampling_factors: set up/down stampling factors for YUV data
                                 stream. Usually ``"4:2:2"``
        :type sampling_factors: :class:`collections.abc.Sequence`,
                                :class:`str`
        :param units: used with ``resolution``, can either be
                     ``'pixelperinch'``, or ``'pixelpercentimeter'``.
        :type units: :class:`str`
        :param width: used with ``height`` to define the canvas size. Useful
                      for reading image streams.
        :type width: :class:`numbers.Integral`

        .. versionadded:: 0.3.0

        .. versionchanged:: 0.5.7
           Added ``units`` parameter.

        .. versionchanged:: 0.6.3
           Added, or documented, optional pre-read parameters:
           ``background``, ``colorspace``, ``depth``, ``format``, ``height``,
           ``interlace``, ``sampling_factors``, & ``width``.
        """
        r = None
        # Resolution must be set after image reading.
        self._preamble_read(
            background=background, colorspace=colorspace, depth=depth,
            extract=extract, format=format, height=height, interlace=interlace,
            resolution=resolution, sampling_factors=sampling_factors,
            width=width
        )
        if file is not None:
            is_fd = (isinstance(file, IOBase),
                     hasattr(libc, 'fdopen'),
                     callable(getattr(file, 'fileno', None)),
                     hasattr(file, 'mode'))
            if all(is_fd):
                fd = libc.fdopen(file.fileno(), binary(file.mode))
                r = library.MagickReadImageFile(self.wand, fd)
            elif not callable(getattr(file, 'read', None)):
                raise TypeError('file must be a readable file object'
                                ', but the given object does not '
                                'have read() method')
            else:
                blob = file.read()
                file = None
        if blob is not None:
            if not isinstance(blob, abc.Iterable):
                raise TypeError('blob must be iterable, not ' +
                                repr(blob))
            if not isinstance(blob, bytes):
                blob = b''.join(blob)
            r = library.MagickReadImageBlob(self.wand, blob, len(blob))
        elif filename is not None:
            filename = encode_filename(filename)
            r = library.MagickReadImage(self.wand, filename)
        if not r:
            self.raise_exception()
            msg = ('MagickReadImage returns false, but did not raise '
                   'ImageMagick  exception. This can occur when a delegate '
                   'is missing, or returns EXIT_SUCCESS without generating a '
                   'raster.')
            raise WandRuntimeError(msg)
        else:
            if units is not None:
                self.units = units

    def reset_sequence(self):
        """Remove any previously allocated :class:`~wand.sequence.SingleImage`
        instances in :attr:`sequence` attribute.

        .. versionadded:: 0.6.0
        """
        for instance in self.sequence.instances:
            if hasattr(instance, 'destroy'):
                instance.destroy()
        self.sequence.instances = []

    def save(self, file=None, filename=None, adjoin=True):
        """Saves the image into the ``file`` or ``filename``. It takes
        only one argument at a time.

        :param file: a file object to write to
        :type file: file object
        :param filename: a filename string to write to
        :type filename: :class:`str`
        :param adjoin: write all images to a single multi-image file. Only
                       available if file format supports frames, layers, & etc.
        :type adjoin: :class:`bool`

        .. versionadded:: 0.1.1

        .. versionchanged:: 0.1.5
           The ``file`` parameter was added.

        .. versionchanged:: 6.0.0
           The ``adjoin`` parameter was added.

        """
        if file is None and filename is None:
            raise TypeError('expected an argument')
        elif file is not None and filename is not None:
            raise TypeError('expected only one argument; but two passed')
        elif file is not None:
            is_fd = (isinstance(file, IOBase),
                     hasattr(libc, 'fdopen'),
                     callable(getattr(file, 'fileno', None)),
                     hasattr(file, 'mode'))
            if isinstance(file, str):
                raise TypeError('file must be a writable file object, '
                                'but {0!r} is a string; did you want '
                                '.save(filename={0!r})?'.format(file))
            elif all(is_fd):
                fd = libc.fdopen(file.fileno(), binary(file.mode))
                if library.MagickGetNumberImages(self.wand) > 1:
                    r = library.MagickWriteImagesFile(self.wand, fd)
                else:
                    r = library.MagickWriteImageFile(self.wand, fd)
                libc.fflush(fd)
                if not r:
                    self.raise_exception()
            elif not callable(getattr(file, 'write', None)):
                raise TypeError('file must be a writable file object, '
                                'but it does not have write() method: ' +
                                repr(file))
            else:
                file.write(self.make_blob())
        else:
            if not isinstance(filename, str):
                if not hasattr(filename, '__fspath__'):
                    raise TypeError('filename must be a string, not ' +
                                    repr(filename))
            filename = encode_filename(filename)
            if library.MagickGetNumberImages(self.wand) > 1:
                r = library.MagickWriteImages(self.wand, filename, adjoin)
            else:
                r = library.MagickWriteImage(self.wand, filename)
            if not r:
                self.raise_exception()


class Iterator(Resource, abc.Iterator):
    """Row iterator for :class:`Image`. It shouldn't be instantiated
    directly; instead, it can be acquired through :class:`Image` instance::

        assert isinstance(image, wand.image.Image)
        iterator = iter(image)

    It doesn't iterate every pixel, but rows. For example::

        for row in image:
            for col in row:
                assert isinstance(col, wand.color.Color)
                print(col)

    Every row is a :class:`collections.abc.Sequence` which consists of
    one or more :class:`wand.color.Color` values.

    :param image: the image to get an iterator
    :type image: :class:`Image`

    .. versionadded:: 0.1.3

    """

    c_is_resource = library.IsPixelIterator
    c_destroy_resource = library.DestroyPixelIterator
    c_get_exception = library.PixelGetIteratorException
    c_clear_exception = library.PixelClearIteratorException

    def __init__(self, image=None, iterator=None):
        if image is not None and iterator is not None:
            raise TypeError('it takes only one argument at a time')
        with self.allocate():
            if image is not None:
                if not isinstance(image, Image):
                    raise TypeError('expected a wand.image.Image instance, '
                                    'not ' + repr(image))
                self.resource = library.NewPixelIterator(image.wand)
                self.height = image.height
            else:
                if not isinstance(iterator, Iterator):
                    raise TypeError('expected a wand.image.Iterator instance, '
                                    'not ' + repr(iterator))
                self.resource = library.ClonePixelIterator(iterator.resource)
                self.height = iterator.height
        self.raise_exception()
        self.cursor = 0

    def __iter__(self):
        return self

    def seek(self, y):
        assertions.assert_unsigned_integer(seek=y)
        if y > self.height:
            raise ValueError('can not be greater than height')
        self.cursor = y
        if y == 0:
            library.PixelSetFirstIteratorRow(self.resource)
        else:
            if not library.PixelSetIteratorRow(self.resource, y - 1):
                self.raise_exception()

    def __next__(self, x=None):
        if self.cursor >= self.height:
            self.destroy()
            raise StopIteration()
        self.cursor += 1
        width = ctypes.c_size_t()
        pixels = library.PixelGetNextIteratorRow(self.resource,
                                                 ctypes.byref(width))
        if x is None:
            r_pixels = [None] * width.value
            for x in range(width.value):
                r_pixels[x] = Color.from_pixelwand(pixels[x])
            return r_pixels
        return Color.from_pixelwand(pixels[x]) if pixels else None

    def clone(self):
        """Clones the same iterator.

        """
        return type(self)(iterator=self)


class ImageProperty:
    """The mixin class to maintain a weak reference to the parent
    :class:`Image` object.

    .. versionadded:: 0.3.0

    """

    def __init__(self, image):
        if not isinstance(image, BaseImage):
            raise TypeError('expected a wand.image.BaseImage instance, '
                            'not ' + repr(image))
        self._image = weakref.ref(image)

    @property
    def image(self):
        """(:class:`Image`) The parent image.

        It ensures that the parent :class:`Image`, which is held in a weak
        reference, still exists.  Returns the dereferenced :class:`Image`
        if it does exist, or raises a :exc:`ClosedImageError` otherwise.

        :exc: `ClosedImageError` when the parent Image has been destroyed

        """
        # Dereference our weakref and check that the parent Image still exists
        image = self._image()
        if image is not None:
            return image
        raise ClosedImageError(
            'parent Image of {0!r} has been destroyed'.format(self)
        )


class OptionDict(ImageProperty, abc.MutableMapping):
    """Free-form mutable mapping of global internal settings.

    .. versionadded:: 0.3.0

    .. versionchanged:: 0.5.0
       Remove key check to :const:`OPTIONS`. Image properties are specific to
       vendor, and this library should not attempt to manage the 100+ options
       in a whitelist.
    """

    def __iter__(self):
        return iter(OPTIONS)

    def __len__(self):
        return len(OPTIONS)

    def __getitem__(self, key):
        assertions.assert_string(key=key)
        opt_str = b''
        opt_p = library.MagickGetOption(self.image.wand, binary(key))
        if opt_p:
            opt_str = text(ctypes.string_at(opt_p))
            opt_p = library.MagickRelinquishMemory(opt_p)
        else:
            raise KeyError(key)
        return opt_str

    def __setitem__(self, key, value):
        assertions.assert_string(key=key, value=value)
        image = self.image
        library.MagickSetOption(image.wand, binary(key), binary(value))

    def __delitem__(self, key):
        self[key] = ''


class Metadata(ImageProperty, abc.MutableMapping):
    """Class that implements dict-like read-only access to image metadata
    like EXIF or IPTC headers. Most WRITE encoders will ignore properties
    assigned here.

    :param image: an image instance
    :type image: :class:`Image`

    .. note::

       You don't have to use this by yourself.
       Use :attr:`Image.metadata` property instead.

    .. versionadded:: 0.3.0

    """

    def __init__(self, image):
        if not isinstance(image, Image):
            raise TypeError('expected a wand.image.Image instance, '
                            'not ' + repr(image))
        super().__init__(image)

    def __getitem__(self, k):
        """
        :param k: Metadata header name string.
        :type k: :class:`str`
        :returns: a header value string
        :rtype: :class:`str`
        """
        assertions.assert_string(key=k)
        image = self.image
        value = b''
        vp = library.MagickGetImageProperty(image.wand, binary(k))
        if vp:
            value = text(ctypes.string_at(vp))
            vp = library.MagickRelinquishMemory(vp)
        else:
            raise KeyError(k)
        return value

    def __setitem__(self, k, v):
        """
        :param k: Metadata header name string.
        :type k: :class:`str`
        :param v: Value to assign.
        :type v: :class:`str`

        .. versionadded: 0.5.0
        """
        assertions.assert_string(key=k, value=v)
        image = self.image
        r = library.MagickSetImageProperty(image.wand, binary(k), binary(v))
        if not r:
            image.raise_exception()
        return v

    def __delitem__(self, k):
        """
        :param k: Metadata header name string.
        :type k: :class:`str`

        .. versionadded: 0.5.0
        """
        assertions.assert_string(key=k)
        image = self.image
        r = library.MagickDeleteImageProperty(image.wand, binary(k))
        if not r:
            image.raise_exception()

    def __iter__(self):
        image = self.image
        num = ctypes.c_size_t()
        props_p = library.MagickGetImageProperties(image.wand, b'', num)
        props = [text(ctypes.string_at(props_p[i])) for i in range(num.value)]
        props_p = library.MagickRelinquishMemory(props_p)
        return iter(props)

    def __len__(self):
        image = self.image
        num = ctypes.c_size_t()
        props_p = library.MagickGetImageProperties(image.wand, b'', num)
        props_p = library.MagickRelinquishMemory(props_p)
        return num.value


class ArtifactTree(ImageProperty, abc.MutableMapping):
    """Splay tree to map image artifacts. Values defined here
    are intended to be used elseware, and will not be written
    to the encoded image.

    For example::

        # Omit timestamp from PNG file headers.
        with Image(filename='input.png') as img:
            img.artifacts['png:exclude-chunks'] = 'tIME'
            img.save(filename='output.png')

    :param image: an image instance
    :type image: :class:`Image`

    .. note::

       You don't have to use this by yourself.
       Use :attr:`Image.artifacts` property instead.

    .. versionadded:: 0.5.0
    """

    def __init__(self, image):
        if not isinstance(image, Image):
            raise TypeError('expected a wand.image.Image instance, '
                            'not ' + repr(image))
        super().__init__(image)

    def __getitem__(self, k):
        """
        :param k: Metadata header name string.
        :type k: :class:`str`
        :returns: a header value string
        :rtype: :class:`str`

        .. versionadded: 0.5.0
        """
        assertions.assert_string(key=k)
        image = self.image
        vs = b''
        vp = library.MagickGetImageArtifact(image.wand, binary(k))
        if vp:
            vs = text(ctypes.string_at(vp))
            vp = library.MagickRelinquishMemory(vp)
        if len(vs) < 1:
            vp = library.MagickGetImageProperty(image.wand, binary(k))
            if vp:
                vs = text(ctypes.string_at(vp))
                vp = library.MagickRelinquishMemory(vp)
            else:
                vs = None
        return vs

    def __setitem__(self, k, v):
        """
        :param k: Metadata header name string.
        :type k: :class:`str`
        :param v: Value to assign.
        :type v: :class:`str`

        .. versionadded: 0.5.0
        """
        assertions.assert_string(key=k, value=v)
        image = self.image
        r = library.MagickSetImageArtifact(image.wand, binary(k), binary(v))
        if not r:  # pragma: no cover
            image.raise_exception()
        return v

    def __delitem__(self, k):
        """
        :param k: Metadata header name string.
        :type k: :class:`str`

        .. versionadded: 0.5.0
        """
        assertions.assert_string(key=k)
        image = self.image
        r = library.MagickDeleteImageArtifact(image.wand, binary(k))
        if not r:  # pragma: no cover
            image.raise_exception()

    def __iter__(self):
        image = self.image
        num = ctypes.c_size_t(0)
        art_p = library.MagickGetImageArtifacts(image.wand, b'', num)
        props = [text(ctypes.string_at(art_p[i])) for i in range(num.value)]
        art_p = library.MagickRelinquishMemory(art_p)
        return iter(props)

    def __len__(self):
        image = self.image
        num = ctypes.c_size_t(0)
        art_p = library.MagickGetImageArtifacts(image.wand, b'', num)
        art_p = library.MagickRelinquishMemory(art_p)
        return num.value


class ProfileDict(ImageProperty, abc.MutableMapping):
    """The mapping table of embedded image profiles.

    Use this to get, set, and delete whole profile payloads on an image. Each
    payload is a raw binary string.

    For example::

        with Image(filename='photo.jpg') as img:
            # Extract EXIF
            with open('exif.bin', 'wb') as payload:
                payload.write(img.profiles['exif'])
            # Import ICC
            with open('color_profile.icc', 'rb') as payload:
                img.profiles['icc'] = payload.read()
            # Remove XMP
            del imp.profiles['xmp']

    .. seealso::

        `Embedded Image Profiles`__ for a list of supported profiles.

        __ https://imagemagick.org/script/formats.php#embedded

    .. versionadded:: 0.5.1
    """
    def __init__(self, image):
        if not isinstance(image, Image):
            raise TypeError('expected a wand.image.Image instance, '
                            'not ' + repr(image))
        super().__init__(image)

    def __delitem__(self, k):
        assertions.assert_string(key=k)
        num = ctypes.c_size_t(0)
        profile_p = library.MagickRemoveImageProfile(self.image.wand,
                                                     binary(k), num)
        profile_p = library.MagickRelinquishMemory(profile_p)

    def __getitem__(self, k):
        assertions.assert_string(key=k)
        num = ctypes.c_size_t(0)
        return_profile = None
        profile_p = library.MagickGetImageProfile(self.image.wand,
                                                  binary(k), num)
        if num.value > 0:
            return_profile = bytes(profile_p[0:num.value])
            profile_p = library.MagickRelinquishMemory(profile_p)
        return return_profile

    def __iter__(self):
        num = ctypes.c_size_t(0)
        prop = library.MagickGetImageProfiles(self.image.wand, b'', num)
        profiles = [text(ctypes.string_at(prop[i])) for i in range(num.value)]
        prop = library.MagickRelinquishMemory(prop)
        return iter(profiles)

    def __len__(self):
        num = ctypes.c_size_t(0)
        profiles_p = library.MagickGetImageProfiles(self.image.wand, b'', num)
        profiles_p = library.MagickRelinquishMemory(profiles_p)
        return num.value

    def __setitem__(self, k, v):
        assertions.assert_string(key=k)
        if not isinstance(v, bytes):
            raise TypeError('value must be a binary string, not ' + repr(v))
        r = library.MagickSetImageProfile(self.image.wand,
                                          binary(k), v, len(v))
        if not r:
            self.image.raise_exception()


class ChannelImageDict(ImageProperty, abc.Mapping):
    """The mapping table of separated images of the particular channel
    from the image.

    :param image: an image instance
    :type image: :class:`Image`

    .. note::

       You don't have to use this by yourself.
       Use :attr:`Image.channel_images` property instead.

    .. versionadded:: 0.3.0

    """

    def __iter__(self):
        return iter(CHANNELS)

    def __len__(self):
        return len(CHANNELS)

    def __getitem__(self, channel):
        c = CHANNELS[channel]
        img = self.image.clone()
        if library.MagickSeparateImageChannel:
            succeeded = library.MagickSeparateImageChannel(img.wand, c)
        else:
            succeeded = library.MagickSeparateImage(img.wand, c)
        if not succeeded:
            try:
                img.raise_exception()
            except WandException:
                img.close()
                raise
        return img


class ChannelDepthDict(ImageProperty, abc.Mapping):
    """The mapping table of channels to their depth.

    :param image: an image instance
    :type image: :class:`Image`

    .. note::

       You don't have to use this by yourself.
       Use :attr:`Image.channel_depths` property instead.

    .. versionadded:: 0.3.0

    """

    def __iter__(self):
        return iter(CHANNELS)

    def __len__(self):
        return len(CHANNELS)

    def __getitem__(self, channel):
        c = CHANNELS[channel]
        if library.MagickGetImageChannelDepth:
            depth = library.MagickGetImageChannelDepth(self.image.wand, c)
        else:
            mask = 0
            if c != 0:
                mask = library.MagickSetImageChannelMask(self.image.wand, c)
            depth = library.MagickGetImageDepth(self.image.wand)
            if mask != 0:
                library.MagickSetImageChannelMask(self.image.wand, mask)
        return int(depth)


class HistogramDict(abc.Mapping):
    """Specialized mapping object to represent color histogram.
    Keys are colors, and values are the number of pixels.

    :param image: the image to get its histogram
    :type image: :class:`BaseImage`

    .. versionadded:: 0.3.0

    """

    def __init__(self, image):
        self.size = ctypes.c_size_t()
        self.pixels = library.MagickGetImageHistogram(
            image.wand,
            ctypes.byref(self.size)
        )
        self.counts = None

    def __del__(self):
        if self.pixels:
            self.pixels = library.DestroyPixelWands(self.pixels,
                                                    self.size.value)

    def __len__(self):
        if self.counts is None:
            return self.size.value
        return len(self.counts)

    def __iter__(self):
        if self.counts is None:
            self._build_counts()
        return iter(self.counts)

    def __getitem__(self, color):
        if self.counts is None:
            self._build_counts()
        if isinstance(color, str):
            color = Color(color)
        assertions.assert_color(color=color)
        return self.counts[color]

    def _build_counts(self):
        self.counts = {}
        for i in range(self.size.value):
            color_count = library.PixelGetColorCount(self.pixels[i])
            color = Color.from_pixelwand(self.pixels[i])
            self.counts[color] = color_count


class ConnectedComponentObject:
    """Generic Python wrapper to translate
    :c:type:`CCObjectInfo` structure into a class describing objects found
    within an image. This class is generated by
    :meth:`Image.connected_components()
    <wand.image.BaseImage.connected_components>` method.

    .. versionadded:: 0.5.5
    .. versionchanged:: 0.6.3
        Added :attr:`merge` & :attr:`metric` for ImageMagick 7.0.10
    .. versionchanged:: 0.6.8
        Added :attr:`key` property for ImageMagick 7.1.0
    """
    #: (:class:`numbers.Integral`) Serialized object identifier
    #: starting at `0`.
    _id = None

    #: (:class:`numbers.Integral`) Width of objects minimum
    #: bounding rectangle.
    width = None

    #: (:class:`numbers.Integral`) Height of objects minimum
    #: bounding rectangle.
    height = None

    #: (:class:`numbers.Integral`) X offset of objects minimum
    #: bounding rectangle.
    left = None

    #: (:class:`numbers.Integral`) Y offset of objects minimum
    #: bounding rectangle.
    top = None

    #: (:class:`numbers.Real`) X offset of objects centroid.
    center_x = None

    #: (:class:`numbers.Real`) Y offset of objects centroid.
    center_y = None

    #: (:class:`numbers.Real`) Quantity of pixels that make-up
    #: the objects shape.
    area = None

    #: (:class:`~wand.color.Color`) The average color of the
    #: shape.
    mean_color = None

    #: (:class:`bool`) Object merge flag. Only available after
    #: ImageMagick-7.0.10.
    #: ..versionadded:: 0.6.3
    merge = None

    #: (:class:`list`) List of doubles used by metric. Only available after
    #: ImageMagick-7.0.10.
    #: ..versionadded:: 0.6.3
    metric = None

    def __init__(self, cc_object=None):
        if isinstance(cc_object, CCObjectInfo):
            self.clone_from_cc_object_info(cc_object)
        if isinstance(cc_object, CCObjectInfo70A):
            self.clone_from_cc_object_info(cc_object)
            self.clone_from_extra_70A_info(cc_object)
        if isinstance(cc_object, CCObjectInfo710):
            self.clone_from_cc_object_info(cc_object)
            self.clone_from_extra_70A_info(cc_object)
            self.clone_from_extra_710_info(cc_object)

    @property
    def size(self):
        """(:class:`tuple` (:attr:`width`, :attr:`height`))
        Minimum bounding rectangle."""
        return self.width, self.height

    @property
    def offset(self):
        """(:class:`tuple` (:attr:`left`, :attr:`top`))
        Position of objects minimum bounding rectangle."""
        return self.left, self.top

    @property
    def centroid(self):
        """(:class:`tuple` (:attr:`center_x`, :attr:`center_y`))
        Center of object."""
        return self.center_x, self.center_y

    def clone_from_cc_object_info(self, cc_object):
        """Copy data from :class:`~wand.cdefs.structures.CCObjectInfo`."""
        self._id = cc_object._id
        self.width = cc_object.bounding_box.width
        self.height = cc_object.bounding_box.height
        self.left = cc_object.bounding_box.x
        self.top = cc_object.bounding_box.y
        self.center_x = cc_object.centroid.x
        self.center_y = cc_object.centroid.y
        self.area = cc_object.area
        pinfo_size = ctypes.sizeof(PixelInfo)
        raw_buffer = ctypes.create_string_buffer(pinfo_size)
        ctypes.memmove(raw_buffer,
                       ctypes.byref(cc_object.color),
                       pinfo_size)
        self.mean_color = Color(raw=raw_buffer)

    def clone_from_extra_70A_info(self, cc_object):
        """Copy the additional values from CCObjectInfo structure. This is the
        :attr:`merge` & :attr:`metric` properties added in ImageMagick 7.0.10.

        .. versionadded:: 0.6.3
        """
        self.merge = cc_object.merge
        self.metric = []
        for i in range(cc_object.CCMaxMetrics):
            self.metric.append(cc_object.metric[i])

    def clone_from_extra_710_info(self, cc_object):
        """Copy additional value from CCObjectInfo structure for properties
        added to ImageMagick 7.1.0.

        .. versionadded:: 0.6.8
        """
        self.key = cc_object.key

    def __repr__(self):
        fmt = ("{name}({_id}: {width}x{height}+{left}+{top} {center_x:.2f},"
               "{center_y:.2f} {area:.0f} {mean_color})")
        return fmt.format(name=self.__class__.__name__, **self.__dict__)


class ClosedImageError(DestroyedResourceError):
    """An error that rises when some code tries access to an already closed
    image.

    """
