Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/wand/image.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""":mod:`wand.image` --- Image objects
2~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4Opens and manipulates images. Image objects can be used in :keyword:`with`
5statement, and these resources will be automatically managed (even if any
6error happened)::
8 with Image(filename='pikachu.png') as i:
9 print('width =', i.width)
10 print('height =', i.height)
12"""
13import ctypes
14import functools
15import numbers
16import weakref
18from . import assertions
19from .api import libc, libmagick, library
20from .color import Color
21from .compat import (abc, binary, binary_type, encode_filename, file_types,
22 PY3, string_type, text, xrange)
23from .exceptions import (MissingDelegateError, WandException,
24 WandRuntimeError, WandLibraryVersionError)
25from .font import Font
26from .resource import DestroyedResourceError, Resource
27from .cdefs.structures import (CCObjectInfo, ChannelFeature, GeometryInfo,
28 PixelInfo, RectangleInfo)
29from .version import MAGICK_VERSION_NUMBER, MAGICK_HDRI
32__all__ = ('ALPHA_CHANNEL_TYPES', 'AUTO_THRESHOLD_METHODS', 'CHANNELS',
33 'COLORSPACE_TYPES', 'COMPARE_METRICS', 'COMPOSITE_OPERATORS',
34 'COMPRESSION_TYPES', 'DISPOSE_TYPES', 'DISTORTION_METHODS',
35 'DITHER_METHODS', 'EVALUATE_OPS', 'FILTER_TYPES', 'FUNCTION_TYPES',
36 'GRAVITY_TYPES', 'IMAGE_LAYER_METHOD', 'IMAGE_TYPES',
37 'INTERLACE_TYPES', 'KERNEL_INFO_TYPES', 'MORPHOLOGY_METHODS',
38 'NOISE_TYPES', 'ORIENTATION_TYPES', 'PIXEL_INTERPOLATE_METHODS',
39 'RENDERING_INTENT_TYPES', 'SPARSE_COLOR_METHODS', 'STATISTIC_TYPES',
40 'STORAGE_TYPES', 'VIRTUAL_PIXEL_METHOD', 'UNIT_TYPES',
41 'BaseImage', 'ChannelDepthDict', 'ChannelImageDict',
42 'ClosedImageError', 'HistogramDict', 'Image', 'ImageProperty',
43 'Iterator', 'Metadata', 'OptionDict', 'manipulative',
44 'ArtifactTree', 'ProfileDict', 'ConnectedComponentObject')
47#: (:class:`tuple`) The list of :attr:`~wand.image.BaseImage.alpha_channel`
48#: types.
49#:
50#: - ``'undefined'``
51#: - ``'activate'``
52#: - ``'background'``
53#: - ``'copy'``
54#: - ``'deactivate'``
55#: - ``'discrete'`` - Only available in ImageMagick-7
56#: - ``'extract'``
57#: - ``'off'`` - Only available in ImageMagick-7
58#: - ``'on'`` - Only available in ImageMagick-7
59#: - ``'opaque'``
60#: - ``'reset'`` - Only available in ImageMagick-6
61#: - ``'set'``
62#: - ``'shape'``
63#: - ``'transparent'``
64#: - ``'flatten'`` - Only available in ImageMagick-6
65#: - ``'remove'``
66#:
67#: .. seealso::
68#: `ImageMagick Image Channel`__
69#: Describes the SetImageAlphaChannel method which can be used
70#: to modify alpha channel. Also describes AlphaChannelType
71#:
72#: __ http://www.imagemagick.org/api/channel.php#SetImageAlphaChannel
73ALPHA_CHANNEL_TYPES = ('undefined', 'activate', 'background', 'copy',
74 'deactivate', 'extract', 'opaque', 'reset', 'set',
75 'shape', 'transparent', 'flatten', 'remove',
76 'associate', 'disassociate')
77if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
78 ALPHA_CHANNEL_TYPES = ('undefined', 'activate', 'associate', 'background',
79 'copy', 'deactivate', 'discrete', 'disassociate',
80 'extract', 'off', 'on', 'opaque', 'remove', 'set',
81 'shape', 'transparent')
84#: (:class:`tuple`) The list of methods used by
85#: :meth:`Image.auto_threshold() <wand.image.BaseImage.auto_threshold>`
86#:
87#: - ``'undefined'``
88#: - ``'kapur'``
89#: - ``'otsu'``
90#: - ``'triangle'``
91#:
92#: .. versionadded:: 0.5.5
93AUTO_THRESHOLD_METHODS = ('undefined', 'kapur', 'otsu', 'triangle')
96#: (:class:`dict`) The dictionary of channel types.
97#:
98#: - ``'undefined'``
99#: - ``'red'``
100#: - ``'gray'``
101#: - ``'cyan'``
102#: - ``'green'``
103#: - ``'magenta'``
104#: - ``'blue'``
105#: - ``'yellow'``
106#: - ``'alpha'``
107#: - ``'opacity'``
108#: - ``'black'``
109#: - ``'index'``
110#: - ``'composite_channels'``
111#: - ``'all_channels'``
112#: - ``'sync_channels'``
113#: - ``'default_channels'``
114#:
115#: .. seealso::
116#:
117#: `ImageMagick Color Channels`__
118#: Lists the various channel types with descriptions of each
119#:
120#: __ http://www.imagemagick.org/Magick++/Enumerations.html#ChannelType
121#:
122#: .. versionchanged:: 0.5.5
123#: Deprecated ``true_alpha``, ``rgb_channels``, and ``gray_channels``
124#: values in favor of MagickCore channel parser.
125#:
126CHANNELS = dict(undefined=0, red=1, gray=1, cyan=1, green=2, magenta=2,
127 blue=4, yellow=4, alpha=8, opacity=8, black=32, index=32,
128 composite_channels=47, all_channels=134217727, true_alpha=64,
129 rgb=7, rgb_channels=7, gray_channels=1, sync_channels=256,
130 default_channels=134217719)
131if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
132 CHANNELS = dict(undefined=0, red=1, gray=1, cyan=1, green=2, magenta=2,
133 blue=4, yellow=4, black=8, alpha=16, opacity=16, index=32,
134 readmask=0x0040, write_mask=128, meta=256,
135 composite_channels=31, all_channels=134217727,
136 true_alpha=256, rgb=7, rgb_channels=7, gray_channels=1,
137 sync_channels=131072, default_channels=134217727)
140#: (:class:`tuple`) The list of colorspaces.
141#:
142#: - ``'undefined'``
143#: - ``'rgb'``
144#: - ``'gray'``
145#: - ``'transparent'``
146#: - ``'ohta'``
147#: - ``'lab'``
148#: - ``'xyz'``
149#: - ``'ycbcr'``
150#: - ``'ycc'``
151#: - ``'yiq'``
152#: - ``'ypbpr'``
153#: - ``'yuv'``
154#: - ``'cmyk'``
155#: - ``'srgb'``
156#: - ``'hsb'``
157#: - ``'hsl'``
158#: - ``'hwb'``
159#: - ``'rec601luma'`` - Only available with ImageMagick-6
160#: - ``'rec601ycbcr'``
161#: - ``'rec709luma'`` - Only available with ImageMagick-6
162#: - ``'rec709ycbcr'``
163#: - ``'log'``
164#: - ``'cmy'``
165#: - ``'luv'``
166#: - ``'hcl'``
167#: - ``'lch'``
168#: - ``'lms'``
169#: - ``'lchab'``
170#: - ``'lchuv'``
171#: - ``'scrgb'``
172#: - ``'hsi'``
173#: - ``'hsv'``
174#: - ``'hclp'``
175#: - ``'xyy'`` - Only available with ImageMagick-7
176#: - ``'ydbdr'``
177#:
178#: .. seealso::
179#:
180#: `ImageMagick Color Management`__
181#: Describes the ImageMagick color management operations
182#:
183#: __ http://www.imagemagick.org/script/color-management.php
184#:
185#: .. versionadded:: 0.3.4
186COLORSPACE_TYPES = ('undefined', 'rgb', 'gray', 'transparent', 'ohta', 'lab',
187 'xyz', 'ycbcr', 'ycc', 'yiq', 'ypbpr', 'yuv', 'cmyk',
188 'srgb', 'hsb', 'hsl', 'hwb', 'rec601luma', 'rec601ycbcr',
189 'rec709luma', 'rec709ycbcr', 'log', 'cmy', 'luv', 'hcl',
190 'lch', 'lms', 'lchab', 'lchuv', 'scrgb', 'hsi', 'hsv',
191 'hclp', 'ydbdr')
192if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
193 COLORSPACE_TYPES = ('undefined', 'cmy', 'cmyk', 'gray', 'hcl', 'hclp',
194 'hsb', 'hsi', 'hsl', 'hsv', 'hwb', 'lab', 'lch',
195 'lchab', 'lchuv', 'log', 'lms', 'luv', 'ohta',
196 'rec601ycbcr', 'rec709ycbcr', 'rgb', 'scrgb', 'srgb',
197 'transparent', 'xyy', 'xyz', 'ycbcr', 'ycc', 'ydbdr',
198 'yiq', 'ypbpr', 'yuv')
200#: (:class:`tuple`) The list of compare metric types used by
201#: :meth:`Image.compare() <wand.image.BaseImage.compare>` and
202#: :meth:`Image.similarity() <wand.image.BaseImage.similarity>` methods.
203#:
204#: - ``'undefined'``
205#: - ``'absolute'``
206#: - ``'fuzz'``
207#: - ``'mean_absolute'``
208#: - ``'mean_error_per_pixel'``
209#: - ``'mean_squared'``
210#: - ``'normalized_cross_correlation'``
211#: - ``'peak_absolute'``
212#: - ``'peak_signal_to_noise_ratio'``
213#: - ``'perceptual_hash'`` - Available with ImageMagick-7
214#: - ``'root_mean_square'``
215#: - ``'structural_similarity'`` - Available with ImageMagick-7
216#: - ``'structural_dissimilarity'`` - Available with ImageMagick-7
217#:
218#: .. seealso::
219#:
220#: `ImageMagick Compare Operations`__
221#:
222#: __ http://www.imagemagick.org/Usage/compare/
223#:
224#: .. versionadded:: 0.4.3
225#:
226#: .. versionchanged:: 0.5.4 - Remapped :c:data:`MetricType` enum.
227COMPARE_METRICS = ('undefined', 'absolute',
228 'mean_absolute', 'mean_error_per_pixel',
229 'mean_squared', 'peak_absolute',
230 'peak_signal_to_noise_ratio', 'root_mean_square',
231 'normalized_cross_correlation', 'fuzz')
232if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
233 COMPARE_METRICS = ('undefined', 'absolute', 'fuzz', 'mean_absolute',
234 'mean_error_per_pixel', 'mean_squared',
235 'normalized_cross_correlation', 'peak_absolute',
236 'peak_signal_to_noise_ratio', 'perceptual_hash',
237 'root_mean_square', 'structural_similarity',
238 'structural_dissimilarity')
241#: (:class:`tuple`) The list of complex operators used by
242#: :meth:`Image.complex() <wand.image.BaseImage.complex>`.
243#:
244#: - ``'undefined'``
245#: - ``'add'``
246#: - ``'conjugate'``
247#: - ``'divide'``
248#: - ``'magnitude',``
249#: - ``'multiply'``
250#: - ``'real_imaginary'``
251#: - ``'subtract'``
252#:
253#: .. versionadded:: 0.5.5
254COMPLEX_OPERATORS = ('undefined', 'add', 'conjugate', 'divide', 'magnitude',
255 'multiply', 'real_imaginary', 'subtract')
258#: (:class:`tuple`) The list of composition operators
259#:
260#: - ``'undefined'``
261#: - ``'alpha'`` - Only available with ImageMagick-7
262#: - ``'atop'``
263#: - ``'blend'``
264#: - ``'blur'``
265#: - ``'bumpmap'``
266#: - ``'change_mask'``
267#: - ``'clear'``
268#: - ``'color_burn'``
269#: - ``'color_dodge'``
270#: - ``'colorize'``
271#: - ``'copy_black'``
272#: - ``'copy_blue'``
273#: - ``'copy'``
274#: - ``'copy_alpha'`` - Only available with ImageMagick-7
275#: - ``'copy_cyan'``
276#: - ``'copy_green'``
277#: - ``'copy_magenta'``
278#: - ``'copy_opacity'`` - Only available with ImageMagick-6
279#: - ``'copy_red'``
280#: - ``'copy_yellow'``
281#: - ``'darken'``
282#: - ``'darken_intensity'``
283#: - ``'difference'``
284#: - ``'displace'``
285#: - ``'dissolve'``
286#: - ``'distort'``
287#: - ``'divide_dst'``
288#: - ``'divide_src'``
289#: - ``'dst_atop'``
290#: - ``'dst'``
291#: - ``'dst_in'``
292#: - ``'dst_out'``
293#: - ``'dst_over'``
294#: - ``'exclusion'``
295#: - ``'hard_light'``
296#: - ``'hard_mix'``
297#: - ``'hue'``
298#: - ``'in'``
299#: - ``'intensity'`` - Only available with ImageMagick-7
300#: - ``'lighten'``
301#: - ``'lighten_intensity'``
302#: - ``'linear_burn'``
303#: - ``'linear_dodge'``
304#: - ``'linear_light'``
305#: - ``'luminize'``
306#: - ``'mathematics'``
307#: - ``'minus_dst'``
308#: - ``'minus_src'``
309#: - ``'modulate'``
310#: - ``'modulus_add'``
311#: - ``'modulus_subtract'``
312#: - ``'multiply'``
313#: - ``'no'``
314#: - ``'out'``
315#: - ``'over'``
316#: - ``'overlay'``
317#: - ``'pegtop_light'``
318#: - ``'pin_light'``
319#: - ``'plus'``
320#: - ``'replace'``
321#: - ``'saturate'``
322#: - ``'screen'``
323#: - ``'soft_light'``
324#: - ``'src_atop'``
325#: - ``'src'``
326#: - ``'src_in'``
327#: - ``'src_out'``
328#: - ``'src_over'``
329#: - ``'threshold'``
330#: - ``'vivid_light'``
331#: - ``'xor'``
332#: - ``'stereo'``
333#:
334#: .. versionchanged:: 0.3.0
335#: Renamed from :const:`COMPOSITE_OPS` to :const:`COMPOSITE_OPERATORS`.
336#:
337#: .. versionchanged:: 0.5.6
338#: Operators have been updated to reflect latest changes in C-API.
339# For ImageMagick-6, ``'add'`` has been renamed to ``'modulus_add'``,
340#: ``'subtract'`` has been renamed to ``'modulus_subtract'``,
341#: ``'divide'`` has been split into ``'divide_dst'`` & ``'divide_src'``, and
342#: ``'minus'`` has been split into ``'minus_dst'`` & ``'minus_src'``.
343#:
344#: .. seealso::
345#:
346#: `Compositing Images`__ ImageMagick v6 Examples
347#: Image composition is the technique of combining images that have,
348#: or do not have, transparency or an alpha channel.
349#: This is usually performed using the IM :program:`composite` command.
350#: It may also be performed as either part of a larger sequence of
351#: operations or internally by other image operators.
352#:
353#: `ImageMagick Composition Operators`__
354#: Demonstrates the results of applying the various composition
355#: composition operators.
356#:
357#: __ http://www.imagemagick.org/Usage/compose/
358#: __ http://www.rubblewebs.co.uk/imagemagick/operators/compose.php
359COMPOSITE_OPERATORS = (
360 'undefined', 'no', 'modulus_add', 'atop', 'blend', 'bumpmap',
361 'change_mask', 'clear', 'color_burn', 'color_dodge', 'colorize',
362 'copy_black', 'copy_blue', 'copy', 'copy_cyan', 'copy_green',
363 'copy_magenta', 'copy_opacity', 'copy_red', 'copy_yellow', 'darken',
364 'dst_atop', 'dst', 'dst_in', 'dst_out', 'dst_over', 'difference',
365 'displace', 'dissolve', 'exclusion', 'hard_light', 'hue', 'in', 'lighten',
366 'linear_light', 'luminize', 'minus_dst', 'modulate', 'multiply', 'out',
367 'over', 'overlay', 'plus', 'replace', 'saturate', 'screen', 'soft_light',
368 'src_atop', 'src', 'src_in', 'src_out', 'src_over', 'modulus_subtract',
369 'threshold', 'xor', 'divide_dst', 'distort', 'blur', 'pegtop_light',
370 'vivid_light', 'pin_light', 'linear_dodge', 'linear_burn', 'mathematics',
371 'divide_src', 'minus_src', 'darken_intensity', 'lighten_intensity',
372 'hard_mix', 'stereo'
373)
374if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
375 COMPOSITE_OPERATORS = (
376 'undefined', 'alpha', 'atop', 'blend', 'blur', 'bumpmap',
377 'change_mask', 'clear', 'color_burn', 'color_dodge', 'colorize',
378 'copy_black', 'copy_blue', 'copy', 'copy_cyan', 'copy_green',
379 'copy_magenta', 'copy_alpha', 'copy_red', 'copy_yellow', 'darken',
380 'darken_intensity', 'difference', 'displace', 'dissolve', 'distort',
381 'divide_dst', 'divide_src', 'dst_atop', 'dst', 'dst_in', 'dst_out',
382 'dst_over', 'exclusion', 'hard_light', 'hard_mix', 'hue', 'in',
383 'intensity', 'lighten', 'lighten_intensity', 'linear_burn',
384 'linear_dodge', 'linear_light', 'luminize', 'mathematics', 'minus_dst',
385 'minus_src', 'modulate', 'modulus_add', 'modulus_subtract', 'multiply',
386 'no', 'out', 'over', 'overlay', 'pegtop_light', 'pin_light', 'plus',
387 'replace', 'saturate', 'screen', 'soft_light', 'src_atop', 'src',
388 'src_in', 'src_out', 'src_over', 'threshold', 'vivid_light', 'xor',
389 'stereo'
390 )
392#: (:class:`tuple`) The list of :attr:`Image.compression` types.
393#:
394#: .. versionadded:: 0.3.6
395#: .. versionchanged:: 0.5.0
396#: Support for ImageMagick-6 & ImageMagick-7
397COMPRESSION_TYPES = (
398 'undefined', 'no', 'bzip', 'dxt1', 'dxt3', 'dxt5',
399 'fax', 'group4', 'jpeg', 'jpeg2000', 'losslessjpeg',
400 'lzw', 'rle', 'zip', 'zips', 'piz', 'pxr24', 'b44',
401 'b44a', 'lzma', 'jbig1', 'jbig2'
402)
403if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
404 COMPRESSION_TYPES = (
405 'undefined', 'b44a', 'b44', 'bzip', 'dxt1', 'dxt3', 'dxt5', 'fax',
406 'group4', 'jbig1', 'jbig2', 'jpeg2000', 'jpeg', 'losslessjpeg',
407 'lzma', 'lzw', 'no', 'piz', 'pxr24', 'rle', 'zip', 'zips'
408 )
410#: (:class:`tuple`) The list of :attr:`BaseImage.dispose` types.
411#:
412#: .. versionadded:: 0.5.0
413DISPOSE_TYPES = (
414 'undefined',
415 'none',
416 'background',
417 'previous'
418)
421#: (:class:`tuple`) The list of :meth:`BaseImage.distort` methods.
422#:
423#: - ``'undefined'``
424#: - ``'affine'``
425#: - ``'affine_projection'``
426#: - ``'scale_rotate_translate'``
427#: - ``'perspective'``
428#: - ``'perspective_projection'``
429#: - ``'bilinear_forward'``
430#: - ``'bilinear_reverse'``
431#: - ``'polynomial'``
432#: - ``'arc'``
433#: - ``'polar'``
434#: - ``'depolar'``
435#: - ``'cylinder_2_plane'``
436#: - ``'plane_2_cylinder'``
437#: - ``'barrel'``
438#: - ``'barrel_inverse'``
439#: - ``'shepards'``
440#: - ``'resize'``
441#: - ``'sentinel'``
442#:
443#: .. versionadded:: 0.4.1
444DISTORTION_METHODS = (
445 'undefined', 'affine', 'affine_projection', 'scale_rotate_translate',
446 'perspective', 'perspective_projection', 'bilinear_forward',
447 'bilinear_reverse', 'polynomial', 'arc', 'polar', 'depolar',
448 'cylinder_2_plane', 'plane_2_cylinder', 'barrel', 'barrel_inverse',
449 'shepards', 'resize', 'sentinel'
450)
453#: (:class:`tuple`) The list of Dither methods. Used by
454#: :meth:`Image.posterize() <BaseImage.posterize>` and
455#: :meth:`Image.remap() <BaseImage.remap>` methods.
456#:
457#: - ``'undefined'``
458#: - ``'no'``
459#: - ``'riemersma'``
460#: - ``'floyd_steinberg'``
461#:
462#: .. versionadded:: 0.5.0
463DITHER_METHODS = ('undefined', 'no', 'riemersma', 'floyd_steinberg')
466#: (:class:`tuple`) The list of evaluation operators. Used by
467#: :meth:`Image.evaluate() <BaseImage.evaluate>` method.
468#:
469#: - ``'undefined'``
470#: - ``'abs'``
471#: - ``'add'``
472#: - ``'addmodulus'``
473#: - ``'and'``
474#: - ``'cosine'``
475#: - ``'divide'``
476#: - ``'exponential'``
477#: - ``'gaussiannoise'``
478#: - ``'impulsenoise'``
479#: - ``'laplaciannoise'``
480#: - ``'leftshift'``
481#: - ``'log'``
482#: - ``'max'``
483#: - ``'mean'``
484#: - ``'median'``
485#: - ``'min'``
486#: - ``'multiplicativenoise'``
487#: - ``'multiply'``
488#: - ``'or'``
489#: - ``'poissonnoise'``
490#: - ``'pow'``
491#: - ``'rightshift'``
492#: - ``'set'``
493#: - ``'sine'``
494#: - ``'subtract'``
495#: - ``'sum'``
496#: - ``'threshold'``
497#: - ``'thresholdblack'``
498#: - ``'thresholdwhite'``
499#: - ``'uniformnoise'``
500#: - ``'xor'``
501#:
502#: .. seealso::
503#:
504#: `ImageMagick Image Evaluation Operators`__
505#: Describes the MagickEvaluateImageChannel method and lists the
506#: various evaluations operators
507#:
508#: __ http://www.magickwand.org/MagickEvaluateImage.html
509EVALUATE_OPS = ('undefined', 'add', 'and', 'divide', 'leftshift', 'max',
510 'min', 'multiply', 'or', 'rightshift', 'set', 'subtract',
511 'xor', 'pow', 'log', 'threshold', 'thresholdblack',
512 'thresholdwhite', 'gaussiannoise', 'impulsenoise',
513 'laplaciannoise', 'multiplicativenoise', 'poissonnoise',
514 'uniformnoise', 'cosine', 'sine', 'addmodulus', 'mean',
515 'abs', 'exponential', 'median', 'sum', 'rootmeansquare')
516if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
517 EVALUATE_OPS = ('undefined', 'abs', 'add', 'addmodulus', 'and', 'cosine',
518 'divide', 'exponential', 'gaussiannoise', 'impulsenoise',
519 'laplaciannoise', 'leftshift', 'log', 'max', 'mean',
520 'median', 'min', 'multiplicativenoise', 'multiply', 'or',
521 'poissonnoise', 'pow', 'rightshift', 'rootmeansquare',
522 'set', 'sine', 'subtract', 'sum', 'thresholdblack',
523 'threshold', 'thresholdwhite', 'uniformnoise', 'xor')
526#: (:class:`tuple`) The list of filter types. Used by
527#: :meth:`Image.resample() <BaseImage.resample>` and
528#: :meth:`Image.resize() <BaseImage.resize>` methods.
529#:
530#: - ``'undefined'``
531#: - ``'point'``
532#: - ``'box'``
533#: - ``'triangle'``
534#: - ``'hermite'``
535#: - ``'hanning'``
536#: - ``'hamming'``
537#: - ``'blackman'``
538#: - ``'gaussian'``
539#: - ``'quadratic'``
540#: - ``'cubic'``
541#: - ``'catrom'``
542#: - ``'mitchell'``
543#: - ``'jinc'``
544#: - ``'sinc'``
545#: - ``'sincfast'``
546#: - ``'kaiser'``
547#: - ``'welsh'``
548#: - ``'parzen'``
549#: - ``'bohman'``
550#: - ``'bartlett'``
551#: - ``'lagrange'``
552#: - ``'lanczos'``
553#: - ``'lanczossharp'``
554#: - ``'lanczos2'``
555#: - ``'lanczos2sharp'``
556#: - ``'robidoux'``
557#: - ``'robidouxsharp'``
558#: - ``'cosine'``
559#: - ``'spline'``
560#: - ``'sentinel'``
561#:
562#: .. seealso::
563#:
564#: `ImageMagick Resize Filters`__
565#: Demonstrates the results of resampling images using the various
566#: resize filters and blur settings available in ImageMagick.
567#:
568#: __ http://www.imagemagick.org/Usage/resize/
569FILTER_TYPES = ('undefined', 'point', 'box', 'triangle', 'hermite', 'hanning',
570 'hamming', 'blackman', 'gaussian', 'quadratic', 'cubic',
571 'catrom', 'mitchell', 'jinc', 'sinc', 'sincfast', 'kaiser',
572 'welsh', 'parzen', 'bohman', 'bartlett', 'lagrange', 'lanczos',
573 'lanczossharp', 'lanczos2', 'lanczos2sharp', 'robidoux',
574 'robidouxsharp', 'cosine', 'spline', 'sentinel')
577#: (:class:`tuple`) The list of :attr:`Image.function <BaseImage.function>`
578#: types.
579#:
580#: - ``'undefined'``
581#: - ``'arcsin'``
582#: - ``'arctan'``
583#: - ``'polynomial'``
584#: - ``'sinusoid'``
585FUNCTION_TYPES = ('undefined', 'polynomial', 'sinusoid', 'arcsin', 'arctan')
586if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
587 FUNCTION_TYPES = ('undefined', 'arcsin', 'arctan', 'polynomial',
588 'sinusoid')
591#: (:class:`tuple`) The list of :attr:`~BaseImage.gravity` types.
592#:
593#: - ``'forget'``
594#: - ``'north_west'``
595#: - ``'north'``
596#: - ``'north_east'``
597#: - ``'west'``
598#: - ``'center'``
599#: - ``'east'``
600#: - ``'south_west'``
601#: - ``'south'``
602#: - ``'south_east'``
603#:
604#: .. versionadded:: 0.3.0
605GRAVITY_TYPES = ('forget', 'north_west', 'north', 'north_east', 'west',
606 'center', 'east', 'south_west', 'south', 'south_east',
607 'static')
610#: (:class:`tuple`) The list of methods for :meth:`~BaseImage.merge_layers`
611#: and :meth:`~Image.compare_layers`.
612#:
613#: - ``'undefined'``
614#: - ``'coalesce'``
615#: - ``'compareany'`` - Only used for :meth:`~Image.compare_layers`.
616#: - ``'compareclear'`` - Only used for :meth:`~Image.compare_layers`.
617#: - ``'compareoverlay'`` - Only used for :meth:`~Image.compare_layers`.
618#: - ``'dispose'``
619#: - ``'optimize'``
620#: - ``'optimizeimage'``
621#: - ``'optimizeplus'``
622#: - ``'optimizetrans'``
623#: - ``'removedups'``
624#: - ``'removezero'``
625#: - ``'composite'``
626#: - ``'merge'`` - Only used for :meth:`~BaseImage.merge_layers`.
627#: - ``'flatten'`` - Only used for :meth:`~BaseImage.merge_layers`.
628#: - ``'mosaic'`` - Only used for :meth:`~BaseImage.merge_layers`.
629#: - ``'trimbounds'`` - Only used for :meth:`~BaseImage.merge_layers`.
630#:
631#: .. versionadded:: 0.4.3
632IMAGE_LAYER_METHOD = ('undefined', 'coalesce', 'compareany', 'compareclear',
633 'compareoverlay', 'dispose', 'optimize', 'optimizeimage',
634 'optimizeplus', 'optimizetrans', 'removedups',
635 'removezero', 'composite', 'merge', 'flatten', 'mosaic',
636 'trimbounds')
639#: (:class:`tuple`) The list of image types
640#:
641#: - ``'undefined'``
642#: - ``'bilevel'``
643#: - ``'grayscale'``
644#: - ``'grayscalealpha'`` - Only available with ImageMagick-7
645#: - ``'grayscalematte'`` - Only available with ImageMagick-6
646#: - ``'palette'``
647#: - ``'palettealpha'`` - Only available with ImageMagick-7
648#: - ``'palettematte'`` - Only available with ImageMagick-6
649#: - ``'truecolor'``
650#: - ``'truecoloralpha'`` - Only available with ImageMagick-7
651#: - ``'truecolormatte'`` - Only available with ImageMagick-6
652#: - ``'colorseparation'``
653#: - ``'colorseparationalpha'`` - Only available with ImageMagick-7
654#: - ``'colorseparationmatte'`` - Only available with ImageMagick-6
655#: - ``'optimize'``
656#: - ``'palettebilevelalpha'`` - Only available with ImageMagick-7
657#: - ``'palettebilevelmatte'`` - Only available with ImageMagick-6
658#:
659#: .. seealso::
660#:
661#: `ImageMagick Image Types`__
662#: Describes the MagickSetImageType method which can be used
663#: to set the type of an image
664#:
665#: __ http://www.imagemagick.org/api/magick-image.php#MagickSetImageType
666IMAGE_TYPES = ('undefined', 'bilevel', 'grayscale', 'grayscalematte',
667 'palette', 'palettematte', 'truecolor', 'truecolormatte',
668 'colorseparation', 'colorseparationmatte', 'optimize',
669 'palettebilevelmatte')
670if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
671 IMAGE_TYPES = ('undefined', 'bilevel', 'grayscale', 'grayscalealpha',
672 'palette', 'palettealpha', 'truecolor', 'truecoloralpha',
673 'colorseparation', 'colorseparationalpha', 'optimize',
674 'palettebilevelalpha')
677#: (:class:`tuple`) The list of interlace schemes.
678#:
679#: - ``'undefined'``
680#: - ``'no'``
681#: - ``'line'``
682#: - ``'plane'``
683#: - ``'partition'``
684#: - ``'gif'``
685#: - ``'jpeg'``
686#: - ``'png'``
687#:
688#: .. versionadded:: 0.5.2
689INTERLACE_TYPES = ('undefined', 'no', 'line', 'plane', 'partition', 'gif',
690 'jpeg', 'png')
693#: (:class:`tuple`) The list of builtin kernels.
694#:
695#: - ``'undefined'``
696#: - ``'unity'``
697#: - ``'gaussian'``
698#: - ``'dog'``
699#: - ``'log'``
700#: - ``'blur'``
701#: - ``'comet'``
702#: - ``'laplacian'``
703#: - ``'sobel'``
704#: - ``'frei_chen'``
705#: - ``'roberts'``
706#: - ``'prewitt'``
707#: - ``'compass'``
708#: - ``'kirsch'``
709#: - ``'diamond'``
710#: - ``'square'``
711#: - ``'rectangle'``
712#: - ``'octagon'``
713#: - ``'disk'``
714#: - ``'plus'``
715#: - ``'cross'``
716#: - ``'ring'``
717#: - ``'peaks'``
718#: - ``'edges'``
719#: - ``'corners'``
720#: - ``'diagonals'``
721#: - ``'line_ends'``
722#: - ``'line_junctions'``
723#: - ``'ridges'``
724#: - ``'convex_hull'``
725#: - ``'thin_se'``
726#: - ``'skeleton'``
727#: - ``'chebyshev'``
728#: - ``'manhattan'``
729#: - ``'octagonal'``
730#: - ``'euclidean'``
731#: - ``'user_defined'``
732#: - ``'binomial'``
733#:
734KERNEL_INFO_TYPES = ('undefined', 'unity', 'gaussian', 'dog', 'log', 'blur',
735 'comet', 'laplacian', 'sobel', 'frei_chen', 'roberts',
736 'prewitt', 'compass', 'kirsch', 'diamond', 'square',
737 'rectangle', 'octagon', 'disk', 'plus', 'cross', 'ring',
738 'peaks', 'edges', 'corners', 'diagonals', 'line_ends',
739 'line_junctions', 'ridges', 'convex_hull', 'thin_se',
740 'skeleton', 'chebyshev', 'manhattan', 'octagonal',
741 'euclidean', 'user_defined', 'binomial')
742if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
743 KERNEL_INFO_TYPES = ('undefined', 'unity', 'gaussian', 'dog', 'log',
744 'blur', 'comet', 'binomial', 'laplacian', 'sobel',
745 'frei_chen', 'roberts', 'prewitt', 'compass',
746 'kirsch', 'diamond', 'square', 'rectangle', 'octagon',
747 'disk', 'plus', 'cross', 'ring', 'peaks', 'edges',
748 'corners', 'diagonals', 'line_ends', 'line_junctions',
749 'ridges', 'convex_hull', 'thin_se', 'skeleton',
750 'chebyshev', 'manhattan', 'octagonal', 'euclidean',
751 'user_defined')
753#: (:class:`tuple`) The list of morphology methods.
754#:
755#: - ``'undefined'``
756#: - ``'convolve'``
757#: - ``'correlate'``
758#: - ``'erode'``
759#: - ``'dilate'``
760#: - ``'erode_intensity'``
761#: - ``'dilate_intensity'``
762#: - ``'distance'``
763#: - ``'open'``
764#: - ``'close'``
765#: - ``'open_intensity'``
766#: - ``'close_intensity'``
767#: - ``'smooth'``
768#: - ``'edgein'``
769#: - ``'edgeout'``
770#: - ``'edge'``
771#: - ``'tophat'``
772#: - ``'bottom_hat'``
773#: - ``'hit_and_miss'``
774#: - ``'thinning'``
775#: - ``'thicken'``
776#: - ``'voronoi'``
777#: - ``'iterative_distance'``
778#:
779MORPHOLOGY_METHODS = ('undefined', 'convolve', 'correlate', 'erode', 'dilate',
780 'erode_intensity', 'dilate_intensity', 'distance',
781 'open', 'close', 'open_intensity', 'close_intensity',
782 'smooth', 'edgein', 'edgeout', 'edge', 'tophat',
783 'bottom_hat', 'hit_and_miss', 'thinning', 'thicken',
784 'voronoi', 'iterative_distance')
785if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
786 MORPHOLOGY_METHODS = ('undefined', 'convolve', 'correlate', 'erode',
787 'dilate', 'erode_intensity', 'dilate_intensity',
788 'iterative_distance', 'open', 'close',
789 'open_intensity', 'close_intensity', 'smooth',
790 'edgein', 'edgeout', 'edge', 'tophat', 'bottom_hat',
791 'hit_and_miss', 'thinning', 'thicken', 'distance',
792 'voronoi')
795#: (:class:`tuple`) The list of noise types used by
796#: :meth:`Image.noise() <wand.image.BaseImage.noise>` method.
797#:
798#: - ``'undefined'``
799#: - ``'uniform'``
800#: - ``'gaussian'``
801#: - ``'multiplicative_gaussian'``
802#: - ``'impulse'``
803#: - ``'laplacian'``
804#: - ``'poisson'``
805#: - ``'random'``
806#:
807#: .. versionadded:: 0.5.3
808NOISE_TYPES = ('undefined', 'uniform', 'gaussian', 'multiplicative_gaussian',
809 'impulse', 'laplacian', 'poisson', 'random')
812#: (:class:`collections.abc.Set`) The set of available
813#: :attr:`~BaseImage.options`.
814#:
815#: .. versionadded:: 0.3.0
816#:
817#: .. versionchanged:: 0.3.4
818#: Added ``'jpeg:sampling-factor'`` option.
819#:
820#: .. versionchanged:: 0.3.9
821#: Added ``'pdf:use-cropbox'`` option. Ensure you set this option *before*
822#: reading the PDF document.
823#:
824#: .. deprecated:: 0.5.0
825#: Any arbitrary key can be set to the option table. Key-Value pairs set
826#: on the MagickWand stack allowing for various coders, kernels, morphology
827#: (&tc) to pick and choose additional user-supplied properties/artifacts.
828OPTIONS = frozenset([
829 'caption',
830 'comment',
831 'date:create',
832 'date:modify',
833 'exif:ColorSpace',
834 'exif:InteroperabilityIndex',
835 'fill',
836 'film-gamma',
837 'gamma',
838 'hdr:exposure',
839 'jpeg:colorspace',
840 'jpeg:sampling-factor',
841 'label',
842 'pdf:use-cropbox',
843 'png:bit-depth-written',
844 'png:IHDR.bit-depth-orig',
845 'png:IHDR.color-type-orig',
846 'png:tIME',
847 'reference-black',
848 'reference-white',
849 'signature',
850 'tiff:Orientation',
851 'tiff:photometric',
852 'tiff:ResolutionUnit',
853 'type:hinting',
854 'vips:metadata'
855])
858#: (:class:`tuple`) The list of :attr:`~BaseImage.orientation` types.
859#:
860#: .. versionadded:: 0.3.0
861ORIENTATION_TYPES = ('undefined', 'top_left', 'top_right', 'bottom_right',
862 'bottom_left', 'left_top', 'right_top', 'right_bottom',
863 'left_bottom')
866#: (:class:`tuple`) List of interpolate pixel methods (ImageMagick-7 only.)
867#:
868#: - ``'undefined'``
869#: - ``'average'``
870#: - ``'average9'``
871#: - ``'average16'``
872#: - ``'background'``
873#: - ``'bilinear'``
874#: - ``'blend'``
875#: - ``'catrom'``
876#: - ``'integer'``
877#: - ``'mesh'``
878#: - ``'nearest'``
879#: - ``'spline'``
880#:
881#: .. versionadded:: 0.5.0
882PIXEL_INTERPOLATE_METHODS = ('undefined', 'average', 'average9', 'average16',
883 'background', 'bilinear', 'blend', 'catrom',
884 'integer', 'mesh', 'nearest', 'spline')
887#: (:class:`tuple`) List of rendering intent types used for
888#: :attr:`Image.rendering_intent <wand.image.BaseImage.rendering_intent>`
889#: property.
890#:
891#: - ``'undefined'``
892#: - ``'saturation'``
893#: - ``'perceptual'``
894#: - ``'absolute'``
895#: - ``'relative'``
896#:
897#: .. versionadded:: 0.5.4
898RENDERING_INTENT_TYPES = ('undefined', 'saturation', 'perceptual', 'absolute',
899 'relative')
902#: (:class:`tuple`) List of sparse color methods used by
903#: :class:`Image.sparse_color() <wand.image.BaseImage.sparse_color>`
904#:
905#: - ``'undefined'``
906#: - ``'barycentric'``
907#: - ``'bilinear'``
908#: - ``'shepards'``
909#: - ``'voronoi'``
910#: - ``'inverse'``
911#: - ``'manhattan'``
912#:
913#: .. versionadded:: 0.5.3
914SPARSE_COLOR_METHODS = dict(undefined=0, barycentric=1, bilinear=7,
915 shepards=16, voronoi=18, inverse=19,
916 manhattan=20)
919#: (:class:`tuple`) The list of statistic types used by
920#: :meth:`Image.statistic() <wand.image.BaseImage.statistic>`.
921#:
922#: - ``'undefined'``
923#: - ``'gradient'``
924#: - ``'maximum'``
925#: - ``'mean'``
926#: - ``'median'``
927#: - ``'minimum'``
928#: - ``'mode'``
929#: - ``'nonpeak'``
930#: - ``'root_mean_square'``
931#: - ``'standard_deviation'``
932#:
933#: .. versionadded:: 0.5.3
934STATISTIC_TYPES = ('undefined', 'gradient', 'maximum', 'mean', 'median',
935 'minimum', 'mode', 'nonpeak', 'standard_deviation',
936 'root_mean_square')
937if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
938 STATISTIC_TYPES = ('undefined', 'gradient', 'maximum', 'mean', 'median',
939 'minimum', 'mode', 'nonpeak', 'root_mean_square',
940 'standard_deviation')
943#: (:class:`tuple`) The list of pixel storage types.
944#:
945#: - ``'undefined'``
946#: - ``'char'``
947#: - ``'double'``
948#: - ``'float'``
949#: - ``'integer'``
950#: - ``'long'``
951#: - ``'quantum'``
952#: - ``'short'``
953#:
954#: .. versionadded:: 0.5.0
955STORAGE_TYPES = ('undefined', 'char', 'double', 'float', 'integer',
956 'long', 'quantum', 'short')
959#: (:class:`tuple`) The list of resolution unit types.
960#:
961#: - ``'undefined'``
962#: - ``'pixelsperinch'``
963#: - ``'pixelspercentimeter'``
964#:
965#: .. seealso::
966#:
967#: `ImageMagick Image Units`__
968#: Describes the MagickSetImageUnits method which can be used
969#: to set image units of resolution
970#:
971#: __ http://www.imagemagick.org/api/magick-image.php#MagickSetImageUnits
972UNIT_TYPES = ('undefined', 'pixelsperinch', 'pixelspercentimeter')
975#: (:class:`tuple`) The list of :attr:`~BaseImage.virtual_pixel` types.
976#:
977#: - ``'undefined'``
978#: - ``'background'``
979#: - ``'constant'`` - Only available with ImageMagick-6
980#: - ``'dither'``
981#: - ``'edge'``
982#: - ``'mirror'``
983#: - ``'random'``
984#: - ``'tile'``
985#: - ``'transparent'``
986#: - ``'mask'``
987#: - ``'black'``
988#: - ``'gray'``
989#: - ``'white'``
990#: - ``'horizontal_tile'``
991#: - ``'vertical_tile'``
992#: - ``'horizontal_tile_edge'``
993#: - ``'vertical_tile_edge'``
994#: - ``'checker_tile'``
995#:
996#: .. versionadded:: 0.4.1
997VIRTUAL_PIXEL_METHOD = ('undefined', 'background', 'constant', 'dither',
998 'edge', 'mirror', 'random', 'tile', 'transparent',
999 'mask', 'black', 'gray', 'white', 'horizontal_tile',
1000 'vertical_tile', 'horizontal_tile_edge',
1001 'vertical_tile_edge', 'checker_tile')
1002if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
1003 VIRTUAL_PIXEL_METHOD = ('undefined', 'background', 'dither',
1004 'edge', 'mirror', 'random', 'tile', 'transparent',
1005 'mask', 'black', 'gray', 'white',
1006 'horizontal_tile', 'vertical_tile',
1007 'horizontal_tile_edge', 'vertical_tile_edge',
1008 'checker_tile')
1011def manipulative(function):
1012 """Mark the operation manipulating itself instead of returning new one."""
1013 @functools.wraps(function)
1014 def wrapped(self, *args, **kwargs):
1015 result = function(self, *args, **kwargs)
1016 self.dirty = True
1017 return result
1018 return wrapped
1021def trap_exception(function):
1022 @functools.wraps(function)
1023 def wrapped(self, *args, **kwargs):
1024 result = function(self, *args, **kwargs)
1025 if not bool(result):
1026 self.raise_exception()
1027 return result
1028 return wrapped
1031class BaseImage(Resource):
1032 """The abstract base of :class:`Image` (container) and
1033 :class:`~wand.sequence.SingleImage`. That means the most of
1034 operations, defined in this abstract class, are possible for
1035 both :class:`Image` and :class:`~wand.sequence.SingleImage`.
1037 .. versionadded:: 0.3.0
1039 """
1041 #: (:class:`OptionDict`) The mapping of internal option settings.
1042 #:
1043 #: .. versionadded:: 0.3.0
1044 #:
1045 #: .. versionchanged:: 0.3.4
1046 #: Added ``'jpeg:sampling-factor'`` option.
1047 #:
1048 #: .. versionchanged:: 0.3.9
1049 #: Added ``'pdf:use-cropbox'`` option.
1050 options = None
1052 #: (:class:`collections.abc.Sequence`) The list of
1053 #: :class:`~wand.sequence.SingleImage`\ s that the image contains.
1054 #:
1055 #: .. versionadded:: 0.3.0
1056 sequence = None
1058 #: (:class:`bool`) Whether the image is changed or not.
1059 dirty = None
1061 #: (:class:`numbers.Integral`) Internal placeholder for
1062 #: :attr:`seed` property.
1063 #:
1064 #: .. versionadded:: 0.5.5
1065 _seed = None
1067 c_is_resource = library.IsMagickWand
1068 c_destroy_resource = library.DestroyMagickWand
1069 c_get_exception = library.MagickGetException
1070 c_clear_exception = library.MagickClearException
1072 __slots__ = '_wand',
1074 def __init__(self, wand):
1075 self.wand = wand
1076 self.channel_images = ChannelImageDict(self)
1077 self.channel_depths = ChannelDepthDict(self)
1078 self.options = OptionDict(self)
1079 self.dirty = False
1081 def __eq__(self, other):
1082 if isinstance(other, type(self)):
1083 return self.signature == other.signature
1084 return False
1086 def __getitem__(self, idx):
1087 if (not isinstance(idx, string_type) and
1088 isinstance(idx, abc.Iterable)):
1089 idx = tuple(idx)
1090 d = len(idx)
1091 if not (1 <= d <= 2):
1092 raise ValueError('index cannot be {0}-dimensional'.format(d))
1093 elif d == 2:
1094 x, y = idx
1095 x_slice = isinstance(x, slice)
1096 y_slice = isinstance(y, slice)
1097 if x_slice and not y_slice:
1098 y = slice(y, y + 1)
1099 elif not x_slice and y_slice:
1100 x = slice(x, x + 1)
1101 elif not (x_slice or y_slice):
1102 if not (isinstance(x, numbers.Integral) and
1103 isinstance(y, numbers.Integral)):
1104 raise TypeError('x and y must be integral, not ' +
1105 repr((x, y)))
1106 if x < 0:
1107 x += self.width
1108 if y < 0:
1109 y += self.height
1110 if x >= self.width:
1111 raise IndexError('x must be less than width')
1112 elif y >= self.height:
1113 raise IndexError('y must be less than height')
1114 elif x < 0:
1115 raise IndexError('x cannot be less than 0')
1116 elif y < 0:
1117 raise IndexError('y cannot be less than 0')
1118 with iter(self) as iterator:
1119 iterator.seek(y)
1120 return iterator.next(x)
1121 if not (x.step is None and y.step is None):
1122 raise ValueError('slicing with step is unsupported')
1123 elif (x.start is None and x.stop is None and
1124 y.start is None and y.stop is None):
1125 return self.clone()
1126 cloned = self.clone()
1127 try:
1128 cloned.crop(x.start, y.start, x.stop, y.stop)
1129 except ValueError as e:
1130 raise IndexError(str(e))
1131 return cloned
1132 else:
1133 return self[idx[0]]
1134 elif isinstance(idx, numbers.Integral):
1135 if idx < 0:
1136 idx += self.height
1137 elif idx >= self.height:
1138 raise IndexError('index must be less than height, but got ' +
1139 repr(idx))
1140 elif idx < 0:
1141 raise IndexError('index cannot be less than zero, but got ' +
1142 repr(idx))
1143 with iter(self) as iterator:
1144 iterator.seek(idx)
1145 return iterator.next()
1146 elif isinstance(idx, slice):
1147 return self[:, idx]
1148 raise TypeError('unsupported index type: ' + repr(idx))
1150 def __setitem__(self, idx, color):
1151 if isinstance(color, string_type):
1152 color = Color(color)
1153 assertions.assert_color(color=color)
1154 if not isinstance(idx, abc.Iterable):
1155 raise TypeError('Expecting list of x,y coordinates, not ' +
1156 repr(idx))
1157 idx = tuple(idx)
1158 if len(idx) != 2:
1159 msg = 'pixel index can not be {0}-dimensional'.format(len(idx))
1160 raise ValueError(msg)
1161 colorspace = self.colorspace
1162 s_index = STORAGE_TYPES.index("double")
1163 width, height = self.size
1164 x1, y1 = idx
1165 x2, y2 = 1, 1
1166 if not (isinstance(x1, numbers.Integral) and
1167 isinstance(y1, numbers.Integral)):
1168 raise TypeError('Expecting x & y to be integers')
1169 if x1 < 0:
1170 x1 += width
1171 if y1 < 0:
1172 y1 += height
1173 if x1 >= width:
1174 raise ValueError('x must be less then image width')
1175 elif y1 >= height:
1176 raise ValueError('y must be less then image height')
1177 if colorspace == 'gray':
1178 channel_map = b'I'
1179 pixel = (ctypes.c_double * 1)()
1180 pixel[0] = color.red
1181 elif colorspace == 'cmyk':
1182 channel_map = b'CMYK'
1183 pixel = (ctypes.c_double * 5)()
1184 pixel[0] = color.red
1185 pixel[1] = color.green
1186 pixel[2] = color.blue
1187 pixel[3] = color.black
1188 if self.alpha_channel:
1189 channel_map += b'A'
1190 pixel[4] = color.alpha
1191 else:
1192 channel_map = b'RGB'
1193 pixel = (ctypes.c_double * 4)()
1194 pixel[0] = color.red
1195 pixel[1] = color.green
1196 pixel[2] = color.blue
1197 if self.alpha_channel:
1198 channel_map += b'A'
1199 pixel[3] = color.alpha
1200 r = library.MagickImportImagePixels(self.wand,
1201 x1, y1, x2, y2,
1202 channel_map,
1203 s_index,
1204 ctypes.byref(pixel))
1205 if not r:
1206 self.raise_exception()
1208 def __hash__(self):
1209 return hash(self.signature)
1211 def __iter__(self):
1212 return Iterator(image=self)
1214 def __len__(self):
1215 return self.height
1217 def __ne__(self, other):
1218 return not (self == other)
1220 def __repr__(self, extra_format=' ({self.width}x{self.height})'):
1221 cls = type(self)
1222 typename = '{0}.{1}'.format(
1223 cls.__module__,
1224 getattr(cls, '__qualname__', cls.__name__)
1225 )
1226 if getattr(self, 'c_resource', None) is None:
1227 return '<{0}: (closed)>'.format(typename)
1228 sig = self.signature
1229 if not sig:
1230 return '<{0}: (empty)>'.format(typename)
1231 return '<{0}: {1}{2}>'.format(
1232 typename, sig[:7], extra_format.format(self=self)
1233 )
1235 @property
1236 def __array_interface__(self):
1237 """Allows image-data from :class:`Image <wand.image.BaseImage>`
1238 instances to be loaded into numpy's array.
1240 .. code::
1242 import numpy
1243 from wand.image import Image
1245 with Image(filename='rose:') as img:
1246 img_data = numpy.asarray(img)
1248 :raises ValueError: if image has no data.
1250 .. versionadded:: 0.5.0
1251 .. versionchanged:: 0.6.0
1252 The :attr:`shape` property is now ordered by ``height``, ``width``,
1253 and ``channel``.
1254 """
1255 if not self.signature:
1256 raise ValueError("No image data to interface with.")
1257 width, height = self.size
1258 storage_type = 1 # CharPixel
1259 channel_format = binary("RGB")
1260 channel_number = 3
1261 if self.alpha_channel:
1262 channel_format = binary("RGBA")
1263 channel_number = 4
1264 self._c_buffer = (width * height * channel_number * ctypes.c_char)()
1265 # FIXME: Move to pixel-data import/export methods.
1266 r = library.MagickExportImagePixels(self.wand,
1267 0, 0, width, height,
1268 channel_format, storage_type,
1269 ctypes.byref(self._c_buffer))
1270 if not r:
1271 self.raise_exception()
1272 return dict(data=(ctypes.addressof(self._c_buffer), True),
1273 shape=(height, width, channel_number),
1274 typestr='|u1',
1275 version=3)
1277 @property
1278 def alpha_channel(self):
1279 """(:class:`bool`) Get state of image alpha channel.
1280 It can also be used to enable/disable alpha channel, but with different
1281 behavior new, copied, or existing.
1283 Behavior of setting :attr:`alpha_channel` is defined with the
1284 following values:
1286 - ``'activate'``, ``'on'``, or :const:`True` will enable an images
1287 alpha channel. Existing alpha data is preserved.
1288 - ``'deactivate'``, ``'off'``, or :const:`False` will disable an images
1289 alpha channel. Any data on the alpha will be preserved.
1290 - ``'associate'`` & ``'disassociate'`` toggle alpha channel flag in
1291 certain image-file specifications.
1292 - ``'set'`` enables and resets any data in an images alpha channel.
1293 - ``'opaque'`` enables alpha/matte channel, and forces full opaque
1294 image.
1295 - ``'transparent'`` enables alpha/matte channel, and forces full
1296 transparent image.
1297 - ``'extract'`` copies data in alpha channel across all other channels,
1298 and disables alpha channel.
1299 - ``'copy'`` calculates the gray-scale of RGB channels,
1300 and applies it to alpha channel.
1301 - ``'shape'`` is identical to ``'copy'``, but will color the resulting
1302 image with the value defined with :attr:`background_color`.
1303 - ``'remove'`` will composite :attr:`background_color` value.
1304 - ``'background'`` replaces full-transparent color with background
1305 color.
1308 .. versionadded:: 0.2.1
1310 .. versionchanged:: 0.4.1
1311 Support for additional setting values.
1312 However :attr:`Image.alpha_channel` will continue to return
1313 :class:`bool` if the current alpha/matte state is enabled.
1315 .. versionchanged:: 0.6.0
1316 Setting the alpha channel will apply the change to all frames
1317 in the image stack.
1318 """
1319 return bool(library.MagickGetImageAlphaChannel(self.wand))
1321 @alpha_channel.setter
1322 @manipulative
1323 def alpha_channel(self, alpha_type):
1324 is_im6 = MAGICK_VERSION_NUMBER < 0x700
1325 # Map common aliases for ``'deactivate'``
1326 if alpha_type is False or (alpha_type == 'off' and is_im6):
1327 alpha_type = 'deactivate'
1328 # Map common aliases for ``'activate'``
1329 elif alpha_type is True or (alpha_type == 'on' and is_im6):
1330 alpha_type = 'activate'
1331 assertions.string_in_list(ALPHA_CHANNEL_TYPES,
1332 'wand.image.ALPHA_CHANNEL_TYPES',
1333 alpha_channel=alpha_type)
1334 alpha_index = ALPHA_CHANNEL_TYPES.index(alpha_type)
1335 library.MagickSetLastIterator(self.wand)
1336 n = library.MagickGetIteratorIndex(self.wand)
1337 library.MagickResetIterator(self.wand)
1338 for i in xrange(0, n + 1):
1339 library.MagickSetIteratorIndex(self.wand, i)
1340 library.MagickSetImageAlphaChannel(self.wand, alpha_index)
1342 @property
1343 def animation(self):
1344 """(:class:`bool`) Whether the image is animation or not.
1345 It doesn't only mean that the image has two or more images (frames),
1346 but all frames are even the same size. It's about image format,
1347 not content. It's :const:`False` even if :mimetype:`image/ico`
1348 consits of two or more images of the same size.
1350 For example, it's :const:`False` for :mimetype:`image/jpeg`,
1351 :mimetype:`image/gif`, :mimetype:`image/ico`.
1353 If :mimetype:`image/gif` has two or more frames, it's :const:`True`.
1354 If :mimetype:`image/gif` has only one frame, it's :const:`False`.
1356 .. versionadded:: 0.3.0
1358 .. versionchanged:: 0.3.8
1359 Became to accept :mimetype:`image/x-gif` as well.
1361 """
1362 return False
1364 @property
1365 def antialias(self):
1366 """(:class:`bool`) If vectors & fonts will use anti-aliasing.
1368 .. versionchanged:: 0.5.0
1369 Previously named :attr:`font_antialias`.
1370 """
1371 return bool(library.MagickGetAntialias(self.wand))
1373 @antialias.setter
1374 @manipulative
1375 def antialias(self, antialias):
1376 assertions.assert_bool(antialias=antialias)
1377 library.MagickSetAntialias(self.wand, antialias)
1379 @property
1380 def background_color(self):
1381 """(:class:`wand.color.Color`) The image background color.
1382 It can also be set to change the background color.
1384 .. versionadded:: 0.1.9
1386 """
1387 pixel = library.NewPixelWand()
1388 result = library.MagickGetImageBackgroundColor(self.wand, pixel)
1389 if not result: # pragma: no cover
1390 self.raise_exception()
1391 else:
1392 color = Color.from_pixelwand(pixel)
1393 pixel = library.DestroyPixelWand(pixel)
1394 return color
1396 @background_color.setter
1397 @manipulative
1398 def background_color(self, color):
1399 if isinstance(color, string_type):
1400 color = Color(color)
1401 assertions.assert_color(color=color)
1402 with color:
1403 result = library.MagickSetImageBackgroundColor(self.wand,
1404 color.resource)
1405 if not result: # pragma: no cover
1406 self.raise_exception()
1407 # Also set the image stack.
1408 result = library.MagickSetBackgroundColor(self.wand,
1409 color.resource)
1410 if not result:
1411 self.raise_exception()
1413 @property
1414 def blue_primary(self):
1415 """(:class:`tuple`) The chromatic blue primary point for the image.
1416 With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
1417 however, ImageMagick-7 has ``(x, y, z)``.
1419 .. versionadded:: 0.5.2
1420 """
1421 x = ctypes.c_double(0.0)
1422 y = ctypes.c_double(0.0)
1423 r = None
1424 p = None
1425 if MAGICK_VERSION_NUMBER < 0x700:
1426 r = library.MagickGetImageBluePrimary(self.wand, x, y)
1427 p = (x.value, y.value)
1428 else: # pragma: no cover
1429 z = ctypes.c_double(0.0)
1430 r = library.MagickGetImageBluePrimary(self.wand, x, y, z)
1431 p = (x.value, y.value, z.value)
1432 if not r: # pragma: no cover
1433 self.raise_exception()
1434 return p
1436 @blue_primary.setter
1437 def blue_primary(self, coordinates):
1438 r = None
1439 if not isinstance(coordinates, abc.Sequence):
1440 raise TypeError('Primary must be a tuple')
1441 if MAGICK_VERSION_NUMBER < 0x700:
1442 x, y = coordinates
1443 r = library.MagickSetImageBluePrimary(self.wand, x, y)
1444 else: # pragma: no cover
1445 x, y, z = coordinates
1446 r = library.MagickSetImageBluePrimary(self.wand, x, y, z)
1447 if not r: # pragma: no cover
1448 self.raise_exception()
1450 @property
1451 def border_color(self):
1452 """(:class:`wand.color.Color`) The image border color. Used for
1453 special effects like :meth:`polaroid()`.
1455 .. versionadded:: 0.5.4
1456 """
1457 pixel = library.NewPixelWand()
1458 result = library.MagickGetImageBorderColor(self.wand, pixel)
1459 if not result: # pragma: no cover
1460 self.raise_exception()
1461 else:
1462 color = Color.from_pixelwand(pixel)
1463 pixel = library.DestroyPixelWand(pixel)
1464 return color
1466 @border_color.setter
1467 def border_color(self, color):
1468 if isinstance(color, string_type):
1469 color = Color(color)
1470 assertions.assert_color(border_color=color)
1471 with color:
1472 r = library.MagickSetImageBorderColor(self.wand, color.resource)
1473 if not r: # pragma: no cover
1474 self.raise_exception()
1476 @property
1477 def colors(self):
1478 """(:class:`numbers.Integral`) Count of unique colors used within the
1479 image. This is READ ONLY property.
1481 .. versionadded:: 0.5.3
1482 """
1483 return library.MagickGetImageColors(self.wand)
1485 @property
1486 def colorspace(self):
1487 """(:class:`basestring`) The image colorspace.
1489 Defines image colorspace as in :const:`COLORSPACE_TYPES` enumeration.
1491 It may raise :exc:`ValueError` when the colorspace is unknown.
1493 .. versionadded:: 0.3.4
1495 """
1496 colorspace_type_index = library.MagickGetImageColorspace(self.wand)
1497 if not colorspace_type_index: # pragma: no cover
1498 self.raise_exception()
1499 return COLORSPACE_TYPES[text(colorspace_type_index)]
1501 @colorspace.setter
1502 @manipulative
1503 def colorspace(self, colorspace_type):
1504 assertions.string_in_list(COLORSPACE_TYPES,
1505 'wand.image.COLORSPACE_TYPES',
1506 colorspace=colorspace_type)
1507 r = library.MagickSetImageColorspace(
1508 self.wand,
1509 COLORSPACE_TYPES.index(colorspace_type)
1510 )
1511 if not r: # pragma: no cover
1512 self.raise_exception()
1514 @property
1515 def compose(self):
1516 """(:class:`basestring`) The type of image compose.
1517 It's a string from :const:`COMPOSITE_OPERATORS` list.
1518 It also can be set.
1520 .. versionadded:: 0.5.1
1521 """
1522 compose_index = library.MagickGetImageCompose(self.wand)
1523 return COMPOSITE_OPERATORS[compose_index]
1525 @compose.setter
1526 def compose(self, operator):
1527 assertions.string_in_list(COMPOSITE_OPERATORS,
1528 'wand.image.COMPOSITE_OPERATORS',
1529 compose=operator)
1530 library.MagickSetImageCompose(self.wand,
1531 COMPOSITE_OPERATORS.index(operator))
1533 @property
1534 def compression(self):
1535 """(:class:`basestring`) The type of image compression.
1536 It's a string from :const:`COMPRESSION_TYPES` list.
1537 It also can be set.
1539 .. versionadded:: 0.3.6
1540 .. versionchanged:: 0.5.2
1541 Setting :attr:`compression` now sets both `image_info`
1542 and `images` in the internal image stack.
1543 """
1544 compression_index = library.MagickGetImageCompression(self.wand)
1545 return COMPRESSION_TYPES[compression_index]
1547 @compression.setter
1548 def compression(self, value):
1549 assertions.string_in_list(COMPRESSION_TYPES,
1550 'wand.image.COMPRESSION_TYPES',
1551 compression=value)
1552 library.MagickSetCompression(
1553 self.wand,
1554 COMPRESSION_TYPES.index(value)
1555 )
1556 library.MagickSetImageCompression(
1557 self.wand,
1558 COMPRESSION_TYPES.index(value)
1559 )
1561 @property
1562 def compression_quality(self):
1563 """(:class:`numbers.Integral`) Compression quality of this image.
1565 .. versionadded:: 0.2.0
1566 .. versionchanged:: 0.5.2
1567 Setting :attr:`compression_quality` now sets both `image_info`
1568 and `images` in the internal image stack.
1569 """
1570 return library.MagickGetImageCompressionQuality(self.wand)
1572 @compression_quality.setter
1573 @manipulative
1574 def compression_quality(self, quality):
1575 """Set compression quality for the image.
1577 :param quality: new compression quality setting
1578 :type quality: :class:`numbers.Integral`
1580 """
1581 assertions.assert_integer(compression_quality=quality)
1582 library.MagickSetCompressionQuality(self.wand, quality)
1583 r = library.MagickSetImageCompressionQuality(self.wand, quality)
1584 if not r: # pragma: no cover
1585 raise ValueError('Unable to set compression quality to ' +
1586 repr(quality))
1588 @property
1589 def delay(self):
1590 """(:class:`numbers.Integral`) The number of ticks between frames.
1592 .. versionadded:: 0.5.9
1593 """
1594 return library.MagickGetImageDelay(self.wand)
1596 @delay.setter
1597 def delay(self, value):
1598 assertions.assert_integer(delay=value)
1599 library.MagickSetImageDelay(self.wand, value)
1601 @property
1602 def depth(self):
1603 """(:class:`numbers.Integral`) The depth of this image.
1605 .. versionadded:: 0.2.1
1607 """
1608 return library.MagickGetImageDepth(self.wand)
1610 @depth.setter
1611 @manipulative
1612 def depth(self, depth):
1613 r = library.MagickSetImageDepth(self.wand, depth)
1614 if not r: # pragma: no cover
1615 raise self.raise_exception()
1617 @property
1618 def dispose(self):
1619 """(:class:`basestring`) Controls how the image data is
1620 handled during animations. Values are from :const:`DISPOSE_TYPES`
1621 list, and can also be set.
1623 .. seealso::
1625 `Dispose Images`__ section in ``Animation Basics`` article.
1627 __ https://www.imagemagick.org/Usage/anim_basics/#dispose_images
1629 .. versionadded:: 0.5.0
1630 """
1631 dispose_idx = library.MagickGetImageDispose(self.wand)
1632 try:
1633 return DISPOSE_TYPES[dispose_idx]
1634 except IndexError: # pragma: no cover
1635 return DISPOSE_TYPES[0]
1637 @dispose.setter
1638 def dispose(self, value):
1639 assertions.string_in_list(DISPOSE_TYPES,
1640 'wand.image.DISPOSE_TYPES',
1641 dispose=value)
1642 library.MagickSetImageDispose(self.wand, DISPOSE_TYPES.index(value))
1644 @property
1645 def font(self):
1646 """(:class:`wand.font.Font`) The current font options."""
1647 if not self.font_path:
1648 return None
1649 return Font(
1650 path=text(self.font_path),
1651 size=self.font_size,
1652 color=self.font_color,
1653 antialias=self.antialias,
1654 stroke_color=self.stroke_color,
1655 stroke_width=self.stroke_width
1656 )
1658 @font.setter
1659 @manipulative
1660 def font(self, font):
1661 if not isinstance(font, Font):
1662 raise TypeError('font must be a wand.font.Font, not ' + repr(font))
1663 self.font_path = font.path
1664 self.font_size = font.size
1665 self.font_color = font.color
1666 self.antialias = font.antialias
1667 if font.stroke_color:
1668 self.stroke_color = font.stroke_color
1669 if font.stroke_width is not None:
1670 self.stroke_width = font.stroke_width
1672 @property
1673 def font_antialias(self):
1674 """
1675 .. deprecated:: 0.5.0
1676 Use :attr:`antialias` instead.
1677 """
1678 return self.antialias
1680 @font_antialias.setter
1681 def font_antialias(self, antialias):
1682 self.antialias = antialias
1684 @property
1685 def font_color(self):
1686 return Color(self.options['fill'])
1688 @font_color.setter
1689 @manipulative
1690 def font_color(self, color):
1691 if isinstance(color, string_type):
1692 color = Color(color)
1693 assertions.assert_color(font_color=color)
1694 self.options['fill'] = color.string
1696 @property
1697 def font_path(self):
1698 """(:class:`basestring`) The path of the current font.
1699 It also can be set.
1701 """
1702 return text(library.MagickGetFont(self.wand))
1704 @font_path.setter
1705 @manipulative
1706 def font_path(self, font):
1707 font = binary(font)
1708 r = library.MagickSetFont(self.wand, font)
1709 if not r: # pragma: no cover
1710 self.raise_exception()
1712 @property
1713 def font_size(self):
1714 """(:class:`numbers.Real`) The font size. It also can be set."""
1715 return library.MagickGetPointsize(self.wand)
1717 @font_size.setter
1718 @manipulative
1719 def font_size(self, size):
1720 assertions.assert_real(font_size=size)
1721 if size < 0.0:
1722 raise ValueError('cannot be less then 0.0, but got ' + repr(size))
1723 r = library.MagickSetPointsize(self.wand, size)
1724 if not r: # pragma: no cover
1725 self.raise_exception()
1727 @property
1728 def format(self):
1729 """(:class:`basestring`) The image format.
1731 If you want to convert the image format, just reset this property::
1733 assert isinstance(img, wand.image.Image)
1734 img.format = 'png'
1736 It may raise :exc:`ValueError` when the format is unsupported.
1738 .. seealso::
1740 `ImageMagick Image Formats`__
1741 ImageMagick uses an ASCII string known as *magick* (e.g. ``GIF``)
1742 to identify file formats, algorithms acting as formats,
1743 built-in patterns, and embedded profile types.
1745 __ http://www.imagemagick.org/script/formats.php
1747 .. versionadded:: 0.1.6
1749 """
1750 fmt = library.MagickGetImageFormat(self.wand)
1751 if bool(fmt):
1752 return text(fmt.value)
1753 else: # pragma: no cover
1754 self.raise_exception()
1756 @format.setter
1757 def format(self, fmt):
1758 assertions.assert_string(format=fmt)
1759 fmt = fmt.strip()
1760 r = library.MagickSetImageFormat(self.wand, binary(fmt.upper()))
1761 if not r:
1762 raise ValueError(repr(fmt) + ' is unsupported format')
1763 r = library.MagickSetFilename(self.wand,
1764 b'buffer.' + binary(fmt.lower()))
1765 if not r: # pragma: no cover
1766 self.raise_exception()
1768 @property
1769 def fuzz(self):
1770 """(:class:`numbers.Real`) The normalized real number between ``0.0``
1771 and :attr:`quantum_range`. This property influences the accuracy of
1772 :meth:`compare()`.
1774 .. versionadded:: 0.5.3
1775 """
1776 return library.MagickGetImageFuzz(self.wand)
1778 @fuzz.setter
1779 def fuzz(self, value):
1780 assertions.assert_real(fuzz=value)
1781 library.MagickSetImageFuzz(self.wand, value)
1783 @property
1784 def gravity(self):
1785 """(:class:`basestring`) The text placement gravity used when
1786 annotating with text. It's a string from :const:`GRAVITY_TYPES`
1787 list. It also can be set.
1789 """
1790 gravity_index = library.MagickGetGravity(self.wand)
1791 if not gravity_index: # pragma: no cover
1792 self.raise_exception()
1793 return GRAVITY_TYPES[gravity_index]
1795 @gravity.setter
1796 @manipulative
1797 def gravity(self, value):
1798 assertions.string_in_list(GRAVITY_TYPES,
1799 'wand.image.GRAVITY_TYPES',
1800 gravity=value)
1801 library.MagickSetGravity(self.wand, GRAVITY_TYPES.index(value))
1803 @property
1804 def green_primary(self):
1805 """(:class:`tuple`) The chromatic green primary point for the image.
1806 With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
1807 however, ImageMagick-7 has ``(x, y, z)``.
1809 .. versionadded:: 0.5.2
1810 """
1811 x = ctypes.c_double(0.0)
1812 y = ctypes.c_double(0.0)
1813 r = None
1814 p = None
1815 if MAGICK_VERSION_NUMBER < 0x700:
1816 r = library.MagickGetImageGreenPrimary(self.wand, x, y)
1817 p = (x.value, y.value)
1818 else: # pragma: no cover
1819 z = ctypes.c_double(0.0)
1820 r = library.MagickGetImageGreenPrimary(self.wand, x, y, z)
1821 p = (x.value, y.value, z.value)
1822 if not r: # pragma: no cover
1823 self.raise_exception()
1824 return p
1826 @green_primary.setter
1827 def green_primary(self, coordinates):
1828 r = None
1829 if not isinstance(coordinates, abc.Sequence):
1830 raise TypeError('Primary must be a tuple')
1831 if MAGICK_VERSION_NUMBER < 0x700:
1832 x, y = coordinates
1833 r = library.MagickSetImageGreenPrimary(self.wand, x, y)
1834 else: # pragma: no cover
1835 x, y, z = coordinates
1836 r = library.MagickSetImageGreenPrimary(self.wand, x, y, z)
1837 if not r: # pragma: no cover
1838 self.raise_exception()
1840 @property
1841 def height(self):
1842 """(:class:`numbers.Integral`) The height of this image."""
1843 return library.MagickGetImageHeight(self.wand)
1845 @height.setter
1846 @manipulative
1847 def height(self, height):
1848 assertions.assert_unsigned_integer(height=height)
1849 library.MagickSetSize(self.wand, self.width, height)
1851 @property
1852 def histogram(self):
1853 """(:class:`HistogramDict`) The mapping that represents the histogram.
1854 Keys are :class:`~wand.color.Color` objects, and values are
1855 the number of pixels.
1857 .. tip::
1859 True-color photos can have millions of color values. If performance
1860 is more valuable than accuracy, remember to :meth:`quantize` the
1861 image before generating a :class:`HistogramDict`.
1863 with Image(filename='hd_photo.jpg') as img:
1864 img.quantize(255, 'RGB', 0, False, False)
1865 hist = img.histogram
1867 .. versionadded:: 0.3.0
1869 """
1870 return HistogramDict(self)
1872 @property
1873 def interlace_scheme(self):
1874 """(:class:`basestring`) The interlace used by the image.
1875 See :const:`INTERLACE_TYPES`.
1877 .. versionadded:: 0.5.2
1878 """
1879 scheme_idx = library.MagickGetInterlaceScheme(self.wand)
1880 return INTERLACE_TYPES[scheme_idx]
1882 @interlace_scheme.setter
1883 def interlace_scheme(self, scheme):
1884 assertions.string_in_list(INTERLACE_TYPES,
1885 'wand.image.INTERLACE_TYPES',
1886 interlace_scheme=scheme)
1887 scheme_idx = INTERLACE_TYPES.index(scheme)
1888 library.MagickSetInterlaceScheme(self.wand, scheme_idx)
1890 @property
1891 def interpolate_method(self):
1892 """(:class:`basestring`) The interpolation method of the image.
1893 See :const:`PIXEL_INTERPOLATE_METHODS`.
1895 .. versionadded:: 0.5.2
1896 """
1897 method_idx = library.MagickGetImageInterpolateMethod(self.wand)
1898 return PIXEL_INTERPOLATE_METHODS[method_idx]
1900 @interpolate_method.setter
1901 def interpolate_method(self, method):
1902 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
1903 'wand.image.PIXEL_INTERPOLATE_METHODS',
1904 interpolate_method=method)
1905 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
1906 library.MagickSetImageInterpolateMethod(self.wand, method_idx)
1908 @property
1909 def kurtosis(self):
1910 """(:class:`numbers.Real`) The kurtosis of the image.
1912 .. tip::
1914 If you want both :attr:`kurtosis` & :attr:`skewness`, it
1915 would be faster to call :meth:`kurtosis_channel()` directly.
1917 .. versionadded:: 0.5.3
1918 """
1919 k, _ = self.kurtosis_channel()
1920 return k
1922 @property
1923 def length_of_bytes(self):
1924 """(:class:`numbers.Integral`) The original size, in bytes,
1925 of the image read. This will return `0` if the image was modified in
1926 a way that would invalidate the original length value.
1928 .. versionadded:: 0.5.4
1929 """
1930 size_ptr = ctypes.c_size_t(0)
1931 library.MagickGetImageLength(self.wand, ctypes.byref(size_ptr))
1932 return size_ptr.value
1934 @property
1935 def loop(self):
1936 """(:class:`numbers.Integral`) Number of frame iterations.
1937 A value of ``0`` will loop forever."""
1938 return library.MagickGetImageIterations(self.wand)
1940 @loop.setter
1941 def loop(self, iterations):
1942 assertions.assert_unsigned_integer(loop=iterations)
1943 library.MagickSetImageIterations(self.wand, iterations)
1945 @property
1946 def matte_color(self):
1947 """(:class:`wand.color.Color`) The color value of the matte channel.
1948 This can also be set.
1950 .. versionadded:: 0.4.1
1951 """
1952 pixel = library.NewPixelWand()
1953 result = library.MagickGetImageMatteColor(self.wand, pixel)
1954 if result:
1955 color = Color.from_pixelwand(pixel)
1956 pixel = library.DestroyPixelWand(pixel)
1957 return color
1958 else: # pragma: no cover
1959 self.raise_exception()
1961 @matte_color.setter
1962 @manipulative
1963 def matte_color(self, color):
1964 if isinstance(color, string_type):
1965 color = Color(color)
1966 assertions.assert_color(matte_color=color)
1967 with color:
1968 result = library.MagickSetImageMatteColor(self.wand,
1969 color.resource)
1970 if not result: # pragma: no cover
1971 self.raise_exception()
1973 @property
1974 def maxima(self):
1975 """(:class:`numbers.Real`) The maximum quantum value within the image.
1976 Value between 0.0 and :attr:`quantum_range`
1978 .. tip::
1980 If you want both :attr:`maxima` & :attr:`minima`,
1981 it would be faster to call :meth:`range_channel()` directly.
1983 .. versionadded:: 0.5.3
1984 """
1985 _, max_q = self.range_channel()
1986 return max_q
1988 @property
1989 def mean(self):
1990 """(:class:`numbers.Real`) The mean of the image, and have a value
1991 between 0.0 and :attr:`quantum_range`
1993 .. tip::
1995 If you want both :attr:`mean` & :attr:`standard_deviation`, it
1996 would be faster to call :meth:`mean_channel()` directly.
1998 .. versionadded:: 0.5.3
1999 """
2000 m, _ = self.mean_channel()
2001 return m
2003 @property
2004 def minima(self):
2005 """(:class:`numbers.Real`) The minimum quantum value within the image.
2006 Value between 0.0 and :attr:`quantum_range`
2008 .. tip::
2010 If you want both :attr:`maxima` & :attr:`minima`,
2011 it would be faster to call :meth:`range_channel()` directly.
2013 .. versionadded:: 0.5.3
2014 """
2015 min_q, _ = self.range_channel()
2016 return min_q
2018 @property
2019 def orientation(self):
2020 """(:class:`basestring`) The image orientation. It's a string from
2021 :const:`ORIENTATION_TYPES` list. It also can be set.
2023 .. versionadded:: 0.3.0
2025 """
2026 orientation_index = library.MagickGetImageOrientation(self.wand)
2027 try:
2028 return ORIENTATION_TYPES[orientation_index]
2029 except IndexError: # pragma: no cover
2030 return ORIENTATION_TYPES[0]
2032 @orientation.setter
2033 @manipulative
2034 def orientation(self, value):
2035 assertions.string_in_list(ORIENTATION_TYPES,
2036 'wand.image.ORIENTATION_TYPES',
2037 orientation=value)
2038 index = ORIENTATION_TYPES.index(value)
2039 library.MagickSetImageOrientation(self.wand, index)
2041 @property
2042 def page(self):
2043 """The dimensions and offset of this Wand's page as a 4-tuple:
2044 ``(width, height, x, y)``.
2046 Note that since it is based on the virtual canvas, it may not equal the
2047 dimensions of an image. See the ImageMagick documentation on the
2048 virtual canvas for more information.
2050 .. versionadded:: 0.4.3
2052 """
2053 w = ctypes.c_uint()
2054 h = ctypes.c_uint()
2055 x = ctypes.c_int()
2056 y = ctypes.c_int()
2057 r = library.MagickGetImagePage(self.wand, w, h, x, y)
2058 if not r: # pragma: no cover
2059 self.raise_exception()
2060 return int(w.value), int(h.value), int(x.value), int(y.value)
2062 @page.setter
2063 @manipulative
2064 def page(self, newpage):
2065 if isinstance(newpage, abc.Sequence):
2066 w, h, x, y = newpage
2067 else:
2068 raise TypeError("page layout must be 4-tuple")
2069 r = library.MagickSetImagePage(self.wand, w, h, x, y)
2070 if not r: # pragma: no cover
2071 self.raise_exception()
2073 @property
2074 def page_height(self):
2075 """(:class:`numbers.Integral`) The height of the page for this wand.
2077 .. versionadded:: 0.4.3
2079 """
2080 return self.page[1]
2082 @page_height.setter
2083 @manipulative
2084 def page_height(self, height):
2085 newpage = list(self.page)
2086 newpage[1] = height
2087 self.page = newpage
2089 @property
2090 def page_width(self):
2091 """(:class:`numbers.Integral`) The width of the page for this wand.
2093 .. versionadded:: 0.4.3
2095 """
2096 return self.page[0]
2098 @page_width.setter
2099 @manipulative
2100 def page_width(self, width):
2101 newpage = list(self.page)
2102 newpage[0] = width
2103 self.page = newpage
2105 @property
2106 def page_x(self):
2107 """(:class:`numbers.Integral`) The X-offset of the page for this wand.
2109 .. versionadded:: 0.4.3
2111 """
2112 return self.page[2]
2114 @page_x.setter
2115 @manipulative
2116 def page_x(self, x):
2117 newpage = list(self.page)
2118 newpage[2] = x
2119 self.page = newpage
2121 @property
2122 def page_y(self):
2123 """(:class:`numbers.Integral`) The Y-offset of the page for this wand.
2125 .. versionadded:: 0.4.3
2127 """
2128 return self.page[3]
2130 @page_y.setter
2131 @manipulative
2132 def page_y(self, y):
2133 newpage = list(self.page)
2134 newpage[3] = y
2135 self.page = newpage
2137 @property
2138 def quantum_range(self):
2139 """(:class:`int`) The maximum value of a color channel that is
2140 supported by the imagemagick library.
2142 .. versionadded:: 0.2.0
2144 """
2145 result = ctypes.c_size_t()
2146 library.MagickGetQuantumRange(ctypes.byref(result))
2147 return result.value
2149 @property
2150 def red_primary(self):
2151 """(:class:`tuple`) The chromatic red primary point for the image.
2152 With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
2153 however, ImageMagick-7 has ``(x, y, z)``.
2155 .. versionadded:: 0.5.2
2156 """
2157 x = ctypes.c_double(0.0)
2158 y = ctypes.c_double(0.0)
2159 r = None
2160 p = None
2161 if MAGICK_VERSION_NUMBER < 0x700:
2162 r = library.MagickGetImageRedPrimary(self.wand, x, y)
2163 p = (x.value, y.value)
2164 else: # pragma: no cover
2165 z = ctypes.c_double(0.0)
2166 r = library.MagickGetImageRedPrimary(self.wand, x, y, z)
2167 p = (x.value, y.value, z.value)
2168 if not r: # pragma: no cover
2169 self.raise_exception()
2170 return p
2172 @red_primary.setter
2173 def red_primary(self, coordinates):
2174 r = None
2175 if not isinstance(coordinates, abc.Sequence):
2176 raise TypeError('Primary must be a tuple')
2177 if MAGICK_VERSION_NUMBER < 0x700:
2178 x, y = coordinates
2179 r = library.MagickSetImageRedPrimary(self.wand, x, y)
2180 else: # pragma: no cover
2181 x, y, z = coordinates
2182 r = library.MagickSetImageRedPrimary(self.wand, x, y, z)
2183 if not r: # pragma: no cover
2184 self.raise_exception()
2186 @property
2187 def rendering_intent(self):
2188 """(:class:`basestring`) PNG rendering intent. See
2189 :const:`RENDERING_INTENT_TYPES` for valid options.
2191 .. versionadded:: 0.5.4
2192 """
2193 ri_index = library.MagickGetImageRenderingIntent(self.wand)
2194 return RENDERING_INTENT_TYPES[ri_index]
2196 @rendering_intent.setter
2197 def rendering_intent(self, value):
2198 assertions.string_in_list(RENDERING_INTENT_TYPES,
2199 'wand.image.RENDERING_INTENT_TYPES',
2200 rendering_intent=value)
2201 ri_index = RENDERING_INTENT_TYPES.index(value)
2202 library.MagickSetImageRenderingIntent(self.wand, ri_index)
2204 @property
2205 def resolution(self):
2206 """(:class:`tuple`) Resolution of this image.
2208 .. versionadded:: 0.3.0
2210 .. versionchanged:: 0.5.8
2211 Resolution returns a tuple of float values to
2212 match ImageMagick's behavior.
2213 """
2214 x = ctypes.c_double(0.0)
2215 y = ctypes.c_double(0.0)
2216 r = library.MagickGetImageResolution(self.wand, x, y)
2217 if not r: # pragma: no cover
2218 self.raise_exception()
2219 return x.value, y.value
2221 @resolution.setter
2222 @manipulative
2223 def resolution(self, geometry):
2224 if isinstance(geometry, abc.Sequence):
2225 x, y = geometry
2226 elif isinstance(geometry, numbers.Real):
2227 x, y = geometry, geometry
2228 else:
2229 raise TypeError('resolution must be a (x, y) pair or a float '
2230 'of the same x/y')
2231 if self.size == (0, 0):
2232 r = library.MagickSetResolution(self.wand, x, y)
2233 else:
2234 r = library.MagickSetImageResolution(self.wand, x, y)
2235 if not r: # pragma: no cover
2236 self.raise_exception()
2238 @property
2239 def scene(self):
2240 """(:class:`numbers.Integral`) The scene number of the current frame
2241 within an animated image.
2243 .. versionadded:: 0.5.4
2244 """
2245 return library.MagickGetImageScene(self.wand)
2247 @scene.setter
2248 def scene(self, value):
2249 assertions.assert_unsigned_integer(scene=value)
2250 library.MagickSetImageScene(self.wand, value)
2252 @property
2253 def seed(self):
2254 """(:class:`numbers.Integral`) The seed for random number generator.
2256 .. warning::
2258 This property is only available with ImageMagick 7.0.8-41, or
2259 greater.
2261 .. versionadded:: 0.5.5
2262 """
2263 return self._seed
2265 @seed.setter
2266 def seed(self, value):
2267 if library.MagickSetSeed is None:
2268 msg = 'Property requires ImageMagick version 7.0.8-41 or greater.'
2269 raise WandLibraryVersionError(msg)
2270 assertions.assert_unsigned_integer(seed=value)
2271 self._seed = value
2272 library.MagickSetSeed(self.wand, value)
2274 @property
2275 def signature(self):
2276 """(:class:`str`) The SHA-256 message digest for the image pixel
2277 stream.
2279 .. versionadded:: 0.1.9
2281 """
2282 signature = library.MagickGetImageSignature(self.wand)
2283 return text(signature.value)
2285 @property
2286 def size(self):
2287 """(:class:`tuple`) The pair of (:attr:`width`, :attr:`height`).
2289 .. note::
2291 When working with animations, or other layer-based image formats,
2292 the :attr:`width` & :attr:`height` properties are referencing the
2293 last frame read into the image stack. To get the :attr:`size`
2294 of the entire animated images, call
2295 :meth:`Image.coalesce() <wand.image.BaseImage.coalesce>` method
2296 immediately after reading the image.
2297 """
2298 return self.width, self.height
2300 @property
2301 def skewness(self):
2302 """(:class:`numbers.Real`) The skewness of the image.
2304 .. tip::
2306 If you want both :attr:`kurtosis` & :attr:`skewness`, it
2307 would be faster to call :meth:`kurtosis_channel()` directly.
2309 .. versionadded:: 0.5.3
2310 """
2311 _, s = self.kurtosis_channel()
2312 return s
2314 @property
2315 def standard_deviation(self):
2316 """(:class:`numbers.Real`) The standard deviation of the image.
2318 .. tip::
2320 If you want both :attr:`mean` & :attr:`standard_deviation`, it
2321 would be faster to call :meth:`mean_channel()` directly.
2323 .. versionadded:: 0.5.3
2324 """
2325 _, s = self.mean_channel()
2326 return s
2328 @property
2329 def stroke_color(self):
2330 stroke = self.options['stroke']
2331 return Color(stroke) if stroke else None
2333 @stroke_color.setter
2334 @manipulative
2335 def stroke_color(self, color):
2336 if isinstance(color, string_type):
2337 color = Color(color)
2338 if isinstance(color, Color):
2339 self.options['stroke'] = color.string
2340 elif color is None:
2341 del self.options['stroke']
2342 else:
2343 raise TypeError('stroke_color must be a wand.color.Color, not ' +
2344 repr(color))
2346 @property
2347 def stroke_width(self):
2348 strokewidth = self.options['strokewidth']
2349 return float(strokewidth) if strokewidth else None
2351 @stroke_width.setter
2352 @manipulative
2353 def stroke_width(self, width):
2354 assertions.assert_real(stroke_width=width)
2355 self.options['strokewidth'] = str(width)
2357 @property
2358 def ticks_per_second(self):
2359 """(:class:`numbers.Integral`) Internal clock for animated images.
2360 .. versionadded:: 0.5.4
2361 """
2362 return library.MagickGetImageTicksPerSecond(self.wand)
2364 @ticks_per_second.setter
2365 def ticks_per_second(self, value):
2366 assertions.assert_unsigned_integer(ticks_per_second=value)
2367 r = library.MagickSetImageTicksPerSecond(self.wand, value)
2368 if not r: # pragma: no cover
2369 self.raise_exception()
2371 @property
2372 def type(self):
2373 """(:class:`basestring`) The image type.
2375 Defines image type as in :const:`IMAGE_TYPES` enumeration.
2377 It may raise :exc:`ValueError` when the type is unknown.
2379 .. versionadded:: 0.2.2
2381 """
2382 image_type_index = library.MagickGetImageType(self.wand)
2383 if not image_type_index: # pragma: no cover
2384 self.raise_exception()
2385 return IMAGE_TYPES[text(image_type_index)]
2387 @type.setter
2388 @manipulative
2389 def type(self, image_type):
2390 assertions.string_in_list(IMAGE_TYPES, 'wand.image.IMAGE_TYPES',
2391 type=image_type)
2392 r = library.MagickSetImageType(self.wand,
2393 IMAGE_TYPES.index(image_type))
2394 if not r: # pragma: no cover
2395 self.raise_exception()
2397 @property
2398 def units(self):
2399 """(:class:`basestring`) The resolution units of this image."""
2400 r = library.MagickGetImageUnits(self.wand)
2401 return UNIT_TYPES[text(r)]
2403 @units.setter
2404 @manipulative
2405 def units(self, units):
2406 assertions.string_in_list(UNIT_TYPES, 'wand.image.UNIT_TYPES',
2407 units=units)
2408 r = library.MagickSetImageUnits(self.wand, UNIT_TYPES.index(units))
2409 if not r: # pragma: no cover
2410 self.raise_exception()
2412 @property
2413 def virtual_pixel(self):
2414 """(:class:`basestring`) The virtual pixel of image.
2415 This can also be set with a value from :const:`VIRTUAL_PIXEL_METHOD`
2416 ... versionadded:: 0.4.1
2417 """
2418 method_index = library.MagickGetImageVirtualPixelMethod(self.wand)
2419 return VIRTUAL_PIXEL_METHOD[method_index]
2421 @virtual_pixel.setter
2422 def virtual_pixel(self, method):
2423 assertions.string_in_list(VIRTUAL_PIXEL_METHOD,
2424 'wand.image.VIRTUAL_PIXEL_METHOD',
2425 virtual_pixel=method)
2426 library.MagickSetImageVirtualPixelMethod(
2427 self.wand,
2428 VIRTUAL_PIXEL_METHOD.index(method)
2429 )
2431 @property
2432 def wand(self):
2433 """Internal pointer to the MagickWand instance. It may raise
2434 :exc:`ClosedImageError` when the instance has destroyed already.
2436 """
2437 try:
2438 return self.resource
2439 except DestroyedResourceError:
2440 raise ClosedImageError(repr(self) + ' is closed already')
2442 @wand.setter
2443 def wand(self, wand):
2444 try:
2445 self.resource = wand
2446 except TypeError:
2447 raise TypeError(repr(wand) + ' is not a MagickWand instance')
2449 @wand.deleter
2450 def wand(self):
2451 del self.resource
2453 @property
2454 def width(self):
2455 """(:class:`numbers.Integral`) The width of this image."""
2456 return library.MagickGetImageWidth(self.wand)
2458 @width.setter
2459 @manipulative
2460 def width(self, width):
2461 assertions.assert_unsigned_integer(width=width)
2462 library.MagickSetSize(self.wand, width, self.height)
2464 @property
2465 def white_point(self):
2466 """(:class:`tuple`) The chromatic white point for the image.
2467 With ImageMagick-6 the primary value is ``(x, y)`` coordinates;
2468 however, ImageMagick-7 has ``(x, y, z)``.
2470 .. versionadded:: 0.5.2
2471 """
2472 x = ctypes.c_double(0.0)
2473 y = ctypes.c_double(0.0)
2474 r = None
2475 p = None
2476 if MAGICK_VERSION_NUMBER < 0x700:
2477 r = library.MagickGetImageWhitePoint(self.wand, x, y)
2478 p = (x.value, y.value)
2479 else: # pragma: no cover
2480 z = ctypes.c_double(0.0)
2481 r = library.MagickGetImageWhitePoint(self.wand, x, y, z)
2482 p = (x.value, y.value, z.value)
2483 if not r: # pragma: no cover
2484 self.raise_exception()
2485 return p
2487 @white_point.setter
2488 def white_point(self, coordinates):
2489 r = None
2490 if not isinstance(coordinates, abc.Sequence):
2491 raise TypeError('Primary must be a tuple')
2492 if MAGICK_VERSION_NUMBER < 0x700:
2493 x, y = coordinates
2494 r = library.MagickSetImageWhitePoint(self.wand, x, y)
2495 else: # pragma: no cover
2496 x, y, z = coordinates
2497 r = library.MagickSetImageWhitePoint(self.wand, x, y, z)
2498 if not r: # pragma: no cover
2499 self.raise_exception()
2501 @manipulative
2502 def _auto_orient(self):
2503 """Fallback for :attr:`auto_orient()` method
2504 (which wraps :c:func:`MagickAutoOrientImage`),
2505 fixes orientation by checking EXIF data.
2507 .. versionadded:: 0.4.1
2509 """
2510 v_ptr = library.MagickGetImageProperty(self.wand,
2511 b'exif:orientation')
2512 if v_ptr:
2513 exif_orientation = v_ptr.value
2514 else:
2515 return
2517 if not exif_orientation:
2518 return
2520 orientation_type = ORIENTATION_TYPES[int(exif_orientation)]
2522 fn_lookup = {
2523 'undefined': None,
2524 'top_left': None,
2525 'top_right': self.flop,
2526 'bottom_right': functools.partial(self.rotate, degree=180.0),
2527 'bottom_left': self.flip,
2528 'left_top': self.transpose,
2529 'right_top': functools.partial(self.rotate, degree=90.0),
2530 'right_bottom': self.transverse,
2531 'left_bottom': functools.partial(self.rotate, degree=270.0)
2532 }
2534 fn = fn_lookup.get(orientation_type)
2536 if not fn:
2537 return
2539 fn()
2540 self.orientation = 'top_left'
2542 def _channel_to_mask(self, value):
2543 """Attempts to resolve user input into a :c:type:`ChannelType` bit-mask.
2544 User input can be an integer, a string defined in :const:`CHANNELS`,
2545 or a string following ImageMagick's `CLI format`__.
2547 __ https://imagemagick.org/script/command-line-options.php#channel
2549 .. code::
2551 # User generated bit-mask.
2552 mask = self._channel_to_mask(CHANNELS['red'] | CHANNELS['green'])
2553 # Defined constant.
2554 mask = self._channel_to_mask('red')
2555 # CLI format.
2556 mask = self._channel_to_mask('RGB,Sync')
2558 :param value: Mixed user input.
2559 :type value: :class:`numbers.Integral` or :class:`basestring`
2560 :returns: Bit-mask constant.
2561 :rtype: :class:`numbers.Integral`
2563 .. versionadded:: 0.5.5
2564 """
2565 mask = -1
2566 if isinstance(value, numbers.Integral) and not isinstance(value, bool):
2567 mask = value
2568 elif isinstance(value, string_type):
2569 if value in CHANNELS:
2570 mask = CHANNELS[value]
2571 elif libmagick.ParseChannelOption:
2572 mask = libmagick.ParseChannelOption(binary(value))
2573 else:
2574 raise TypeError(repr(value) + ' is an invalid channel type'
2575 '; see wand.image.CHANNELS dictionary')
2576 if mask < 0:
2577 raise ValueError('expected value from wand.image.CHANNELS, not '
2578 + repr(value))
2579 return mask
2581 def _gravity_to_offset(self, gravity, width, height):
2582 """Calculate the top/left offset by a given gravity.
2584 Some methods in MagickWand's C-API do not respect gravity, but
2585 instead, expect a x/y offset. This is confusing to folks coming from
2586 the CLI documentation that does respect gravity
2588 :param gravity: Value from :const:`GRAVITY_TYPES`.
2589 :type gravity: :class:`basestring`
2590 :raises: :class:`ValueError` if gravity is no known.
2591 :returns: :class:`numbers.Intergal` top, :class:`numbers.Intergal` left
2593 .. versionadded:: 0.5.3
2594 """
2595 top, left = 0, 0
2596 assertions.string_in_list(GRAVITY_TYPES, 'wand.image.GRAVITY_TYPES',
2597 gravity=gravity)
2598 # Set `top` based on given gravity
2599 if gravity in ('north_west', 'north', 'north_east'):
2600 top = 0
2601 elif gravity in ('west', 'center', 'east'):
2602 top = int(self.height / 2) - int(height / 2)
2603 elif gravity in ('south_west', 'south', 'south_east'):
2604 top = self.height - height
2605 # Set `left` based on given gravity
2606 if gravity in ('north_west', 'west', 'south_west'):
2607 left = 0
2608 elif gravity in ('north', 'center', 'south'):
2609 left = int(self.width / 2) - int(width / 2)
2610 elif gravity in ('north_east', 'east', 'south_east'):
2611 left = self.width - width
2612 return top, left
2614 @manipulative
2615 @trap_exception
2616 def adaptive_blur(self, radius=0.0, sigma=0.0, channel=None):
2617 """Adaptively blurs the image by decreasing Gaussian as the operator
2618 approaches detected edges.
2620 :param radius: size of gaussian aperture.
2621 :type radius: :class:`numbers.Real`
2622 :param sigma: Standard deviation of the gaussian filter.
2623 :type sigma: :class:`numbers.Real`
2624 :param channel: Apply the blur effect on a specific channel.
2625 See :const:`CHANNELS`.
2626 :type channel: :class:`basestring`
2628 .. versionadded:: 0.5.3
2630 .. versionchanged:: 0.5.5
2631 Added optional ``channel`` argument
2632 """
2633 assertions.assert_real(radius=radius, sigma=sigma)
2634 if channel is None:
2635 r = library.MagickAdaptiveBlurImage(self.wand, radius, sigma)
2636 else:
2637 channel_ch = self._channel_to_mask(channel)
2638 if MAGICK_VERSION_NUMBER < 0x700:
2639 r = library.MagickAdaptiveBlurImageChannel(self.wand,
2640 channel_ch,
2641 radius,
2642 sigma)
2643 else: # pragma: no cover
2644 mask = library.MagickSetImageChannelMask(self.wand,
2645 channel_ch)
2646 r = library.MagickAdaptiveBlurImage(self.wand, radius, sigma)
2647 library.MagickSetImageChannelMask(self.wand, mask)
2648 return r
2650 @manipulative
2651 @trap_exception
2652 def adaptive_resize(self, columns=None, rows=None):
2653 """Adaptively resize image by applying Mesh interpolation.
2655 :param columns: width of resized image.
2656 :type columns: :class:`numbers.Integral`
2657 :param rows: hight of resized image.
2658 :type rows: :class:`numbers.Integral`
2660 .. versionadded:: 0.5.3
2661 """
2662 if columns is None:
2663 columns = self.width
2664 if rows is None:
2665 rows = self.height
2666 assertions.assert_integer(columns=columns, rows=rows)
2667 return library.MagickAdaptiveResizeImage(self.wand, columns, rows)
2669 @manipulative
2670 @trap_exception
2671 def adaptive_sharpen(self, radius=0.0, sigma=0.0, channel=None):
2672 """Adaptively sharpens the image by sharpening more intensely near
2673 image edges and less intensely far from edges.
2675 :param radius: size of gaussian aperture.
2676 :type radius: :class:`numbers.Real`
2677 :param sigma: Standard deviation of the gaussian filter.
2678 :type sigma: :class:`numbers.Real`
2679 :param channel: Apply the sharpen effect on a specific channel.
2680 See :const:`CHANNELS`.
2681 :type channel: :class:`basestring`
2683 .. versionadded:: 0.5.3
2685 .. versionchanged:: 0.5.5
2686 Added optional ``channel`` argument
2687 """
2688 assertions.assert_real(radius=radius, sigma=sigma)
2689 if channel is None:
2690 r = library.MagickAdaptiveSharpenImage(self.wand, radius, sigma)
2691 else:
2692 channel_ch = self._channel_to_mask(channel)
2693 if MAGICK_VERSION_NUMBER < 0x700:
2694 r = library.MagickAdaptiveSharpenImageChannel(self.wand,
2695 channel_ch,
2696 radius,
2697 sigma)
2698 else: # pragma: no cover
2699 mask = library.MagickSetImageChannelMask(self.wand,
2700 channel_ch)
2701 r = library.MagickAdaptiveSharpenImage(self.wand,
2702 radius,
2703 sigma)
2704 library.MagickSetImageChannelMask(self.wand, mask)
2705 return r
2707 @manipulative
2708 def adaptive_threshold(self, width, height, offset=0.0):
2709 """Applies threshold for each pixel based on neighboring pixel values.
2711 :param width: size of neighboring pixels on the X-axis.
2712 :type width: :class:`numbers.Integral`
2713 :param height: size of neighboring pixels on the Y-axis.
2714 :type height: :class:`numbers.Integral`
2715 :param offset: normalized number between `0.0` and
2716 :attr:`quantum_range`. Forces the pixels to black if
2717 values are below ``offset``.
2718 :type offset: :class:`numbers.Real`
2720 .. versionadded:: 0.5.3
2721 """
2722 assertions.assert_integer(width=width, height=height)
2723 assertions.assert_real(offset=offset)
2724 if MAGICK_VERSION_NUMBER < 0x700:
2725 offset = int(offset)
2726 return library.MagickAdaptiveThresholdImage(self.wand, width,
2727 height, offset)
2729 @manipulative
2730 @trap_exception
2731 def annotate(self, text, drawing_wand, left=0, baseline=0, angle=0):
2732 """Draws text on an image. This method differs from :meth:`caption()`
2733 as it uses :class:`~wand.drawing.Drawing` class to manage the
2734 font configuration & style context.
2736 .. code::
2738 from wand.drawing import Drawing
2739 from wand.image import Image
2741 with Image(filename='input.jpg') as img:
2742 with Drawing() as ctx:
2743 ctx.font_family = 'Times New Roman, Nimbus Roman No9'
2744 ctx.font_size = 18
2745 ctx.text_decoration = 'underline'
2746 ctx.text_kerning = -1
2747 img.annotate('Hello World', ctx, left=20, baseline=50)
2748 img.save(filename='output.jpg')
2750 :param text: String to annotate on image.
2751 :type text: :class:`basestring`
2752 :param drawing_wand: Font configuration & style context.
2753 :type text: :class:`wand.drawing.Drawing`
2754 :param left: X-axis offset of the text baseline.
2755 :type left: :class:`numbers.Real`
2756 :param baseline: Y-axis offset of the bottom of the text.
2757 :type baseline: :class:`numbers.Real`
2758 :param angle: Degree rotation to draw text at.
2759 :type angle: :class:`numbers.Real`
2761 .. versionadded:: 0.5.6
2762 """
2763 from .drawing import Drawing
2764 if not isinstance(drawing_wand, Drawing):
2765 raise TypeError('drawing_wand must be in instances of ' +
2766 'wand.drawing.Drawing, not ' + repr(drawing_wand))
2767 assertions.assert_real(left=left, baseline=baseline, angle=angle)
2768 btext = binary(text)
2769 return library.MagickAnnotateImage(self.wand, drawing_wand.resource,
2770 left, baseline, angle, btext)
2772 @manipulative
2773 @trap_exception
2774 def auto_gamma(self):
2775 """Adjust the gamma level of an image.
2777 .. versionadded:: 0.5.4
2778 """
2779 return library.MagickAutoGammaImage(self.wand)
2781 @manipulative
2782 @trap_exception
2783 def auto_level(self):
2784 """Scale the minimum and maximum values to a full quantum range.
2786 .. versionadded:: 0.5.4
2787 """
2788 return library.MagickAutoLevelImage(self.wand)
2790 @manipulative
2791 @trap_exception
2792 def auto_orient(self):
2793 """Adjusts an image so that its orientation is suitable
2794 for viewing (i.e. top-left orientation). If available it uses
2795 :c:func:`MagickAutoOrientImage` (was added in ImageMagick 6.8.9+)
2796 if you have an older magick library,
2797 it will use :attr:`_auto_orient()` method for fallback.
2799 .. versionadded:: 0.4.1
2801 """
2802 try:
2803 return library.MagickAutoOrientImage(self.wand)
2804 except AttributeError: # pragma: no cover
2805 self._auto_orient()
2806 return True
2808 @manipulative
2809 @trap_exception
2810 def auto_threshold(self, method='kapur'):
2811 """Automatically performs threshold method to reduce grayscale data
2812 down to a binary black & white image. Included algorithms are
2813 Kapur, Otsu, and Triangle methods.
2815 .. warning::
2817 This class method is only available with ImageMagick 7.0.8-41, or
2818 greater.
2820 :param method: Which threshold method to apply.
2821 See :const:`AUTO_THRESHOLD_METHODS`.
2822 Defaults to ``'kapur'``.
2823 :type method: :class:`basestring`
2824 :raises WandLibraryVersionError: if function is not available on
2825 system's library.
2827 .. versionadded:: 0.5.5
2828 """
2829 if library.MagickAutoThresholdImage is None:
2830 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
2831 raise WandLibraryVersionError(msg)
2832 assertions.string_in_list(AUTO_THRESHOLD_METHODS,
2833 'wand.image.AUTO_THRESHOLD_METHODS',
2834 method=method)
2835 method_idx = AUTO_THRESHOLD_METHODS.index(method)
2836 return library.MagickAutoThresholdImage(self.wand, method_idx)
2838 @manipulative
2839 @trap_exception
2840 def black_threshold(self, threshold):
2841 """Forces all pixels above a given color as black. Leaves pixels
2842 above threshold unaltered.
2844 :param threshold: Color to be referenced as a threshold.
2845 :type threshold: :class:`Color`
2847 .. versionadded:: 0.5.3
2848 """
2849 if isinstance(threshold, string_type):
2850 threshold = Color(threshold)
2851 assertions.assert_color(threshold=threshold)
2852 with threshold:
2853 r = library.MagickBlackThresholdImage(self.wand,
2854 threshold.resource)
2855 return r
2857 @manipulative
2858 @trap_exception
2859 def blue_shift(self, factor=1.5):
2860 """Mutes colors of the image by shifting blue values.
2862 :param factor: Amount to adjust values.
2863 :type factor: :class:`numbers.Real`
2865 .. versionadded:: 0.5.3
2866 """
2867 assertions.assert_real(factor=factor)
2868 return library.MagickBlueShiftImage(self.wand, factor)
2870 @manipulative
2871 @trap_exception
2872 def blur(self, radius=0.0, sigma=0.0, channel=None):
2873 """Blurs the image. Convolve the image with a gaussian operator
2874 of the given ``radius`` and standard deviation (``sigma``).
2875 For reasonable results, the ``radius`` should be larger
2876 than ``sigma``. Use a ``radius`` of 0 and :meth:`blur()` selects
2877 a suitable ``radius`` for you.
2879 :param radius: the radius of the, in pixels,
2880 not counting the center pixel. Default is ``0.0``.
2881 :type radius: :class:`numbers.Real`
2882 :param sigma: the standard deviation of the, in pixels. Default value
2883 is ``0.0``.
2884 :type sigma: :class:`numbers.Real`
2885 :param channel: Optional color channel to apply blur. See
2886 :const:`CHANNELS`.
2887 :type channel: :class:`basestring`
2889 .. versionadded:: 0.4.5
2891 .. versionchanged:: 0.5.5
2892 Added optional ``channel`` argument.
2894 .. versionchanged:: 0.5.7
2895 Positional arguments ``radius`` & ``sigman`` have been converted to
2896 key-word arguments.
2897 """
2898 assertions.assert_real(radius=radius, sigma=sigma)
2899 if channel is None:
2900 r = library.MagickBlurImage(self.wand, radius, sigma)
2901 else:
2902 channel_ch = self._channel_to_mask(channel)
2903 if MAGICK_VERSION_NUMBER < 0x700:
2904 r = library.MagickBlurImageChannel(self.wand,
2905 channel_ch,
2906 radius,
2907 sigma)
2908 else: # pragma: no cover
2909 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
2910 r = library.MagickBlurImage(self.wand, radius, sigma)
2911 library.MagickSetImageChannelMask(self.wand, mask)
2912 return r
2914 @trap_exception
2915 def border(self, color, width, height, compose="copy"):
2916 """Surrounds the image with a border.
2918 :param bordercolor: the border color pixel wand
2919 :type image: :class:`~wand.color.Color`
2920 :param width: the border width
2921 :type width: :class:`numbers.Integral`
2922 :param height: the border height
2923 :type height: :class:`numbers.Integral`
2924 :param compose: Use composite operator when applying frame. Only used
2925 if called with ImageMagick 7+.
2926 :type compose: :class:`basestring`
2928 .. versionadded:: 0.3.0
2929 .. versionchanged:: 0.5.0
2930 Added ``compose`` paramater, and ImageMagick 7 support.
2931 """
2932 if isinstance(color, string_type):
2933 color = Color(color)
2934 assertions.assert_color(color=color)
2935 with color:
2936 if MAGICK_VERSION_NUMBER < 0x700:
2937 result = library.MagickBorderImage(self.wand, color.resource,
2938 width, height)
2939 else: # pragma: no cover
2940 assertions.string_in_list(COMPOSITE_OPERATORS,
2941 'wand.image.COMPOSITE_OPERATORS',
2942 compose=compose)
2943 compose_idx = COMPOSITE_OPERATORS.index(compose)
2944 result = library.MagickBorderImage(self.wand, color.resource,
2945 width, height, compose_idx)
2946 return result
2948 @manipulative
2949 @trap_exception
2950 def brightness_contrast(self, brightness=0.0, contrast=0.0, channel=None):
2951 """Converts ``brightness`` & ``contrast`` paramaters into a slope &
2952 intercept, and applies a polynomial function.
2954 :param brightness: between ``-100.0`` and ``100.0``. Default is ``0.0``
2955 for unchanged.
2956 :type brightness: :class:`numbers.Real`
2957 :param contrast: between ``-100.0`` and ``100.0``. Default is ``0.0``
2958 for unchanged.
2959 :type contrast: :class:`numbers.Real`
2960 :param channel: Isolate a single color channel to apply contrast.
2961 See :const:`CHANNELS`.
2963 .. versionadded:: 0.5.4
2965 .. versionchanged:: 0.5.5
2966 Optional ``channel`` argument added.
2967 """
2968 assertions.assert_real(brightness=brightness, contrast=contrast)
2969 if channel is None:
2970 r = library.MagickBrightnessContrastImage(self.wand,
2971 brightness,
2972 contrast)
2973 else:
2974 channel_ch = self._channel_to_mask(channel)
2975 if MAGICK_VERSION_NUMBER < 0x700:
2976 r = library.MagickBrightnessContrastImageChannel(self.wand,
2977 channel_ch,
2978 brightness,
2979 contrast)
2980 else: # pragma: no cover
2981 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
2982 r = library.MagickBrightnessContrastImage(self.wand,
2983 brightness,
2984 contrast)
2985 library.MagickSetImageChannelMask(self.wand, mask)
2986 return r
2988 @manipulative
2989 @trap_exception
2990 def canny(self, radius=0.0, sigma=1.0, lower_percent=0.1,
2991 upper_percent=0.3):
2992 """Detect edges by leveraging a multi-stage Canny algorithm.
2994 .. warning::
2996 This class method is only available with ImageMagick 7.0.8-41, or
2997 greater.
2999 :param radius: Size of gaussian filter.
3000 :type radius: :class:`numbers.Real`
3001 :param sigma: Standard deviation of gaussian filter.
3002 :type sigma: :class:`numbers.Real`
3003 :param lower_percent: Normalized lower threshold. Values between
3004 ``0.0`` (0%) and ``1.0`` (100%). The default
3005 value is ``0.1`` or 10%.
3006 :type lower_percent: :class:`numbers.Real`
3007 :param upper_percent: Normalized upper threshold. Values between
3008 ``0.0`` (0%) and ``1.0`` (100%). The default
3009 value is ``0.3`` or 30%.
3010 :type upper_percent: :class:`numbers.Real`
3011 :raises WandLibraryVersionError: if function is not available on
3012 system's library.
3014 .. versionadded:: 0.5.5
3015 """
3016 if library.MagickCannyEdgeImage is None:
3017 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
3018 raise WandLibraryVersionError(msg)
3019 assertions.assert_real(radius=radius, sigma=sigma,
3020 lower_percent=lower_percent,
3021 upper_percent=upper_percent)
3022 return library.MagickCannyEdgeImage(self.wand, radius, sigma,
3023 lower_percent, upper_percent)
3025 @manipulative
3026 def caption(self, text, left=0, top=0, width=None, height=None, font=None,
3027 gravity=None):
3028 """Writes a caption ``text`` into the position.
3030 :param text: text to write
3031 :type text: :class:`basestring`
3032 :param left: x offset in pixels
3033 :type left: :class:`numbers.Integral`
3034 :param top: y offset in pixels
3035 :type top: :class:`numbers.Integral`
3036 :param width: width of caption in pixels.
3037 default is :attr:`width` of the image
3038 :type width: :class:`numbers.Integral`
3039 :param height: height of caption in pixels.
3040 default is :attr:`height` of the image
3041 :type height: :class:`numbers.Integral`
3042 :param font: font to use. default is :attr:`font` of the image
3043 :type font: :class:`wand.font.Font`
3044 :param gravity: text placement gravity.
3045 uses the current :attr:`gravity` setting of the image
3046 by default
3047 :type gravity: :class:`basestring`
3049 .. versionadded:: 0.3.0
3051 """
3052 assertions.assert_integer(left=left, top=top)
3053 if font is not None and not isinstance(font, Font):
3054 raise TypeError('font must be a wand.font.Font, not ' + repr(font))
3055 if gravity is not None:
3056 assertions.string_in_list(GRAVITY_TYPES,
3057 'wand.image.GRAVITY_TYPES',
3058 gravity=gravity)
3059 if width is None:
3060 width = self.width - left
3061 else:
3062 assertions.assert_integer(width=width)
3063 if height is None:
3064 height = self.height - top
3065 else:
3066 assertions.assert_integer(height=height)
3067 if not font:
3068 try:
3069 font = self.font
3070 except TypeError:
3071 raise TypeError('font must be specified or existing in image')
3072 with Image() as textboard:
3073 library.MagickSetSize(textboard.wand, width, height)
3074 textboard.font = font
3075 textboard.gravity = gravity or self.gravity
3076 with Color('transparent') as background_color:
3077 library.MagickSetBackgroundColor(textboard.wand,
3078 background_color.resource)
3079 textboard.read(filename=b'caption:' + text.encode('utf-8'))
3080 self.composite(textboard, left, top)
3082 def cdl(self, ccc):
3083 """Alias for :meth:`color_decision_list`.
3085 .. versionadded:: 0.5.7
3086 """
3087 return self.color_decision_list(ccc)
3089 @trap_exception
3090 def charcoal(self, radius, sigma):
3091 """Transform an image into a simulated charcoal drawing.
3093 :param radius: The size of the Gaussian operator.
3094 :type radius: :class:`numbers.Real`
3095 :param sigma: The standard deviation of the Gaussian.
3096 :type sigma: :class:`numbers.Real`
3098 .. versionadded:: 0.5.3
3099 """
3100 assertions.assert_real(radius=radius, sigma=sigma)
3101 return library.MagickCharcoalImage(self.wand, radius, sigma)
3103 @manipulative
3104 @trap_exception
3105 def chop(self, width, height, x=0, y=0):
3106 """Removes a region of an image, and reduces the image size
3107 accordingly.
3109 :param width: Size of region.
3110 :type width: :class:`numbers.Integral`
3111 :param height: Size of region.
3112 :type height: :class:`numbers.Integral`
3113 :param x: Offset on the X-axis.
3114 :type x: :class:`numbers.Integral`
3115 :param y: Offset on the Y-axis.
3116 :type y: :class:`numbers.Integral`
3118 .. versionadded:: 0.5.5
3119 """
3120 assertions.assert_unsigned_integer(width=width, height=height)
3121 assertions.assert_integer(x=x, y=y)
3122 return library.MagickChopImage(self.wand, width, height, x, y)
3124 @manipulative
3125 @trap_exception
3126 def clahe(self, width, height, number_bins, clip_limit):
3127 """Contrast limited adaptive histogram equalization.
3129 .. warning::
3131 The CLAHE method is only available with ImageMagick-7.
3133 :param width: Tile division width.
3134 :type width: :class:`numbers.Integral`
3135 :param height: Tile division height.
3136 :type height: :class:`numbers.Integral`
3137 :param number_bins: Histogram bins.
3138 :type number_bins: :class:`numbers.Real`
3139 :param clip_limit: contrast limit.
3140 :type clip_limit: :class:`numbers.Real`
3141 :raises WandLibraryVersionError: If system's version of ImageMagick
3142 does not support this method.
3144 .. versionadded:: 0.5.5
3145 """
3146 if library.MagickCLAHEImage is None:
3147 msg = 'CLAHE method not defined in ImageMagick library.'
3148 raise WandLibraryVersionError(msg)
3149 assertions.assert_unsigned_integer(width=width, height=height)
3150 assertions.assert_real(number_bins=number_bins, clip_limit=clip_limit)
3151 return library.MagickCLAHEImage(self.wand, width, height,
3152 number_bins, clip_limit)
3154 @trap_exception
3155 def clamp(self, channel=None):
3156 """Restrict color values between 0 and quantum range. This is useful
3157 when applying arithmetic operations that could result in color values
3158 over/under-flowing.
3160 :param channel: Optional color channel.
3161 :type channel: :class:`basestring`
3163 .. versionadded:: 0.5.0
3165 .. versionchanged:: 0.5.5
3166 Added ``channel`` argument.
3167 """
3168 if channel is None:
3169 r = library.MagickClampImage(self.wand)
3170 else:
3171 channel_ch = self._channel_to_mask(channel)
3172 if MAGICK_VERSION_NUMBER < 0x700:
3173 r = library.MagickClampImageChannel(self.wand, channel_ch)
3174 else: # pragma: no cover
3175 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
3176 r = library.MagickClampImage(self.wand)
3177 library.MagickSetImageChannelMask(self.wand, mask)
3178 return r
3180 def clone(self):
3181 """Clones the image. It is equivalent to call :class:`Image` with
3182 ``image`` parameter. ::
3184 with img.clone() as cloned:
3185 # manipulate the cloned image
3186 pass
3188 :returns: the cloned new image
3189 :rtype: :class:`Image`
3191 .. versionadded:: 0.1.1
3193 """
3194 return Image(image=self)
3196 @manipulative
3197 @trap_exception
3198 def clut(self, image, method='undefined', channel=None):
3199 """Replace color values by referencing another image as a Color
3200 Look Up Table.
3202 :param image: Color Look Up Table image.
3203 :type image: :class:`wand.image.BaseImage`
3204 :param method: Pixel Interpolate method. Only available with
3205 ImageMagick-7. See :const:`PIXEL_INTERPOLATE_METHODS`
3206 :type method: :class:`basestring`
3207 :param channel: Optional color channel to target. See
3208 :const:`CHANNELS`
3209 :type channel: :class:`basestring`
3211 .. versionadded:: 0.5.0
3213 .. versionchanged:: 0.5.5
3214 Added optional ``channel`` argument.
3215 """
3216 if not isinstance(image, BaseImage):
3217 raise TypeError('image must be a base image, not ' + repr(image))
3218 if MAGICK_VERSION_NUMBER < 0x700:
3219 if channel is None:
3220 r = library.MagickClutImage(self.wand, image.wand)
3221 else:
3222 channel_ch = self._channel_to_mask(channel)
3223 r = library.MagickClutImageChannel(self.wand,
3224 channel_ch,
3225 image.wand)
3226 else: # pragma: no cover
3227 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
3228 'wand.image.PIXEL_INTERPOLATE_METHODS',
3229 pixel_interpolate_method=method)
3230 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
3231 if channel is None:
3232 r = library.MagickClutImage(self.wand, image.wand, method_idx)
3233 else:
3234 channel_ch = self._channel_to_mask(channel)
3235 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
3236 r = library.MagickClutImage(self.wand, image.wand, method_idx)
3237 library.MagickSetImageChannelMask(self.wand, mask)
3238 return r
3240 @manipulative
3241 @trap_exception
3242 def coalesce(self):
3243 """Rebuilds image sequence with each frame size the same as first frame,
3244 and composites each frame atop of previous.
3246 .. note::
3248 Only affects GIF, and other formats with multiple pages/layers.
3250 .. versionadded:: 0.5.0
3251 """
3252 r = library.MagickCoalesceImages(self.wand)
3253 if r:
3254 self.wand = r
3255 self.reset_sequence()
3256 return bool(r)
3258 @manipulative
3259 @trap_exception
3260 def color_decision_list(self, ccc):
3261 """Applies color correction from a Color Correction Collection (CCC)
3262 xml string. An example of xml:
3264 .. code-block:: xml
3266 <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
3267 <ColorCorrection id="cc03345">
3268 <SOPNode>
3269 <Slope> 0.9 1.2 0.5 </Slope>
3270 <Offset> 0.4 -0.5 0.6 </Offset>
3271 <Power> 1.0 0.8 1.5 </Power>
3272 </SOPNode>
3273 <SATNode>
3274 <Saturation> 0.85 </Saturation>
3275 </SATNode>
3276 </ColorCorrection>
3277 </ColorCorrectionCollection>
3279 :param ccc: A XML string of the CCC contents.
3280 :type ccc: :class:`basestring`
3282 .. versionadded:: 0.5.7
3283 """
3284 return library.MagickColorDecisionListImage(self.wand, binary(ccc))
3286 def color_map(self, index, color=None):
3287 """Get & Set a color at a palette index. If ``color`` is given,
3288 the color at the index location will be set & returned. Omitting the
3289 ``color`` argument will only return the color value at index.
3291 Valid indexes are between ``0`` and total :attr:`colors` of the image.
3293 .. note::
3295 Ensure the image type is set to ``'palette'`` before calling the
3296 :meth:`color_map` method. For example::
3298 with Image(filename='graph.png') as img:
3299 img.type = 'palette'
3300 palette = [img.color_map(idx) for idx in range(img.colors)]
3301 # ...
3303 :param index: The color postion of the image palette.
3304 :type index: :class:`numbers.Integral`
3305 :param color: Optional color to _set_ at the given index.
3306 :type color: :class:`wand.color.Color`
3307 :returns: Color at index.
3308 :rtype: :class:`wand.color.Color`
3310 .. versionadded:: 0.5.3
3311 """
3312 if not isinstance(index, numbers.Integral):
3313 raise TypeError('index most be an integer, not ' + repr(index))
3314 if index < 0 or index >= self.colors:
3315 raise ValueError('index is out of palette range')
3316 if color:
3317 if isinstance(color, string_type):
3318 color = Color(color)
3319 if not isinstance(color, Color):
3320 raise TypeError('expecting in instance of Color, not ' +
3321 repr(color))
3322 with color:
3323 r = library.MagickSetImageColormapColor(self.wand,
3324 index,
3325 color.resource)
3326 if not r: # pragma: no cover
3327 self.raise_exception()
3328 else:
3329 color_ptr = library.NewPixelWand()
3330 r = library.MagickGetImageColormapColor(self.wand,
3331 index,
3332 color_ptr)
3333 if not r: # pragma: no cover
3334 color_ptr = library.DestroyPixelWand(color_ptr)
3335 self.raise_exception()
3336 color = Color.from_pixelwand(color_ptr)
3337 color_ptr = library.DestroyPixelWand(color_ptr)
3338 return color
3340 @manipulative
3341 @trap_exception
3342 def color_matrix(self, matrix):
3343 """Adjust color values by applying a matrix transform per pixel.
3345 Matrix should be given as 2D list, with a max size of 6x6.
3347 An example of 3x3 matrix::
3349 matrix = [
3350 [1.0, 0.0, 0.0],
3351 [0.0, 1.0, 0.0],
3352 [0.0, 0.0, 1.0],
3353 ]
3355 Which would translate RGB color channels by calculating the
3356 following:
3358 .. math::
3360 \\begin{aligned}
3361 red' &= 1.0 * red + 0.0 * green + 0.0 * blue\\\\
3362 green' &= 0.0 * red + 1.0 * green + 0.0 * blue\\\\
3363 blue' &= 0.0 * red + 0.0 * green + 1.0 * blue\\\\
3364 \\end{aligned}
3366 For RGB colorspace images, the rows & columns are laid out as:
3368 +---------+-----+-------+------+------+-------+--------+
3369 | | Red | Green | Blue | n/a | Alpha | Offset |
3370 +=========+=====+=======+======+======+=======+========+
3371 | Red' | 1 | 0 | 0 | 0 | 0 | 0 |
3372 +---------+-----+-------+------+------+-------+--------+
3373 | Green' | 0 | 1 | 0 | 0 | 0 | 0 |
3374 +---------+-----+-------+------+------+-------+--------+
3375 | Blue' | 0 | 0 | 1 | 0 | 0 | 0 |
3376 +---------+-----+-------+------+------+-------+--------+
3377 | n/a | 0 | 0 | 0 | 0 | 0 | 0 |
3378 +---------+-----+-------+------+------+-------+--------+
3379 | Alpha' | 0 | 0 | 0 | 0 | 0 | 0 |
3380 +---------+-----+-------+------+------+-------+--------+
3381 | Offset' | 0 | 0 | 0 | 0 | 0 | 0 |
3382 +---------+-----+-------+------+------+-------+--------+
3384 Or for a CMYK colorspace image:
3386 +----------+------+--------+---------+-------+-------+--------+
3387 | | Cyan | Yellow | Magenta | Black | Alpha | Offset |
3388 +==========+======+========+=========+=======+=======+========+
3389 | Cyan' | 1 | 0 | 0 | 0 | 0 | 0 |
3390 +----------+------+--------+---------+-------+-------+--------+
3391 | Yellow' | 0 | 1 | 0 | 0 | 0 | 0 |
3392 +----------+------+--------+---------+-------+-------+--------+
3393 | Magenta' | 0 | 0 | 1 | 0 | 0 | 0 |
3394 +----------+------+--------+---------+-------+-------+--------+
3395 | Black' | 0 | 0 | 0 | 0 | 0 | 0 |
3396 +----------+------+--------+---------+-------+-------+--------+
3397 | Alpha' | 0 | 0 | 0 | 0 | 0 | 0 |
3398 +----------+------+--------+---------+-------+-------+--------+
3399 | Offset' | 0 | 0 | 0 | 0 | 0 | 0 |
3400 +----------+------+--------+---------+-------+-------+--------+
3402 See `color-matrix`__ for examples.
3404 __ https://www.imagemagick.org/Usage/color_mods/#color-matrix
3406 :param matrix: 2D List of doubles.
3407 :type matrix: :class:`collections.abc.Sequence`
3409 .. versionadded:: 0.5.3
3410 """
3411 if not isinstance(matrix, abc.Sequence):
3412 raise TypeError('matrix must be a sequence, not ' + repr(matrix))
3413 rows = len(matrix)
3414 columns = None
3415 values = []
3416 for row in matrix:
3417 if not isinstance(row, abc.Sequence):
3418 raise TypeError('nested row must be a sequence, not ' +
3419 repr(row))
3420 if columns is None:
3421 columns = len(row)
3422 elif columns != len(row):
3423 raise ValueError('rows have different column length')
3424 for column in row:
3425 values.append(str(column))
3426 kernel = binary('{0}x{1}:{2}'.format(columns,
3427 rows,
3428 ','.join(values)))
3429 exception_info = libmagick.AcquireExceptionInfo()
3430 if MAGICK_VERSION_NUMBER < 0x700:
3431 kernel_info = libmagick.AcquireKernelInfo(kernel)
3432 else: # pragma: no cover
3433 kernel_info = libmagick.AcquireKernelInfo(kernel, exception_info)
3434 exception_info = libmagick.DestroyExceptionInfo(exception_info)
3435 r = library.MagickColorMatrixImage(self.wand, kernel_info)
3436 kernel_info = libmagick.DestroyKernelInfo(kernel_info)
3437 return r
3439 @manipulative
3440 @trap_exception
3441 def colorize(self, color=None, alpha=None):
3442 """Blends a given fill color over the image. The amount of blend is
3443 determined by the color channels given by the ``alpha`` argument.
3445 :param color: Color to paint image with.
3446 :type color: :class:`wand.color.Color`
3447 :param alpha: Defines how to blend color.
3448 :type alpha: :class:`wand.color.Color`
3450 .. versionadded:: 0.5.3
3451 """
3452 if isinstance(color, string_type):
3453 color = Color(color)
3454 if isinstance(alpha, string_type):
3455 alpha = Color(alpha)
3456 assertions.assert_color(color=color, alpha=alpha)
3457 with color:
3458 with alpha:
3459 r = library.MagickColorizeImage(self.wand,
3460 color.resource,
3461 alpha.resource)
3462 return r
3464 @manipulative
3465 @trap_exception
3466 def combine(self, channel='rgb_channels', colorspace='rgb'):
3467 """Creates an image where each color channel is assigned by a grayscale
3468 image in a sequence.
3470 .. warning::
3472 If your using ImageMagick-6, use ``channel`` argument to control
3473 the color-channel order. With ImageMagick-7, the ``channel``
3474 argument has been replaced with ``colorspace``.
3476 For example::
3478 for wand.image import Image
3480 with Image() as img:
3481 img.read(filename='red_channel.png')
3482 img.read(filename='green_channel.png')
3483 img.read(filename='blue_channel.png')
3484 img.combine(colorspace='rgb')
3485 img.save(filename='output.png')
3487 :param channel: Determines the colorchannel ordering of the
3488 sequence. Only used for ImageMagick-6.
3489 See :const:`CHANNELS`.
3490 :type channel: :class:`basestring`
3491 :param colorspace: Determines the colorchannel ordering of the
3492 sequence. Only used for ImageMagick-7.
3493 See :const:`COLORSPACE_TYPES`.
3494 :type colorspace: :class:`basestring`
3496 .. versionadded:: 0.5.9
3497 """
3498 assertions.string_in_list(COLORSPACE_TYPES,
3499 'wand.image.COLORSPACE_TYPES',
3500 colorspace=colorspace)
3501 library.MagickResetIterator(self.wand)
3502 colorspace_c = COLORSPACE_TYPES.index(colorspace)
3503 channel_c = self._channel_to_mask(channel)
3504 if MAGICK_VERSION_NUMBER < 0x700:
3505 new_wand = library.MagickCombineImages(self.wand, channel_c)
3506 else: # pragma: no-cover
3507 new_wand = library.MagickCombineImages(self.wand, colorspace_c)
3508 if new_wand:
3509 self.wand = new_wand
3510 self.reset_sequence()
3511 return bool(new_wand)
3513 @manipulative
3514 def compare(self, image, metric='undefined', highlight=None,
3515 lowlight=None):
3516 """Compares an image to a reconstructed image.
3518 Set :attr:`fuzz` property to adjust pixel-compare thresholds.
3520 For example::
3522 from wand.image import Image
3524 with Image(filename='input.jpg') as base:
3525 with Image(filename='subject.jpg') as img:
3526 base.fuzz = base.quantum_range * 0.20 # Threshold of 20%
3527 result_image, result_metric = base.compare(img)
3528 with result_image:
3529 result_image.save(filename='diff.jpg')
3531 :param image: The reference image
3532 :type image: :class:`wand.image.Image`
3533 :param metric: The metric type to use for comparing. See
3534 :const:`COMPARE_METRICS`
3535 :type metric: :class:`basestring`
3536 :param highlight: Set the color of the delta pixels in the resulting
3537 difference image.
3538 :type highlight: :class:`~wand.color.Color` or :class:`basestring`
3539 :param lowlight: Set the color of the similar pixels in the resulting
3540 difference image.
3541 :type lowlight: :class:`~wand.color.Color` or :class:`basestring`
3542 :returns: The difference image(:class:`wand.image.Image`),
3543 the computed distortion between the images
3544 (:class:`numbers.Integral`)
3545 :rtype: :class:`tuple`
3547 .. versionadded:: 0.4.3
3549 .. versionchanged:: 0.5.3
3550 Added support for ``highlight`` & ``lowlight``.
3551 """
3552 assertions.string_in_list(COMPARE_METRICS,
3553 'wand.image.COMPARE_METRICS',
3554 metric=metric)
3555 if highlight:
3556 if isinstance(highlight, Color):
3557 highlight = highlight.string
3558 library.MagickSetImageArtifact(self.wand,
3559 b'compare:highlight-color',
3560 binary(highlight))
3561 if lowlight:
3562 if isinstance(lowlight, Color):
3563 lowlight = lowlight.string
3564 library.MagickSetImageArtifact(self.wand,
3565 b'compare:lowlight-color',
3566 binary(lowlight))
3567 metric = COMPARE_METRICS.index(metric)
3568 distortion = ctypes.c_double()
3569 compared_image = library.MagickCompareImages(self.wand, image.wand,
3570 metric,
3571 ctypes.byref(distortion))
3572 return Image(BaseImage(compared_image)), distortion.value
3574 @manipulative
3575 def complex(self, operator='undefined', snr=None):
3576 """Performs `complex`_ mathematics against two images in a sequence,
3577 and generates a new image with two results.
3579 .. seealso::
3581 :meth:`forward_fourier_transform` &
3582 :meth:`inverse_fourier_transform`
3584 .. code::
3586 from wand.image import Image
3588 with Image(filename='real_part.png') as imgA:
3589 with Image(filename='imaginary_part.png') as imgB:
3590 imgA.sequence.append(imgB)
3591 with imgA.complex('conjugate') as results:
3592 results.save(filename='output-%02d.png')
3594 .. _complex: https://en.wikipedia.org/wiki/Complex_number
3596 .. warning::
3598 This class method is only available with ImageMagick 7.0.8-41, or
3599 greater.
3601 :param operator: Define which mathematic operator to perform. See
3602 :const:`COMPLEX_OPERATORS`.
3603 :type operator: :class:`basestring`
3604 :param snr: Optional ``SNR`` parameter for ``'divide'`` operator.
3605 :type snr: :class:`basestring`
3606 :raises WandLibraryVersionError: If ImageMagick library does not
3607 support this function.
3609 .. versionadded:: 0.5.5
3610 """
3611 if library.MagickComplexImages is None:
3612 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
3613 raise WandLibraryVersionError(msg)
3614 assertions.string_in_list(COMPLEX_OPERATORS,
3615 'wand.image.COMPLEX_OPERATORS',
3616 operator=operator)
3617 if snr is not None:
3618 library.MagickSetImageArtifact(self.wand,
3619 b'complex:snr=float',
3620 b'{0}'.format(snr))
3621 operator_idx = COMPLEX_OPERATORS.index(operator)
3622 wand = library.MagickComplexImages(self.wand, operator_idx)
3623 if not bool(wand):
3624 self.raise_exception()
3625 return Image(BaseImage(wand))
3627 @trap_exception
3628 def composite(self, image, left=None, top=None, operator='over',
3629 arguments=None, gravity=None):
3630 """Places the supplied ``image`` over the current image, with the top
3631 left corner of ``image`` at coordinates ``left``, ``top`` of the
3632 current image. The dimensions of the current image are not changed.
3634 :param image: the image placed over the current image
3635 :type image: :class:`wand.image.Image`
3636 :param left: the x-coordinate where `image` will be placed
3637 :type left: :class:`numbers.Integral`
3638 :param top: the y-coordinate where `image` will be placed
3639 :type top: :class:`numbers.Integral`
3640 :param operator: the operator that affects how the composite
3641 is applied to the image. available values
3642 can be found in the :const:`COMPOSITE_OPERATORS`
3643 list. Default is ``'over'``.
3644 :type operator: :class:`basestring`
3645 :param arguments: Additional numbers given as a geometry string, or
3646 comma delimited values. This is needed for
3647 ``'blend'``, ``'displace'``, ``'dissolve'``, and
3648 ``'modulate'`` operators.
3649 :type arguments: :class:`basestring`
3650 :param gravity: Calculate the ``top`` & ``left`` values based on
3651 gravity value from :const:`GRAVITY_TYPES`.
3652 :type: gravity: :class:`basestring`
3654 .. versionadded:: 0.2.0
3656 .. versionchanged:: 0.5.3
3657 The operator can be set, as well as additional composite arguments.
3659 .. versionchanged:: 0.5.3
3660 Optional ``gravity`` argument was added.
3661 """
3662 if top is None and left is None:
3663 if gravity is None:
3664 gravity = self.gravity
3665 top, left = self._gravity_to_offset(gravity,
3666 image.width,
3667 image.height)
3668 elif gravity is not None:
3669 raise TypeError('Can not use gravity if top & left are given')
3670 elif top is None:
3671 top = 0
3672 elif left is None:
3673 left = 0
3674 assertions.assert_integer(left=left, top=top)
3675 try:
3676 op = COMPOSITE_OPERATORS.index(operator)
3677 except IndexError:
3678 raise ValueError(repr(operator) + ' is an invalid composite '
3679 'operator type; see wand.image.COMPOSITE_'
3680 'OPERATORS dictionary')
3681 if arguments:
3682 assertions.assert_string(arguments=arguments)
3683 r = library.MagickSetImageArtifact(image.wand,
3684 binary('compose:args'),
3685 binary(arguments))
3686 if not r:
3687 self.raise_exception()
3688 r = library.MagickSetImageArtifact(self.wand,
3689 binary('compose:args'),
3690 binary(arguments))
3691 if not r: # pragma: no cover
3692 self.raise_exception()
3693 if MAGICK_VERSION_NUMBER < 0x700:
3694 r = library.MagickCompositeImage(self.wand, image.wand, op,
3695 int(left), int(top))
3696 else: # pragma: no cover
3697 r = library.MagickCompositeImage(self.wand, image.wand, op, True,
3698 int(left), int(top))
3699 return r
3701 @manipulative
3702 @trap_exception
3703 def composite_channel(self, channel, image, operator, left=None, top=None,
3704 arguments=None, gravity=None):
3705 """Composite two images using the particular ``channel``.
3707 :param channel: the channel type. available values can be found
3708 in the :const:`CHANNELS` mapping
3709 :param image: the composited source image.
3710 (the receiver image becomes the destination)
3711 :type image: :class:`Image`
3712 :param operator: the operator that affects how the composite
3713 is applied to the image. available values
3714 can be found in the :const:`COMPOSITE_OPERATORS`
3715 list
3716 :type operator: :class:`basestring`
3717 :param left: the column offset of the composited source image
3718 :type left: :class:`numbers.Integral`
3719 :param top: the row offset of the composited source image
3720 :type top: :class:`numbers.Integral`
3721 :param arguments: Additional numbers given as a geometry string, or
3722 comma delimited values. This is needed for
3723 ``'blend'``, ``'displace'``, ``'dissolve'``, and
3724 ``'modulate'`` operators.
3725 :type arguments: :class:`basestring`
3726 :param gravity: Calculate the ``top`` & ``left`` values based on
3727 gravity value from :const:`GRAVITY_TYPES`.
3728 :type: gravity: :class:`basestring`
3729 :raises ValueError: when the given ``channel`` or
3730 ``operator`` is invalid
3732 .. versionadded:: 0.3.0
3734 .. versionchanged:: 0.5.3
3735 Support for optional composite arguments has been added.
3737 .. versionchanged:: 0.5.3
3738 Optional ``gravity`` argument was added.
3739 """
3740 assertions.assert_string(operator=operator)
3741 ch_const = self._channel_to_mask(channel)
3742 if gravity:
3743 if left is None and top is None:
3744 top, left = self._gravity_to_offset(gravity,
3745 image.width,
3746 image.height)
3747 else:
3748 raise TypeError('Can not use gravity if top & left are given')
3749 if top is None:
3750 top = 0
3751 if left is None:
3752 left = 0
3753 assertions.assert_integer(left=left, top=top)
3754 try:
3755 op = COMPOSITE_OPERATORS.index(operator)
3756 except IndexError:
3757 raise IndexError(repr(operator) + ' is an invalid composite '
3758 'operator type; see wand.image.COMPOSITE_'
3759 'OPERATORS dictionary')
3760 if arguments:
3761 assertions.assert_string(arguments=arguments)
3762 library.MagickSetImageArtifact(image.wand,
3763 binary('compose:args'),
3764 binary(arguments))
3765 library.MagickSetImageArtifact(self.wand,
3766 binary('compose:args'),
3767 binary(arguments))
3768 if library.MagickCompositeImageChannel:
3769 r = library.MagickCompositeImageChannel(self.wand, ch_const,
3770 image.wand, op, int(left),
3771 int(top))
3772 else: # pragma: no cover
3773 ch_mask = library.MagickSetImageChannelMask(self.wand, ch_const)
3774 r = library.MagickCompositeImage(self.wand, image.wand, op, True,
3775 int(left), int(top))
3776 library.MagickSetImageChannelMask(self.wand, ch_mask)
3777 return r
3779 @manipulative
3780 @trap_exception
3781 def concat(self, stacked=False):
3782 """Concatenates images in stack into a single image. Left-to-right
3783 by default, top-to-bottom if ``stacked`` is True.
3785 :param stacked: stack images in a column, or in a row (default)
3786 :type stacked: :class:`bool`
3788 .. versionadded:: 0.5.0
3789 """
3790 assertions.assert_bool(stacked=stacked)
3791 r = library.MagickAppendImages(self.wand, stacked)
3792 if r:
3793 self.wand = r
3794 self.reset_sequence()
3795 return bool(r)
3797 def connected_components(self, connectivity=4, area_threshold=None,
3798 mean_color=False, keep=None, remove=None):
3799 """Evaluates binary image, and groups connected pixels into objects.
3800 This method will also return a list of
3801 :class:`ConnectedComponentObject` instances that will describe an
3802 object's features.
3804 .. code::
3806 from wand.image import Image
3808 with Image(filename='objects.gif') as img:
3809 objects = img.connected_components()
3810 for cc_obj in objects:
3811 print("{0._id}: {0.size} {0.offset}".format(cc_obj))
3813 #=> 0: (256, 171) (0, 0)
3814 #=> 2: (120, 135) (104, 18)
3815 #=> 3: (50, 36) (129, 44)
3816 #=> 4: (21, 23) (0, 45)
3817 #=> 1: (4, 10) (252, 0)
3819 .. warning::
3821 This class method is only available with ImageMagick 7.0.8-41, or
3822 greater.
3824 .. tip::
3826 Set :attr:`fuzz` property to increase pixel matching by reducing
3827 tolerance of color-value comparisons::
3829 from wand.image import Image
3830 from wand.version import QUANTUM_RANGE
3832 with Image(filename='objects.gif') as img:
3833 img.fuzz = 0.1 * QUANTUM_RANGE # 10%
3834 objects = img.connected_components()
3836 :param connectivity: Either ``4``, or ``8``. A value of ``4`` will
3837 evaluate each pixels top-bottom, & left-right
3838 neighbors. A value of ``8`` will use the same
3839 pixels as with ``4``, but will also include the
3840 four corners of each pixel.
3841 :type connectivity: :class:`numbers.Integral`
3842 :param area_threshold: Optional argument to exclude objects under an
3843 area size.
3844 :type area_threshold: :class:`basestring`
3845 :param mean_color: Optional argument. Replace object color with mean
3846 color of the source image.
3847 :type mean_color: :class:`bool`
3848 :param keep: Comma separated list of object IDs to isolate, the reset
3849 are converted to transparent.
3850 :type keep: :class:`basestring`
3851 :param remove: Comma separated list of object IDs to ignore, and
3852 convert to transparent.
3853 :type remove: :class:`basestring`
3854 :returns: A list of :class:`ConnectedComponentObject`.
3855 :rtype: :class:`list` [:class:`ConnectedComponentObject`]
3856 :raises WandLibraryVersionError: If ImageMagick library
3857 does not support this method.
3859 .. versionadded:: 0.5.5
3861 .. versionchanged:: 0.5.6
3862 Added ``mean_color``, ``keep``, & ``remove`` optional arguments.
3863 """
3864 if library.MagickConnectedComponentsImage is None:
3865 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
3866 raise WandLibraryVersionError(msg)
3867 if connectivity not in (4, 8):
3868 raise ValueError('connectivity must be 4, or 8.')
3869 if area_threshold is not None:
3870 key = b'connected-components:area-threshold'
3871 library.MagickSetImageArtifact(self.wand,
3872 key,
3873 b'{0}'.format(area_threshold))
3874 if mean_color:
3875 key = b'connected-components:mean-color'
3876 library.MagickSetImageArtifact(self.wand,
3877 key,
3878 b'true')
3879 if keep is not None:
3880 key = b'connected-components:keep'
3881 library.MagickSetImageArtifact(self.wand,
3882 key,
3883 b'{0}'.format(keep))
3884 if remove is not None:
3885 key = b'connected-components:remove'
3886 library.MagickSetImageArtifact(self.wand,
3887 key,
3888 b'{0}'.format(remove))
3889 objects_ptr = ctypes.c_void_p(0)
3890 ccoi_mem_size = ctypes.sizeof(CCObjectInfo)
3891 r = library.MagickConnectedComponentsImage(self.wand, connectivity,
3892 ctypes.byref(objects_ptr))
3893 objects = []
3894 if r and objects_ptr.value:
3895 for i in range(self.colors):
3896 temp = CCObjectInfo()
3897 src_addr = objects_ptr.value + (i * ccoi_mem_size)
3898 ctypes.memmove(ctypes.addressof(temp), src_addr, ccoi_mem_size)
3899 objects.append(ConnectedComponentObject(temp))
3900 objects_ptr = libmagick.RelinquishMagickMemory(objects_ptr)
3901 else:
3902 self.raise_exception()
3903 return objects
3905 @manipulative
3906 @trap_exception
3907 def contrast(self, sharpen=True):
3908 """Enhances the difference between lighter & darker values of the
3909 image. Set ``sharpen`` to ``False`` to reduce contrast.
3911 :param sharpen: Increase, or decrease, contrast. Default is ``True``
3912 for increased contrast.
3913 :type sharpen: :class:`bool`
3915 .. versionadded:: 0.5.7
3916 """
3917 assertions.assert_bool(sharpen=sharpen)
3918 return library.MagickContrastImage(self.wand, sharpen)
3920 @manipulative
3921 @trap_exception
3922 def contrast_stretch(self, black_point=0.0, white_point=None,
3923 channel=None):
3924 """Enhance contrast of image by adjusting the span of the available
3925 colors.
3927 :param black_point: black point between 0.0 and 1.0. default is 0.0
3928 :type black_point: :class:`numbers.Real`
3929 :param white_point: white point between 0.0 and 1.0.
3930 Defaults to the same value given to the
3931 ``black_point`` argument.
3932 :type white_point: :class:`numbers.Real`
3933 :param channel: optional color channel to apply contrast stretch
3934 :type channel: :const:`CHANNELS`
3935 :raises ValueError: if ``channel`` is not in :const:`CHANNELS`
3937 .. versionadded:: 0.4.1
3939 .. versionchanged:: 0.5.5
3940 The ``white_point`` argument will now default to the value given
3941 by the ``black_point`` argument.
3942 """
3943 assertions.assert_real(black_point=black_point)
3944 # If only black-point is given, match CLI behavior by
3945 # calculating white point
3946 if white_point is None:
3947 white_point = black_point
3948 assertions.assert_real(white_point=white_point)
3949 contrast_range = float(self.width * self.height)
3950 if 0.0 < black_point <= 1.0:
3951 black_point *= contrast_range
3952 if 0.0 < white_point <= 1.0:
3953 white_point *= contrast_range
3954 white_point = contrast_range - white_point
3955 if channel is None:
3956 r = library.MagickContrastStretchImage(self.wand,
3957 black_point,
3958 white_point)
3959 else:
3960 ch_const = self._channel_to_mask(channel)
3961 if library.MagickContrastStretchImageChannel:
3962 r = library.MagickContrastStretchImageChannel(self.wand,
3963 ch_const,
3964 black_point,
3965 white_point)
3966 else: # pragma: no cover
3967 # Set active channel, and capture mask to restore.
3968 channel_mask = library.MagickSetImageChannelMask(self.wand,
3969 ch_const)
3970 r = library.MagickContrastStretchImage(self.wand,
3971 black_point,
3972 white_point)
3973 # Restore original state of channels
3974 library.MagickSetImageChannelMask(self.wand, channel_mask)
3975 return r
3977 @manipulative
3978 @trap_exception
3979 def crop(self, left=0, top=0, right=None, bottom=None,
3980 width=None, height=None, reset_coords=True,
3981 gravity=None):
3982 """Crops the image in-place.
3984 .. sourcecode:: text
3986 +--------------------------------------------------+
3987 | ^ ^ |
3988 | | | |
3989 | top | |
3990 | | | |
3991 | v | |
3992 | <-- left --> +-------------------+ bottom |
3993 | | ^ | | |
3994 | | <-- width --|---> | | |
3995 | | height | | |
3996 | | | | | |
3997 | | v | | |
3998 | +-------------------+ v |
3999 | <--------------- right ----------> |
4000 +--------------------------------------------------+
4002 :param left: x-offset of the cropped image. default is 0
4003 :type left: :class:`numbers.Integral`
4004 :param top: y-offset of the cropped image. default is 0
4005 :type top: :class:`numbers.Integral`
4006 :param right: second x-offset of the cropped image.
4007 default is the :attr:`width` of the image.
4008 this parameter and ``width`` parameter are exclusive
4009 each other
4010 :type right: :class:`numbers.Integral`
4011 :param bottom: second y-offset of the cropped image.
4012 default is the :attr:`height` of the image.
4013 this parameter and ``height`` parameter are exclusive
4014 each other
4015 :type bottom: :class:`numbers.Integral`
4016 :param width: the :attr:`width` of the cropped image.
4017 default is the :attr:`width` of the image.
4018 this parameter and ``right`` parameter are exclusive
4019 each other
4020 :type width: :class:`numbers.Integral`
4021 :param height: the :attr:`height` of the cropped image.
4022 default is the :attr:`height` of the image.
4023 this parameter and ``bottom`` parameter are exclusive
4024 each other
4025 :type height: :class:`numbers.Integral`
4026 :param reset_coords:
4027 optional flag. If set, after the rotation, the coordinate frame
4028 will be relocated to the upper-left corner of the new image.
4029 By default is `True`.
4030 :type reset_coords: :class:`bool`
4031 :param gravity: optional flag. If set, will calculate the :attr:`top`
4032 and :attr:`left` attributes. This requires both
4033 :attr:`width` and :attr:`height` parameters to be
4034 included.
4035 :type gravity: :const:`GRAVITY_TYPES`
4036 :raises ValueError: when one or more arguments are invalid
4038 .. note::
4040 If you want to crop the image but not in-place, use slicing
4041 operator.
4043 .. versionchanged:: 0.4.1
4044 Added ``gravity`` option. Using ``gravity`` along with
4045 ``width`` & ``height`` to auto-adjust ``left`` & ``top``
4046 attributes.
4048 .. versionchanged:: 0.1.8
4049 Made to raise :exc:`~exceptions.ValueError` instead of
4050 :exc:`~exceptions.IndexError` for invalid ``width``/``height``
4051 arguments.
4053 .. versionadded:: 0.1.7
4055 """
4056 if not (right is None or width is None):
4057 raise TypeError('parameters right and width are exclusive each '
4058 'other; use one at a time')
4059 elif not (bottom is None or height is None):
4060 raise TypeError('parameters bottom and height are exclusive each '
4061 'other; use one at a time')
4063 def abs_(n, m, null=None):
4064 if n is None:
4065 return m if null is None else null
4066 elif not isinstance(n, numbers.Integral):
4067 raise TypeError('expected integer, not ' + repr(n))
4068 elif n > m:
4069 raise ValueError(repr(n) + ' > ' + repr(m))
4070 return m + n if n < 0 else n
4072 # Define left & top if gravity is given.
4073 if gravity:
4074 if width is None or height is None:
4075 raise TypeError(
4076 'both width and height must be defined with gravity'
4077 )
4078 top, left = self._gravity_to_offset(gravity, width, height)
4079 else:
4080 left = abs_(left, self.width, 0)
4081 top = abs_(top, self.height, 0)
4083 if width is None:
4084 right = abs_(right, self.width)
4085 width = right - left
4086 if height is None:
4087 bottom = abs_(bottom, self.height)
4088 height = bottom - top
4089 assertions.assert_counting_number(width=width, height=height)
4090 if (
4091 left == top == 0 and
4092 width == self.width and
4093 height == self.height
4094 ):
4095 return True
4096 if self.animation:
4097 self.wand = library.MagickCoalesceImages(self.wand)
4098 self.reset_sequence()
4099 library.MagickSetLastIterator(self.wand)
4100 n = library.MagickGetIteratorIndex(self.wand)
4101 library.MagickResetIterator(self.wand)
4102 for i in xrange(0, n + 1):
4103 library.MagickSetIteratorIndex(self.wand, i)
4104 r = library.MagickCropImage(self.wand,
4105 width, height,
4106 left, top)
4107 if reset_coords:
4108 self.reset_coords()
4109 else:
4110 r = library.MagickCropImage(self.wand, width, height, left, top)
4111 if reset_coords:
4112 self.reset_coords()
4113 return r
4115 @trap_exception
4116 def cycle_color_map(self, offset=1):
4117 """Shift the image color-map by a given offset.
4119 :param offset: number of steps to rotate index by.
4120 :type offset: :class:`numbers.Integral`
4122 .. versionadded:: 0.5.3
4123 """
4124 assertions.assert_integer(offset=offset)
4125 return library.MagickCycleColormapImage(self.wand, offset)
4127 @manipulative
4128 @trap_exception
4129 def deconstruct(self):
4130 """Iterates over internal image stack, and adjust each frame size to
4131 minimum bounding region of any changes from the previous frame.
4133 .. versionadded:: 0.5.0
4134 """
4135 r = library.MagickDeconstructImages(self.wand)
4136 if r:
4137 self.wand = r
4138 self.reset_sequence()
4139 return bool(r)
4141 @manipulative
4142 @trap_exception
4143 def deskew(self, threshold):
4144 """Attempts to remove skew artifacts common with most
4145 scanning & optical import devices.
4147 :params threshold: limit between foreground & background. Use a real
4148 number between `0.0` & `1.0` to match CLI's percent
4149 argument.
4150 :type threshold: :class:`numbers.Real`
4152 .. versionadded:: 0.5.0
4153 """
4154 assertions.assert_real(threshold=threshold)
4155 if 0 < threshold <= 1.0:
4156 threshold *= self.quantum_range
4157 return library.MagickDeskewImage(self.wand, threshold)
4159 @manipulative
4160 @trap_exception
4161 def despeckle(self):
4162 """Applies filter to reduce noise in image.
4164 .. versionadded:: 0.5.0
4165 """
4166 return library.MagickDespeckleImage(self.wand)
4168 @manipulative
4169 @trap_exception
4170 def distort(self, method, arguments, best_fit=False):
4171 """Distorts an image using various distorting methods.
4173 .. code:: python
4175 from wand.image import Image
4176 from wand.color import Color
4178 with Image(filename='checks.png') as img:
4179 img.virtual_pixel = 'background'
4180 img.background_color = Color('green')
4181 img.matte_color = Color('skyblue')
4182 arguments = (0, 0, 20, 60,
4183 90, 0, 70, 63,
4184 0, 90, 5, 83,
4185 90, 90, 85, 88)
4186 img.distort('perspective', arguments)
4187 img.save(filename='checks_perspective.png')
4189 .. image:: ../_images/wand/image/checks.png
4190 .. image:: ../_images/wand/image/checks_perspective.png
4192 Use :attr:`virtual_pixel`, :attr:`background_color`, and
4193 :attr:`matte_color` properties to control the behavior of pixels
4194 rendered outside of the image boundaries.
4196 Use :attr:`interpolate_method` to control how images scale-up.
4198 Distortion viewport, and scale, can be defined by using
4199 :attr:`Image.artifacts` dictionary. For example::
4201 img.artifacts['distort:viewport'] = '44x44+15+0'
4202 img.artifacts['distort:scale'] = '10'
4204 :param method: Distortion method name from :const:`DISTORTION_METHODS`
4205 :type method: :class:`basestring`
4206 :param arguments: List of distorting float arguments
4207 unique to distortion method
4208 :type arguments: :class:`collections.abc.Sequence`
4209 :param best_fit: Attempt to resize resulting image fit distortion.
4210 Defaults False
4211 :type best_fit: :class:`bool`
4213 .. versionadded:: 0.4.1
4214 """
4215 assertions.string_in_list(DISTORTION_METHODS,
4216 'wand.image.DISTORTION_METHODS',
4217 method=method)
4218 if not isinstance(arguments, abc.Sequence):
4219 raise TypeError('expected sequence of doubles, not ' +
4220 repr(arguments))
4221 argc = len(arguments)
4222 argv = (ctypes.c_double * argc)(*arguments)
4223 method_idx = DISTORTION_METHODS.index(method)
4224 return library.MagickDistortImage(self.wand, method_idx,
4225 argc, argv, bool(best_fit))
4227 @manipulative
4228 @trap_exception
4229 def edge(self, radius=0.0):
4230 """Applies convolution filter to detect edges.
4232 :param radius: aperture of detection filter.
4233 :type radius: :class:`numbers.Real`
4235 .. versionadded:: 0.5.0
4236 """
4237 assertions.assert_real(radius=radius)
4238 return library.MagickEdgeImage(self.wand, radius)
4240 @manipulative
4241 @trap_exception
4242 def emboss(self, radius=0.0, sigma=0.0):
4243 """Applies convolution filter against Gaussians filter.
4245 .. note::
4247 The `radius` value should be larger than `sigma` for best results.
4249 :param radius: filter aperture size.
4250 :type radius: :class:`numbers.Real`
4251 :param sigma: standard deviation.
4252 :type sigma: :class:`numbers.Real`
4254 .. versionadded:: 0.5.0
4255 """
4256 assertions.assert_real(radius=radius, sigma=sigma)
4257 return library.MagickEmbossImage(self.wand, radius, sigma)
4259 @manipulative
4260 @trap_exception
4261 def enhance(self):
4262 """Applies digital filter to reduce noise.
4264 .. versionadded:: 0.5.0
4265 """
4266 return library.MagickEnhanceImage(self.wand)
4268 @manipulative
4269 @trap_exception
4270 def equalize(self, channel=None):
4271 """Equalizes the image histogram
4273 :param channel: Optional channel. See :const:`CHANNELS`.
4274 :type channel: :class:`basestring`
4276 .. versionadded:: 0.3.10
4278 .. versionchanged:: 0.5.5
4279 Added optional ``channel`` argument.
4280 """
4281 if channel is None:
4282 r = library.MagickEqualizeImage(self.wand)
4283 else:
4284 channel_ch = self._channel_to_mask(channel)
4285 if MAGICK_VERSION_NUMBER < 0x700:
4286 r = library.MagickEqualizeImageChannel(self.wand, channel_ch)
4287 else: # pragma: no cover
4288 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
4289 r = library.MagickEqualizeImage(self.wand)
4290 library.MagickSetImageChannelMask(self.wand, mask)
4291 return r
4293 @manipulative
4294 @trap_exception
4295 def evaluate(self, operator=None, value=0.0, channel=None):
4296 """Apply arithmetic, relational, or logical expression to an image.
4298 Percent values must be calculated against the quantum range of the
4299 image::
4301 fifty_percent = img.quantum_range * 0.5
4302 img.evaluate(operator='set', value=fifty_percent)
4304 :param operator: Type of operation to calculate
4305 :type operator: :const:`EVALUATE_OPS`
4306 :param value: Number to calculate with ``operator``
4307 :type value: :class:`numbers.Real`
4308 :param channel: Optional channel to apply operation on.
4309 :type channel: :const:`CHANNELS`
4310 :raises TypeError: When ``value`` is not numeric.
4311 :raises ValueError: When ``operator``, or ``channel`` are not defined
4312 in constants.
4314 .. versionadded:: 0.4.1
4315 """
4316 assertions.string_in_list(EVALUATE_OPS, 'wand.image.EVALUATE_OPS',
4317 operator=operator)
4318 assertions.assert_real(value=value)
4319 idx_op = EVALUATE_OPS.index(operator)
4320 if channel is None:
4321 r = library.MagickEvaluateImage(self.wand, idx_op, value)
4322 else:
4323 ch_const = self._channel_to_mask(channel)
4324 # Use channel method if IM6, else create channel mask for IM7.
4325 if library.MagickEvaluateImageChannel:
4326 r = library.MagickEvaluateImageChannel(self.wand,
4327 ch_const,
4328 idx_op,
4329 value)
4330 else: # pragma: no cover
4331 # Set active channel, and capture mask to restore.
4332 channel_mask = library.MagickSetImageChannelMask(self.wand,
4333 ch_const)
4334 r = library.MagickEvaluateImage(self.wand, idx_op, value)
4335 # Restore original state of channels
4336 library.MagickSetImageChannelMask(self.wand, channel_mask)
4337 return r
4339 def export_pixels(self, x=0, y=0, width=None, height=None,
4340 channel_map="RGBA", storage='char'):
4341 """Export pixel data from a raster image to
4342 a list of values.
4344 The ``channel_map`` tells ImageMagick which color
4345 channels to export, and what order they should be
4346 written as -- per pixel. Valid entries for
4347 ``channel_map`` are:
4349 - ``'R'`` - Red channel
4350 - ``'G'`` - Green channel
4351 - ``'B'`` - Blue channel
4352 - ``'A'`` - Alpha channel (``0`` is transparent)
4353 - ``'O'`` - Alpha channel (``0`` is opaque)
4354 - ``'C'`` - Cyan channel
4355 - ``'Y'`` - Yellow channel
4356 - ``'M'`` - Magenta channel
4357 - ``'K'`` - Black channel
4358 - ``'I'`` - Intensity channel (only for grayscale)
4359 - ``'P'`` - Padding
4361 See :const:`STORAGE_TYPES` for a list of valid
4362 ``storage`` options. This tells ImageMagick
4363 what type of data it should calculate & write to.
4364 For example; a storage type of ``'char'`` will write
4365 a 8-bit value between 0 ~ 255, a storage type
4366 of ``'short'`` will write a 16-bit value between
4367 0 ~ 65535, and a ``'integer'`` will write a
4368 32-bit value between 0 ~ 4294967295.
4370 .. note::
4372 By default, the entire image will be exported
4373 as ``'char'`` storage with each pixel mapping
4374 Red, Green, Blue, & Alpha channels.
4377 :param x: horizontal starting coordinate of raster.
4378 :type x: :class:`numbers.Integral`
4379 :param y: vertical starting coordinate of raster.
4380 :type y: :class:`numbers.Integral`
4381 :param width: horizontal length of raster.
4382 :type width: :class:`numbers.Integral`
4383 :param height: vertical length of raster.
4384 :type height: :class:`numbers.Integral`
4385 :param channel_map: a string listing the channel data
4386 format for each pixel.
4387 :type channel_map: :class:`basestring`
4388 :param storage: what data type each value should
4389 be calculated as.
4390 :type storage: :class:`basestring`
4391 :returns: list of values.
4392 :rtype: :class:`collections.abc.Sequence`
4394 .. versionadded:: 0.5.0
4395 """
4396 _w, _h = self.size
4397 if width is None:
4398 width = _w
4399 if height is None:
4400 height = _h
4401 assertions.assert_integer(x=x, y=y, width=width, height=height)
4402 assertions.assert_string(channel_map=channel_map)
4403 assertions.string_in_list(STORAGE_TYPES, 'wand.image.STORAGE_TYPES',
4404 storage=storage)
4405 channel_map = channel_map.upper()
4406 valid_channels = 'RGBAOCYMKIP'
4407 for channel in channel_map:
4408 if channel not in valid_channels:
4409 raise ValueError('Unknown channel label: ' +
4410 repr(channel))
4411 c_storage_types = [
4412 None,
4413 ctypes.c_ubyte,
4414 ctypes.c_double,
4415 ctypes.c_float,
4416 ctypes.c_uint,
4417 ctypes.c_ulong,
4418 ctypes.c_double, # FIXME: Might be c_longdouble?
4419 ctypes.c_ushort
4420 ]
4421 s_index = STORAGE_TYPES.index(storage)
4422 c_storage = c_storage_types[s_index]
4423 total_pixels = width * height
4424 c_buffer_size = total_pixels * len(channel_map)
4425 c_buffer = (c_buffer_size * c_storage)()
4426 r = library.MagickExportImagePixels(self.wand,
4427 x, y, width, height,
4428 binary(channel_map),
4429 s_index,
4430 ctypes.byref(c_buffer))
4431 if not r: # pragma: no cover
4432 self.raise_exception()
4433 return c_buffer[:c_buffer_size]
4435 @manipulative
4436 @trap_exception
4437 def extent(self, width=None, height=None, x=0, y=0):
4438 """extends the image as defined by the geometry, gravity, and wand
4439 background color. Set the (x,y) offset of the geometry to move the
4440 original wand relative to the extended wand.
4442 :param width: the :attr:`width` of the extended image.
4443 default is the :attr:`width` of the image.
4444 :type width: :class:`numbers.Integral`
4445 :param height: the :attr:`height` of the extended image.
4446 default is the :attr:`height` of the image.
4447 :type height: :class:`numbers.Integral`
4448 :param x: the :attr:`x` offset of the extended image.
4449 default is 0
4450 :type x: :class:`numbers.Integral`
4451 :param y: the :attr:`y` offset of the extended image.
4452 default is 0
4453 :type y: :class:`numbers.Integral`
4455 .. versionadded:: 0.4.5
4456 """
4457 if width is None or width == 0:
4458 width = self.width
4459 if height is None or height == 0:
4460 height = self.height
4461 if width < 0:
4462 raise ValueError('image width cannot be negative integer')
4463 elif height < 0:
4464 raise ValueError('image height cannot be negative integer')
4466 return library.MagickExtentImage(self.wand, width, height, x, y)
4468 def features(self, distance):
4469 """Calculate directional image features for each color channel.
4470 Feature metrics including:
4472 - angular second moment
4473 - contrast
4474 - correlation
4475 - variance sum of squares
4476 - inverse difference moment
4477 - sum average
4478 - sum variance
4479 - sum entropy
4480 - entropy
4481 - difference variance
4482 - difference entropy
4483 - information measures of correlation 1
4484 - information measures of correlation 2
4485 - maximum correlation coefficient
4487 With each metric containing horizontal, vertical, left & right
4488 diagonal values.
4490 .. code::
4492 from wand.image import Image
4494 with Image(filename='rose:') as img:
4495 channel_features = img.features(distance=32)
4496 for channels, features in channel_features.items():
4497 print(channels)
4498 for feature, directions in features.items():
4499 print(' ', feature)
4500 for name, value in directions.items():
4501 print(' ', name, value)
4503 :param distance: Define the distance if pixels to calculate.
4504 :type distance: :class:`numbers.Integral`
4505 :returns: a dict mapping each color channel with a dict of each
4506 feature.
4507 :rtype: :class:`dict`
4509 .. versionadded:: 0.5.5
4510 """
4511 def build_channel(address, channel):
4512 feature = ChannelFeature()
4513 size = ctypes.sizeof(feature)
4514 ctypes.memmove(ctypes.addressof(feature),
4515 feature_ptr + (CHANNELS[channel] * size),
4516 size)
4517 keys = ('horizontal', 'vertical',
4518 'left_diagonal', 'right_diagonal')
4519 feature_dict = {}
4520 for k in feature._fields_:
4521 a = k[0]
4522 feature_dict[a] = dict(zip(keys, getattr(feature, a)))
4523 return feature_dict
4524 if MAGICK_VERSION_NUMBER < 0x700:
4525 method = library.MagickGetImageChannelFeatures
4526 else: # pragma: no cover
4527 method = library.MagickGetImageFeatures
4528 assertions.assert_unsigned_integer(distance=distance)
4529 feature_ptr = method(self.wand, distance)
4530 response = {}
4531 if feature_ptr:
4532 colorspace = self.colorspace
4533 if self.alpha_channel:
4534 response['alpha'] = build_channel(feature_ptr, 'alpha')
4535 if colorspace == 'gray':
4536 response['gray'] = build_channel(feature_ptr, 'gray')
4537 elif colorspace == 'cmyk':
4538 response['cyan'] = build_channel(feature_ptr, 'cyan')
4539 response['magenta'] = build_channel(feature_ptr, 'magenta')
4540 response['yellow'] = build_channel(feature_ptr, 'yellow')
4541 response['black'] = build_channel(feature_ptr, 'black')
4542 else:
4543 response['red'] = build_channel(feature_ptr, 'red')
4544 response['green'] = build_channel(feature_ptr, 'green')
4545 response['blue'] = build_channel(feature_ptr, 'blue')
4546 feature_ptr = library.MagickRelinquishMemory(feature_ptr)
4547 return response
4549 def fft(self, magnitude=True):
4550 """Alias for :meth:`forward_fourier_transform`.
4552 .. versionadded:: 0.5.7
4553 """
4554 return self.forward_fourier_transform(magnitude)
4556 @manipulative
4557 @trap_exception
4558 def flip(self):
4559 """Creates a vertical mirror image by reflecting the pixels around
4560 the central x-axis. It manipulates the image in place.
4562 .. versionadded:: 0.3.0
4564 """
4565 return library.MagickFlipImage(self.wand)
4567 @manipulative
4568 @trap_exception
4569 def flop(self):
4570 """Creates a horizontal mirror image by reflecting the pixels around
4571 the central y-axis. It manipulates the image in place.
4573 .. versionadded:: 0.3.0
4575 """
4576 return library.MagickFlopImage(self.wand)
4578 @trap_exception
4579 def forward_fourier_transform(self, magnitude=True):
4580 """Performs a discrete Fourier transform. The image stack is replaced
4581 with the results. Either a pair of magnitude & phase images, or
4582 real & imaginary (HDRI).
4584 .. code::
4586 from wand.image import Image
4587 from wand.version import QUANTUM_RANGE
4589 with Image(filename='source.png') as img:
4590 img.forward_fourier_transform()
4591 img.depth = QUANTUM_RANGE
4592 img.save(filename='fft_%02d.png')
4594 .. seealso:: :meth:`inverse_fourier_transform` & :meth:`complex`
4596 .. note::
4598 ImageMagick must have HDRI support to compute real & imaginary
4599 components (i.e. ``magnitude=False``).
4601 :param magnitude: If ``True``, generate magnitude & phase, else
4602 real & imaginary. Default ``True``
4603 :type magnitude: :class:`bool`
4605 .. versionadded:: 0.5.5
4606 """
4607 assertions.assert_bool(magnitude=magnitude)
4608 return library.MagickForwardFourierTransformImage(self.wand, magnitude)
4610 @manipulative
4611 @trap_exception
4612 def frame(self, matte=None, width=1, height=1, inner_bevel=0,
4613 outer_bevel=0, compose='over'):
4614 """Creates a bordered frame around image.
4615 Inner & outer bevel can simulate a 3D effect.
4617 :param matte: color of the frame
4618 :type matte: :class:`wand.color.Color`
4619 :param width: total size of frame on x-axis
4620 :type width: :class:`numbers.Integral`
4621 :param height: total size of frame on y-axis
4622 :type height: :class:`numbers.Integral`
4623 :param inner_bevel: inset shadow length
4624 :type inner_bevel: :class:`numbers.Real`
4625 :param outer_bevel: outset highlight length
4626 :type outer_bevel: :class:`numbers.Real`
4627 :param compose: Optional composite operator. Default ``'over'``, and
4628 only available with ImageMagick-7.
4629 :type compose: :class:`basestring`
4631 .. versionadded:: 0.4.1
4633 .. versionchanged:: 0.5.6
4634 Added optional ``compose`` parameter.
4635 """
4636 if matte is None:
4637 matte = Color('gray')
4638 if isinstance(matte, string_type):
4639 matte = Color(matte)
4640 assertions.assert_color(matte=matte)
4641 assertions.assert_integer(width=width, height=height)
4642 assertions.assert_real(inner_bevel=inner_bevel,
4643 outer_bevel=outer_bevel)
4644 with matte:
4645 if MAGICK_VERSION_NUMBER < 0x700:
4646 r = library.MagickFrameImage(self.wand,
4647 matte.resource,
4648 width, height,
4649 inner_bevel, outer_bevel)
4650 else: # pragma: no cover
4651 assertions.string_in_list(COMPOSITE_OPERATORS,
4652 'wand.image.COMPOSITE_OPERATORS',
4653 compose=compose)
4654 op = COMPOSITE_OPERATORS.index(compose)
4655 r = library.MagickFrameImage(self.wand,
4656 matte.resource,
4657 width, height,
4658 inner_bevel, outer_bevel,
4659 op)
4660 return r
4662 @manipulative
4663 @trap_exception
4664 def function(self, function, arguments, channel=None):
4665 """Apply an arithmetic, relational, or logical expression to an image.
4667 Defaults entire image, but can isolate affects to single color channel
4668 by passing :const:`CHANNELS` value to ``channel`` parameter.
4670 .. note::
4672 Support for function methods added in the following versions
4673 of ImageMagick.
4675 - ``'polynomial'`` >= 6.4.8-8
4676 - ``'sinusoid'`` >= 6.4.8-8
4677 - ``'arcsin'`` >= 6.5.3-1
4678 - ``'arctan'`` >= 6.5.3-1
4680 :param function: a string listed in :const:`FUNCTION_TYPES`
4681 :type function: :class:`basestring`
4682 :param arguments: a sequence of doubles to apply against ``function``
4683 :type arguments: :class:`collections.abc.Sequence`
4684 :param channel: optional :const:`CHANNELS`, defaults all
4685 :type channel: :class:`basestring`
4686 :raises ValueError: when a ``function``, or ``channel`` is not
4687 defined in there respected constant
4688 :raises TypeError: if ``arguments`` is not a sequence
4690 .. versionadded:: 0.4.1
4691 """
4692 assertions.string_in_list(FUNCTION_TYPES, 'wand.image.FUNCTION_TYPES',
4693 function=function)
4694 if not isinstance(arguments, abc.Sequence):
4695 raise TypeError('expecting sequence of arguments, not ' +
4696 repr(arguments))
4697 argc = len(arguments)
4698 argv = (ctypes.c_double * argc)(*arguments)
4699 index = FUNCTION_TYPES.index(function)
4700 if channel is None:
4701 r = library.MagickFunctionImage(self.wand, index, argc, argv)
4702 else:
4703 ch_channel = self._channel_to_mask(channel)
4704 # Use channel method if IM6, else create channel mask for IM7.
4705 if library.MagickFunctionImageChannel:
4706 r = library.MagickFunctionImageChannel(self.wand,
4707 ch_channel,
4708 index,
4709 argc,
4710 argv)
4711 else: # pragma: no cover
4712 # Set active channel, and capture mask to restore.
4713 channel_mask = library.MagickSetImageChannelMask(self.wand,
4714 ch_channel)
4715 r = library.MagickFunctionImage(self.wand, index, argc, argv)
4716 # Restore original state of channels
4717 library.MagickSetImageChannelMask(self.wand, channel_mask)
4718 return r
4720 @manipulative
4721 def fx(self, expression, channel=None):
4722 """Manipulate each pixel of an image by given expression.
4724 FX will preserver current wand instance, and return a new instance of
4725 :class:`Image` containing affected pixels.
4727 Defaults entire image, but can isolate affects to single color channel
4728 by passing :const:`CHANNELS` value to ``channel`` parameter.
4730 .. seealso:: The anatomy of FX expressions can be found at
4731 http://www.imagemagick.org/script/fx.php
4734 :param expression: The entire FX expression to apply
4735 :type expression: :class:`basestring`
4736 :param channel: Optional channel to target.
4737 :type channel: :const:`CHANNELS`
4738 :returns: A new instance of an image with expression applied
4739 :rtype: :class:`Image`
4741 .. versionadded:: 0.4.1
4742 """
4743 assertions.assert_string(expression=expression)
4744 c_expression = binary(expression)
4745 if channel is None:
4746 new_wand = library.MagickFxImage(self.wand, c_expression)
4747 else:
4748 ch_channel = self._channel_to_mask(channel)
4749 if library.MagickFxImageChannel:
4750 new_wand = library.MagickFxImageChannel(self.wand,
4751 ch_channel,
4752 c_expression)
4753 else: # pragma: no cover
4754 # Set active channel, and capture mask to restore.
4755 channel_mask = library.MagickSetImageChannelMask(self.wand,
4756 ch_channel)
4757 new_wand = library.MagickFxImage(self.wand, c_expression)
4758 # Restore original state of channels
4759 library.MagickSetImageChannelMask(self.wand, channel_mask)
4760 if new_wand:
4761 return Image(image=BaseImage(new_wand))
4762 else: # pragma: no cover
4763 self.raise_exception()
4765 @manipulative
4766 @trap_exception
4767 def gamma(self, adjustment_value=1.0, channel=None):
4768 """Gamma correct image.
4770 Specific color channels can be correct individual. Typical values
4771 range between 0.8 and 2.3.
4773 :param adjustment_value: value to adjust gamma level. Default `1.0`
4774 :type adjustment_value: :class:`numbers.Real`
4775 :param channel: optional channel to apply gamma correction
4776 :type channel: :class:`basestring`
4777 :raises TypeError: if ``gamma_point`` is not a :class:`numbers.Real`
4778 :raises ValueError: if ``channel`` is not in :const:`CHANNELS`
4780 .. versionadded:: 0.4.1
4782 """
4783 assertions.assert_real(adjustment_value=adjustment_value)
4784 if channel is None:
4785 r = library.MagickGammaImage(self.wand, adjustment_value)
4786 else:
4787 ch_const = self._channel_to_mask(channel)
4788 if library.MagickGammaImageChannel:
4789 r = library.MagickGammaImageChannel(self.wand,
4790 ch_const,
4791 adjustment_value)
4792 else: # pragma: no cover
4793 # Set active channel, and capture mask to restore.
4794 channel_mask = library.MagickSetImageChannelMask(self.wand,
4795 ch_const)
4796 r = library.MagickGammaImage(self.wand, adjustment_value)
4797 # Restore original state of channels
4798 library.MagickSetImageChannelMask(self.wand, channel_mask)
4799 return r
4801 @manipulative
4802 @trap_exception
4803 def gaussian_blur(self, radius=0.0, sigma=0.0, channel=None):
4804 """Blurs the image. We convolve the image with a gaussian operator
4805 of the given ``radius`` and standard deviation (``sigma``).
4806 For reasonable results, the ``radius`` should be larger
4807 than ``sigma``. Use a ``radius`` of 0 and :meth:`blur()` selects
4808 a suitable ``radius`` for you.
4810 :param radius: the radius of the, in pixels,
4811 not counting the center pixel
4812 :type radius: :class:`numbers.Real`
4813 :param sigma: the standard deviation of the, in pixels
4814 :type sigma: :class:`numbers.Real`
4815 :param channel: Optional color channel to target. See
4816 :const:`CHANNELS`
4817 :type channel: :class:`basestring`
4819 .. versionadded:: 0.3.3
4821 .. versionchanged:: 0.5.5
4822 Added ``channel`` argument.
4823 .. versionchanged:: 0.5.7
4824 Positional arguments ``radius`` & ``sigma`` have been converted
4825 to keyword arguments.
4826 """
4827 assertions.assert_real(radius=radius, sigma=sigma)
4828 if channel is None:
4829 r = library.MagickGaussianBlurImage(self.wand, radius, sigma)
4830 else:
4831 channel_ch = self._channel_to_mask(channel)
4832 if MAGICK_VERSION_NUMBER < 0x700:
4833 r = library.MagickGaussianBlurImageChannel(self.wand,
4834 channel_ch,
4835 radius,
4836 sigma)
4837 else: # pragma: no cover
4838 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
4839 r = library.MagickGaussianBlurImage(self.wand, radius, sigma)
4840 library.MagickSetImageChannelMask(self.wand, mask)
4841 return r
4843 @manipulative
4844 @trap_exception
4845 def hald_clut(self, image, channel=None):
4846 """Replace color values by referencing a Higher And Lower Dimension
4847 (HALD) Color Look Up Table (CLUT). You can generate a HALD image
4848 by using ImageMagick's `hald:` protocol. ::
4850 with Image(filename='rose:') as img:
4851 with Image(filename='hald:3') as hald:
4852 hald.gamma(1.367)
4853 img.hald_clut(hald)
4855 :param image: The HALD color matrix.
4856 :type image: :class:`wand.image.BaseImage`
4857 :param channel: Optional color channel to target. See
4858 :const:`CHANNELS`
4859 :type channel: :class:`basestring`
4861 .. versionadded:: 0.5.0
4863 .. versionchanged:: 0.5.5
4864 Added ``channel`` argument.
4865 """
4866 if not isinstance(image, BaseImage):
4867 raise TypeError('expecting a base image, not ' + repr(image))
4868 if channel is None:
4869 r = library.MagickHaldClutImage(self.wand, image.wand)
4870 else:
4871 channel_ch = self._channel_to_mask(channel)
4872 if MAGICK_VERSION_NUMBER < 0x700:
4873 r = library.MagickHaldClutImageChannel(self.wand, channel_ch,
4874 image.wand)
4875 else: # pragma: no cover
4876 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
4877 r = library.MagickHaldClutImage(self.wand, image.wand)
4878 library.MagickSetImageChannelMask(self.wand, mask)
4879 return r
4881 @manipulative
4882 @trap_exception
4883 def hough_lines(self, width, height=None, threshold=40):
4884 """Identify lines within an image. Use :meth:`canny` to reduce image
4885 to a binary edge before calling this method.
4887 .. warning::
4889 This class method is only available with ImageMagick 7.0.8-41, or
4890 greater.
4892 :param width: Local maxima of neighboring pixels.
4893 :type width: :class:`numbers.Integral`
4894 :param height: Local maxima of neighboring pixels.
4895 :type height: :class:`numbers.Integral`
4896 :param threshold: Line count to limit. Default to 40.
4897 :type threshold: :class:`numbers.Integral`
4898 :raises WandLibraryVersionError: If system's version of ImageMagick
4899 does not support this method.
4901 .. versionadded:: 0.5.5
4902 """
4903 if library.MagickHoughLineImage is None:
4904 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
4905 raise WandLibraryVersionError(msg)
4906 if height is None:
4907 height = width
4908 assertions.assert_unsigned_integer(width=width, height=height,
4909 threshold=threshold)
4910 return library.MagickHoughLineImage(self.wand, width, height,
4911 threshold)
4913 def ift(self, phase, magnitude=True):
4914 """Alias for :meth:`inverse_fourier_transform`.
4916 .. versionadded:: 0.5.7
4917 """
4918 return self.inverse_fourier_transform(phase, magnitude)
4920 @trap_exception
4921 def implode(self, amount=0.0, method="undefined"):
4922 """Creates a "imploding" effect by pulling pixels towards the center
4923 of the image.
4925 :param amount: Normalized degree of effect between `0.0` & `1.0`.
4926 :type amount: :class:`numbers.Real`
4927 :param method: Which interpolate method to apply to effected pixels.
4928 See :const:`PIXEL_INTERPOLATE_METHODS` for a list of
4929 options. Only available with ImageMagick-7.
4930 :type method: :class:`basestring`
4932 .. versionadded:: 0.5.2
4933 """
4934 assertions.assert_real(amount=amount)
4935 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
4936 'wand.image.PIXEL_INTERPOLATE_METHODS',
4937 method=method)
4938 if MAGICK_VERSION_NUMBER < 0x700:
4939 r = library.MagickImplodeImage(self.wand, amount)
4940 else: # pragma: no cover
4941 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
4942 r = library.MagickImplodeImage(self.wand, amount, method_idx)
4943 return r
4945 @trap_exception
4946 def import_pixels(self, x=0, y=0, width=None, height=None,
4947 channel_map='RGB', storage='char', data=None):
4948 """Import pixel data from a byte-string to
4949 the image. The instance of :class:`Image` must already
4950 be allocated with the correct size.
4952 The ``channel_map`` tells ImageMagick which color
4953 channels to export, and what order they should be
4954 written as -- per pixel. Valid entries for
4955 ``channel_map`` are:
4957 - ``'R'`` - Red channel
4958 - ``'G'`` - Green channel
4959 - ``'B'`` - Blue channel
4960 - ``'A'`` - Alpha channel (``0`` is transparent)
4961 - ``'O'`` - Alpha channel (``0`` is opaque)
4962 - ``'C'`` - Cyan channel
4963 - ``'Y'`` - Yellow channel
4964 - ``'M'`` - Magenta channel
4965 - ``'K'`` - Black channel
4966 - ``'I'`` - Intensity channel (only for grayscale)
4967 - ``'P'`` - Padding
4969 See :const:`STORAGE_TYPES` for a list of valid
4970 ``storage`` options. This tells ImageMagick
4971 what type of data it should calculate & write to.
4972 For example; a storage type of ``'char'`` will write
4973 a 8-bit value between 0 ~ 255, a storage type
4974 of ``'short'`` will write a 16-bit value between
4975 0 ~ 65535, and a ``'integer'`` will write a
4976 32-bit value between 0 ~ 4294967295.
4978 .. note::
4980 By default, the entire image will be exported
4981 as ``'char'`` storage with each pixel mapping
4982 Red, Green, Blue, & Alpha channels.
4985 :param x: horizontal starting coordinate of raster.
4986 :type x: :class:`numbers.Integral`
4987 :param y: vertical starting coordinate of raster.
4988 :type y: :class:`numbers.Integral`
4989 :param width: horizontal length of raster.
4990 :type width: :class:`numbers.Integral`
4991 :param height: vertical length of raster.
4992 :type height: :class:`numbers.Integral`
4993 :param channel_map: a string listing the channel data
4994 format for each pixel.
4995 :type channel_map: :class:`basestring`
4996 :param storage: what data type each value should
4997 be calculated as.
4998 :type storage: :class:`basestring`
5000 .. versionadded:: 0.5.0
5001 """
5002 _w, _h = self.size
5003 if width is None:
5004 width = _w
5005 if height is None:
5006 height = _h
5007 assertions.assert_integer(x=x, y=y, width=width, height=height)
5008 assertions.string_in_list(STORAGE_TYPES, 'wand.image.STORAGE_TYPES',
5009 storage=storage)
5010 assertions.assert_string(channel_map=channel_map)
5011 channel_map = channel_map.upper()
5012 valid_channels = 'RGBAOCYMKIP'
5013 for channel in channel_map:
5014 if channel not in valid_channels:
5015 raise ValueError('Unknown channel label: ' +
5016 repr(channel))
5017 if not isinstance(data, abc.Sequence):
5018 raise TypeError('data must list of values, not' +
5019 repr(data))
5020 # Ensure enough data was given.
5021 expected_len = width * height * len(channel_map)
5022 given_len = len(data)
5023 if expected_len != given_len:
5024 msg = 'data length should be {0}, not {1}.'.format(
5025 expected_len,
5026 given_len
5027 )
5028 raise ValueError(msg)
5029 c_storage_types = [
5030 None,
5031 ctypes.c_ubyte,
5032 ctypes.c_double,
5033 ctypes.c_float,
5034 ctypes.c_uint,
5035 ctypes.c_ulong,
5036 ctypes.c_double, # FIXME: Might be c_longdouble ?
5037 ctypes.c_ushort
5038 ]
5039 s_index = STORAGE_TYPES.index(storage)
5040 c_type = c_storage_types[s_index]
5041 c_buffer = (len(data) * c_type)(*data)
5042 r = library.MagickImportImagePixels(self.wand,
5043 x, y, width, height,
5044 binary(channel_map),
5045 s_index,
5046 ctypes.byref(c_buffer))
5047 return r
5049 @trap_exception
5050 def inverse_fourier_transform(self, phase, magnitude=True):
5051 """Applies the inverse of a discrete Fourier transform. The image stack
5052 is replaced with the results. Either a pair of magnitude & phase
5053 images, or real & imaginary (HDRI).
5055 .. code::
5057 from wand.image import Image
5059 with Image(filename='magnitude.png') as img:
5060 with Image(filename='phase.png') as phase:
5061 img.inverse_fourier_transform(phase)
5062 img.save(filename='output.png')
5064 .. seealso:: :meth:`forward_fourier_transform` & :meth:`complex`
5066 .. note::
5068 ImageMagick must have HDRI support to compute real & imaginary
5069 components (i.e. ``magnitude=False``).
5071 :param phase: Second part (image) of the transform. Either the phase,
5072 or the imaginary part.
5073 :type phase: :class:`BaseImage`
5074 :param magnitude: If ``True``, accept magnitude & phase input, else
5075 real & imaginary. Default ``True``
5076 :type magnitude: :class:`bool`
5078 .. versionadded:: 0.5.5
5079 """
5080 if not isinstance(phase, BaseImage):
5081 raise TypeError('phase must be an image, not ' + repr(phase))
5082 assertions.assert_bool(magnitude=magnitude)
5083 return library.MagickInverseFourierTransformImage(self.wand,
5084 phase.wand,
5085 magnitude)
5087 def kurtosis_channel(self, channel='default_channels'):
5088 """Calculates the kurtosis and skewness of the image.
5090 .. code:: python
5092 from wand.image import Image
5094 with Image(filename='input.jpg') as img:
5095 kurtosis, skewness = img.kurtosis_channel()
5097 :param channel: Select which color channel to evaluate. See
5098 :const:`CHANNELS`. Default ``'default_channels'``.
5099 :type channel: :class:`basestring`
5100 :returns: Tuple of :attr:`kurtosis` & :attr:`skewness`
5101 values.
5102 :rtype: :class:`tuple`
5104 .. versionadded:: 0.5.3
5105 """
5106 ch_channel = self._channel_to_mask(channel)
5107 k = ctypes.c_double(0.0)
5108 s = ctypes.c_double(0.0)
5109 if MAGICK_VERSION_NUMBER < 0x700:
5110 library.MagickGetImageChannelKurtosis(self.wand, ch_channel,
5111 ctypes.byref(k),
5112 ctypes.byref(s))
5113 else: # pragma: no cover
5114 # Set active channel, and capture mask to restore.
5115 channel_mask = library.MagickSetImageChannelMask(self.wand,
5116 ch_channel)
5117 library.MagickGetImageKurtosis(self.wand,
5118 ctypes.byref(k),
5119 ctypes.byref(s))
5120 # Restore original state of channels
5121 library.MagickSetImageChannelMask(self.wand, channel_mask)
5122 return k.value, s.value
5124 @manipulative
5125 @trap_exception
5126 def kuwahara(self, radius=1.0, sigma=None):
5127 """Edge preserving noise reduction filter.
5129 https://en.wikipedia.org/wiki/Kuwahara_filter
5131 If ``sigma`` is not given, the value will be calculated as:
5133 sigma = radius - 0.5
5135 To match original algorithm's behavior, increase ``radius`` value by
5136 one:
5138 myImage.kuwahara(myRadius + 1, mySigma)
5140 .. warning::
5142 This class method is only available with ImageMagick 7.0.8-41, or
5143 greater.
5145 :param radius: Size of the filter aperture.
5146 :type radius: :class:`numbers.Real`
5147 :param sigma: Standard deviation of Gaussian filter.
5148 :type sigma: :class:`numbers.Real`
5149 :raises WandLibraryVersionError: If system's version of ImageMagick
5150 does not support this method.
5152 .. versionadded:: 0.5.5
5153 """
5154 if library.MagickKuwaharaImage is None:
5155 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
5156 raise WandLibraryVersionError(msg)
5157 if sigma is None:
5158 sigma = radius - 0.5
5159 assertions.assert_real(radius=radius, sigma=sigma)
5160 return library.MagickKuwaharaImage(self.wand, radius, sigma)
5162 @trap_exception
5163 def level(self, black=0.0, white=None, gamma=1.0, channel=None):
5164 """Adjusts the levels of an image by scaling the colors falling
5165 between specified black and white points to the full available
5166 quantum range.
5168 If only ``black`` is given, ``white`` will be adjusted inward.
5170 :param black: Black point, as a percentage of the system's quantum
5171 range. Defaults to 0.
5172 :type black: :class:`numbers.Real`
5173 :param white: White point, as a percentage of the system's quantum
5174 range. Defaults to 1.0.
5175 :type white: :class:`numbers.Real`
5176 :param gamma: Optional gamma adjustment. Values > 1.0 lighten the
5177 image's midtones while values < 1.0 darken them.
5178 :type gamma: :class:`numbers.Real`
5179 :param channel: The channel type. Available values can be found
5180 in the :const:`CHANNELS` mapping. If ``None``,
5181 normalize all channels.
5182 :type channel: :const:`CHANNELS`
5184 .. note::
5185 Images may not be affected if the ``white`` value is equal, or
5186 less then, the ``black`` value.
5188 .. versionadded:: 0.4.1
5190 """
5191 assertions.assert_real(black=black)
5192 # If white is not given, mimic CLI behavior by reducing top point
5193 if white is None:
5194 white = 1.0 - black
5195 assertions.assert_real(white=white, gamma=gamma)
5197 bp = float(self.quantum_range * black)
5198 wp = float(self.quantum_range * white)
5199 if MAGICK_HDRI: # pragma: no cover
5200 bp -= 0.5 # TODO: Document why HDRI requires 0.5 adjustments.
5201 wp -= 0.5
5202 if channel is None:
5203 r = library.MagickLevelImage(self.wand, bp, gamma, wp)
5204 else:
5205 ch_const = self._channel_to_mask(channel)
5206 if library.MagickLevelImageChannel:
5207 r = library.MagickLevelImageChannel(self.wand,
5208 ch_const,
5209 bp,
5210 gamma,
5211 wp)
5212 else: # pragma: no cover
5213 # Set active channel, and capture mask to restore.
5214 channel_mask = library.MagickSetImageChannelMask(self.wand,
5215 ch_const)
5216 r = library.MagickLevelImage(self.wand, bp, gamma, wp)
5217 # Restore original state of channels
5218 library.MagickSetImageChannelMask(self.wand, channel_mask)
5219 return r
5221 @manipulative
5222 @trap_exception
5223 def level_colors(self, black_color, white_color, channel=None):
5224 """Maps given colors to "black" & "white" values.
5226 .. warning::
5228 This class method is only available with ImageMagick 7.0.8-54, or
5229 greater.
5231 :param black_color: linearly map given color as "black" point.
5232 :type black_color: :class:`Color`
5233 :param white_color: linearly map given color as "white" point.
5234 :type white_color: :class:`Color`
5235 :param channel: target a specific color-channel to levelize.
5236 :type channel: :class:`basestring`
5237 :raises WandLibraryVersionError: If system's version of ImageMagick
5238 does not support this method.
5240 .. versionadded:: 0.5.6
5241 """
5242 if library.MagickLevelImageColors is None:
5243 msg = 'Method requires ImageMagick version 7.0.8-54 or greater.'
5244 raise WandLibraryVersionError(msg)
5245 if isinstance(black_color, string_type):
5246 black_color = Color(black_color)
5247 if isinstance(white_color, string_type):
5248 white_color = Color(white_color)
5249 assertions.assert_color(black_color=black_color,
5250 white_color=white_color)
5251 channel_mask = None
5252 if channel is not None:
5253 ch_const = self._channel_to_mask(channel)
5254 channel_mask = library.MagickSetImageChannelMask(self.wand,
5255 ch_const)
5256 with black_color:
5257 with white_color:
5258 r = library.MagickLevelImageColors(self.wand,
5259 black_color.resource,
5260 white_color.resource,
5261 False)
5262 if channel is not None:
5263 library.MagickSetImageChannelMask(self.wand, channel_mask)
5264 return r
5266 @manipulative
5267 @trap_exception
5268 def levelize(self, black=0.0, white=None, gamma=1.0, channel=None):
5269 """Reverse of :meth:`level()`, this method compresses the range of
5270 colors between ``black`` & ``white`` values.
5272 If only ``black`` is given, ``white`` will be adjusted inward.
5274 .. warning::
5276 This class method is only available with ImageMagick 7.0.8-41, or
5277 greater.
5279 :param black: Black point, as a percentage of the system's quantum
5280 range. Defaults to 0.
5281 :type black: :class:`numbers.Real`
5282 :param white: White point, as a percentage of the system's quantum
5283 range. Defaults to 1.0.
5284 :type white: :class:`numbers.Real`
5285 :param gamma: Optional gamma adjustment. Values > 1.0 lighten the
5286 image's midtones while values < 1.0 darken them.
5287 :type gamma: :class:`numbers.Real`
5288 :param channel: The channel type. Available values can be found
5289 in the :const:`CHANNELS` mapping. If ``None``,
5290 normalize all channels.
5291 :type channel: :const:`CHANNELS`
5292 :raises WandLibraryVersionError: If system's version of ImageMagick
5293 does not support this method.
5295 .. versionadded:: 0.5.5
5296 """
5297 if library.MagickLevelizeImage is None:
5298 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
5299 raise WandLibraryVersionError(msg)
5300 if white is None:
5301 white = float(self.quantum_range)
5302 assertions.assert_real(black=black, white=white, gamma=gamma)
5303 if 0 < black <= 1.0:
5304 black *= self.quantum_range
5305 if 0 < white <= 1.0:
5306 white *= self.quantum_range
5307 if channel is None:
5308 r = library.MagickLevelizeImage(self.wand, black, gamma, white)
5309 else:
5310 ch_const = self._channel_to_mask(channel)
5311 channel_mask = library.MagickSetImageChannelMask(self.wand,
5312 ch_const)
5313 r = library.MagickLevelizeImage(self.wand, black, gamma, white)
5314 library.MagickSetImageChannelMask(self.wand, channel_mask)
5315 return r
5317 @manipulative
5318 @trap_exception
5319 def levelize_colors(self, black_color, white_color, channel=None):
5320 """Reverse of :meth:`level_colors()`, and creates a de-contrasting
5321 gradient of given colors. This works best with grayscale images.
5323 .. warning::
5325 This class method is only available with ImageMagick 7.0.8-54, or
5326 greater.
5328 :param black_color: tint map given color as "black" point.
5329 :type black_color: :class:`Color`
5330 :param white_color: tint map given color as "white" point.
5331 :type white_color: :class:`Color`
5332 :param channel: target a specific color-channel to levelize.
5333 :type channel: :class:`basestring`
5334 :raises WandLibraryVersionError: If system's version of ImageMagick
5335 does not support this method.
5337 .. versionadded:: 0.5.6
5338 """
5339 if library.MagickLevelImageColors is None:
5340 msg = 'Method requires ImageMagick version 7.0.8-54 or greater.'
5341 raise WandLibraryVersionError(msg)
5342 if isinstance(black_color, string_type):
5343 black_color = Color(black_color)
5344 if isinstance(white_color, string_type):
5345 white_color = Color(white_color)
5346 assertions.assert_color(black_color=black_color,
5347 white_color=white_color)
5348 channel_mask = None
5349 ch_const = None
5350 if channel is not None:
5351 ch_const = self._channel_to_mask(channel)
5352 channel_mask = library.MagickSetImageChannelMask(self.wand,
5353 ch_const)
5354 with black_color:
5355 with white_color:
5356 r = library.MagickLevelImageColors(self.wand,
5357 black_color.resource,
5358 white_color.resource,
5359 True)
5360 if channel is not None:
5361 library.MagickSetImageChannelMask(self.wand, channel_mask)
5362 return r
5364 @manipulative
5365 @trap_exception
5366 def linear_stretch(self, black_point=0.0, white_point=1.0):
5367 """Enhance saturation intensity of an image.
5369 :param black_point: Black point between 0.0 and 1.0. Default 0.0
5370 :type black_point: :class:`numbers.Real`
5371 :param white_point: White point between 0.0 and 1.0. Default 1.0
5372 :type white_point: :class:`numbers.Real`
5374 .. versionadded:: 0.4.1
5375 """
5376 assertions.assert_real(black_point=black_point,
5377 white_point=white_point)
5378 linear_range = float(self.width * self.height)
5379 return library.MagickLinearStretchImage(self.wand,
5380 linear_range * black_point,
5381 linear_range * white_point)
5383 @manipulative
5384 def liquid_rescale(self, width, height, delta_x=0, rigidity=0):
5385 """Rescales the image with `seam carving`_, also known as
5386 image retargeting, content-aware resizing, or liquid rescaling.
5388 :param width: the width in the scaled image
5389 :type width: :class:`numbers.Integral`
5390 :param height: the height in the scaled image
5391 :type height: :class:`numbers.Integral`
5392 :param delta_x: maximum seam transversal step.
5393 0 means straight seams. default is 0
5394 :type delta_x: :class:`numbers.Real`
5395 :param rigidity: introduce a bias for non-straight seams.
5396 default is 0
5397 :type rigidity: :class:`numbers.Real`
5398 :raises wand.exceptions.MissingDelegateError:
5399 when ImageMagick isn't configured ``--with-lqr`` option.
5401 .. note::
5403 This feature requires ImageMagick to be configured
5404 ``--with-lqr`` option. Or it will raise
5405 :exc:`~wand.exceptions.MissingDelegateError`:
5407 .. seealso::
5409 `Seam carving`_ --- Wikipedia
5410 The article which explains what seam carving is
5411 on Wikipedia.
5413 .. _Seam carving: http://en.wikipedia.org/wiki/Seam_carving
5415 """
5416 assertions.assert_integer(width=width, height=height)
5417 assertions.assert_real(delta_x=delta_x, rigidity=rigidity)
5418 library.MagickLiquidRescaleImage(self.wand, width, height,
5419 delta_x, rigidity)
5420 try:
5421 self.raise_exception()
5422 except MissingDelegateError as e: # pragma: no cover
5423 raise MissingDelegateError(
5424 str(e) + '\n\nImageMagick in the system is likely to be '
5425 'impossible to load liblqr. You might not install liblqr, '
5426 'or ImageMagick may not compiled with liblqr.'
5427 )
5429 @manipulative
5430 @trap_exception
5431 def local_contrast(self, radius=10, strength=12.5):
5432 """Increase light-dark transitions within image.
5434 .. warning::
5436 This class method is only available with ImageMagick 6.9.3, or
5437 greater.
5439 :param radius: The size of the Gaussian operator. Default value is
5440 ``10.0``.
5441 :type radius: :class:`numbers.Real`
5442 :param strength: Percentage of blur mask to apply. Values can be
5443 between ``0.0`` and ``100`` with a default of
5444 ``12.5``.
5445 :type strength: :class:`numbers.Real`
5447 .. versionadded:: 0.5.7
5448 """
5449 if library.MagickLocalContrastImage is None: # pragma: no cover
5450 msg = 'Method requires ImageMagick version 6.9.3 or greater.'
5451 raise WandLibraryVersionError(msg)
5452 assertions.assert_real(radius=radius, strength=strength)
5453 return library.MagickLocalContrastImage(self.wand, radius, strength)
5455 @manipulative
5456 @trap_exception
5457 def magnify(self):
5458 """Quickly double an image in size. This is a convenience method.
5459 Use :meth:`resize()`, :meth:`resample()`, or :meth:`sample()` for
5460 more control.
5462 .. versionadded:: 0.5.5
5463 """
5464 return library.MagickMagnifyImage(self.wand)
5466 def mean_channel(self, channel='default_channels'):
5467 """Calculates the mean and standard deviation of the image.
5469 .. code:: python
5471 from wand.image import Image
5473 with Image(filename='input.jpg') as img:
5474 mean, stddev = img.mean_channel()
5476 :param channel: Select which color channel to evaluate. See
5477 :const:`CHANNELS`. Default ``'default_channels'``.
5478 :type channel: :class:`basestring`
5479 :returns: Tuple of :attr:`mean` & :attr:`standard_deviation`
5480 values. The ``mean`` value will be between 0.0 &
5481 :attr:`quantum_range`
5482 :rtype: :class:`tuple`
5484 .. versionadded:: 0.5.3
5485 """
5486 ch_channel = self._channel_to_mask(channel)
5487 m = ctypes.c_double(0.0)
5488 s = ctypes.c_double(0.0)
5489 if MAGICK_VERSION_NUMBER < 0x700:
5490 library.MagickGetImageChannelMean(self.wand, ch_channel,
5491 ctypes.byref(m),
5492 ctypes.byref(s))
5493 else: # pragma: no cover
5494 # Set active channel, and capture mask to restore.
5495 channel_mask = library.MagickSetImageChannelMask(self.wand,
5496 ch_channel)
5497 library.MagickGetImageMean(self.wand,
5498 ctypes.byref(m),
5499 ctypes.byref(s))
5500 # Restore original state of channels
5501 library.MagickSetImageChannelMask(self.wand, channel_mask)
5502 return m.value, s.value
5504 @manipulative
5505 @trap_exception
5506 def mean_shift(self, width, height, color_distance=0.1):
5507 """Recalculates pixel value by comparing neighboring pixels within a
5508 color distance, and replacing with a mean value. Works best with
5509 Gray, YCbCr, YIQ, or YUV colorspaces.
5511 .. warning::
5513 This class method is only available with ImageMagick 7.0.8-41, or
5514 greater.
5516 :param width: Size of the neighborhood window in pixels.
5517 :type width: :class:`numbers.Integral`
5518 :param height: Size of the neighborhood window in pixels.
5519 :type height: :class:`numbers.Integral`
5520 :param color_distance: Include pixel values within this color distance.
5521 :type color_distance: :class:`numbers.Real`
5522 :raises WandLibraryVersionError: If system's version of ImageMagick
5523 does not support this method.
5525 .. versionadded:: 0.5.5
5526 """
5527 if library.MagickMeanShiftImage is None:
5528 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
5529 raise WandLibraryVersionError(msg)
5530 assertions.assert_counting_number(width=width, height=height)
5531 assertions.assert_real(color_distance=color_distance)
5532 if 0 < color_distance <= 1.0:
5533 color_distance *= self.quantum_range
5534 return library.MagickMeanShiftImage(self.wand, width, height,
5535 color_distance)
5537 @manipulative
5538 @trap_exception
5539 def merge_layers(self, method):
5540 """Composes all the image layers from the current given image onward
5541 to produce a single image of the merged layers.
5543 The initial canvas's size depends on the given ImageLayerMethod, and is
5544 initialized using the first images background color. The images
5545 are then composited onto that image in sequence using the given
5546 composition that has been assigned to each individual image.
5547 The method must be set with a value from :const:`IMAGE_LAYER_METHOD`
5548 that is acceptable to this operation. (See ImageMagick documentation
5549 for more details.)
5551 :param method: the method of selecting the size of the initial canvas.
5552 :type method: :class:`basestring`
5554 .. versionadded:: 0.4.3
5556 """
5557 assertions.assert_string(method=method)
5558 if method not in ('merge', 'flatten', 'mosaic', 'trimbounds'):
5559 raise ValueError('method can only be \'merge\', \'flatten\', '
5560 '\'mosaic\', or \'trimbounds\'')
5561 m = IMAGE_LAYER_METHOD.index(method)
5562 r = library.MagickMergeImageLayers(self.wand, m)
5563 if r:
5564 self.wand = r
5565 self.reset_sequence()
5566 return bool(r)
5568 @manipulative
5569 def mode(self, width, height=None):
5570 """Replace each pixel with the mathematical mode of the neighboring
5571 colors. This is an alias of the :meth:`statistic` method.
5573 :param width: Number of neighboring pixels to include in mode.
5574 :type width: :class:`numbers.Integral`
5575 :param height: Optional height of neighboring pixels, defaults to the
5576 same value as ``width``.
5577 :type height: :class:`numbers.Integral`
5579 .. versionadded:: 0.5.4
5580 """
5581 if height is None:
5582 height = width
5583 self.statistic('mode', width, height)
5585 @manipulative
5586 @trap_exception
5587 def modulate(self, brightness=100.0, saturation=100.0, hue=100.0):
5588 """Changes the brightness, saturation and hue of an image.
5589 We modulate the image with the given ``brightness``, ``saturation``
5590 and ``hue``.
5592 :param brightness: percentage of brightness
5593 :type brightness: :class:`numbers.Real`
5594 :param saturation: percentage of saturation
5595 :type saturation: :class:`numbers.Real`
5596 :param hue: percentage of hue rotation
5597 :type hue: :class:`numbers.Real`
5598 :raises ValueError: when one or more arguments are invalid
5600 .. versionadded:: 0.3.4
5602 """
5603 assertions.assert_real(brightness=brightness, saturation=saturation,
5604 hue=hue)
5605 return library.MagickModulateImage(
5606 self.wand,
5607 brightness,
5608 saturation,
5609 hue
5610 )
5612 @manipulative
5613 @trap_exception
5614 def morphology(self, method=None, kernel=None, iterations=1, channel=None):
5615 """Manipulate pixels based on the shape of neighboring pixels.
5617 The ``method`` determines what type of effect to apply to matching
5618 ``kernel`` shapes. Common methods can be add/remove,
5619 or lighten/darken pixel values.
5621 The ``kernel`` describes the shape of the matching neighbors. Common
5622 shapes are provided as "built-in" kernels. See
5623 :const`KERNEL_INFO_TYPES` for examples. The format for built-in kernels
5624 is:
5626 .. sourcecode:: text
5628 label:geometry
5630 Where `label` is the kernel name defined in :const:`KERNEL_INFO_TYPES`,
5631 and `:geometry` is an optional geometry size. For example::
5633 with Image(filename='rose:') as img:
5634 img.morphology(method='dilate', kernel='octagon:3x3')
5635 # or simply
5636 img.morphology(method='edgein', kernel='octagon')
5638 Custom kernels can be applied by following a similar format:
5640 .. sourcecode:: text
5642 geometry:args
5644 Where `geometry` is the size of the custom kernel, and `args`
5645 list a comma separated list of values. For example::
5647 custom_kernel='5x3:nan,1,1,1,nan 1,1,1,1,1 nan,1,1,1,nan'
5648 with Image(filename='rose:') as img:
5649 img.morphology(method='dilate', kernel=custom_kernel)
5651 :param method: effect function to apply. See
5652 :const:`MORPHOLOGY_METHODS` for a list of
5653 methods.
5654 :type method: :class:`basestring`
5655 :param kernel: shape to evaluate surrounding pixels. See
5656 :const:`KERNEL_INFO_TYPES` for a list of
5657 built-in shapes.
5658 :type kernel: :class:`basestring`
5659 :param iterations: Number of times a morphology method should be
5660 applied to the image. Default ``1``. Use ``-1`` for
5661 unlimited iterations until the image is unchanged
5662 by the method operator.
5663 :type iterations: :class:`numbers.Integral`
5664 :param channel: Optional color channel to target. See
5665 :const:`CHANNELS`
5666 :type channel: `basestring`
5668 .. versionadded:: 0.5.0
5670 .. versionchanged:: 0.5.5
5671 Added ``channel`` argument.
5672 """
5673 assertions.assert_string(method=method, kernel=kernel)
5674 assertions.assert_integer(iterations=iterations)
5675 buitin = None
5676 geometry = ''
5677 parts = kernel.split(':')
5678 if parts[0] in KERNEL_INFO_TYPES:
5679 buitin = parts[0]
5680 if len(parts) == 2:
5681 geometry = parts[1]
5682 exception_info = libmagick.AcquireExceptionInfo()
5683 if buitin:
5684 kernel_idx = KERNEL_INFO_TYPES.index(buitin)
5685 geometry_info = GeometryInfo()
5686 flags = libmagick.ParseGeometry(binary(geometry),
5687 ctypes.byref(geometry_info))
5688 if buitin in ('unity',):
5689 if (flags & 0x0004) == 0:
5690 geometry_info.rho = 1.0
5691 elif buitin in ('square', 'diamond', 'octagon', 'disk',
5692 'plus', 'cross'):
5693 if (flags & 0x0008) == 0:
5694 geometry_info.sigma = 1.0
5695 elif buitin in ('ring',):
5696 if (flags & 0x0001) == 0:
5697 geometry_info.xi = 1.0
5698 elif buitin in ('rectangle',):
5699 if (flags & 0x0004) == 0:
5700 geometry_info.rho = geometry_info.sigma
5701 if geometry_info.rho < 1.0:
5702 geometry_info.rho = 3.0
5703 if geometry_info.sigma < 1.0:
5704 geometry_info.sigma = geometry_info.rho
5705 if (flags & 0x0001) == 0:
5706 geometry_info.xi = (geometry_info.rho - 1.0) / 2.0
5707 if (flags & 0x0002) == 0:
5708 geometry_info.psi = (geometry_info.sigma - 1.0) / 2.0
5709 elif buitin in ('chebyshev', 'manhattan', 'octagonal',
5710 'euclidean'):
5711 if (flags & 0x0008) == 0:
5712 geometry_info.sigma = 100.0
5713 elif (flags & 0x2000) != 0:
5714 geometry_info.sigma = (float(self.quantum_range) /
5715 (geometry_info.sigma + 1.0))
5716 elif (flags & 0x1000) != 0:
5717 geometry_info.sigma *= float(self.quantum_range) / 100.0
5718 if MAGICK_VERSION_NUMBER < 0x700:
5719 kernel_info = libmagick.AcquireKernelBuiltIn(
5720 kernel_idx,
5721 ctypes.byref(geometry_info)
5722 )
5723 else: # pragma: no cover
5724 kernel_info = libmagick.AcquireKernelBuiltIn(
5725 kernel_idx,
5726 ctypes.byref(geometry_info),
5727 exception_info
5728 )
5729 elif kernel:
5730 if MAGICK_VERSION_NUMBER < 0x700:
5731 kernel_info = libmagick.AcquireKernelInfo(
5732 binary(kernel)
5733 )
5734 else: # pragma: no cover
5735 kernel_info = libmagick.AcquireKernelInfo(
5736 binary(kernel),
5737 exception_info
5738 )
5739 r = None
5740 exception_info = libmagick.DestroyExceptionInfo(exception_info)
5741 if kernel_info:
5742 method_idx = MORPHOLOGY_METHODS.index(method)
5743 if channel is None:
5744 r = library.MagickMorphologyImage(self.wand, method_idx,
5745 iterations, kernel_info)
5746 else:
5747 channel_ch = self._channel_to_mask(channel)
5748 if MAGICK_VERSION_NUMBER < 0x700:
5749 r = library.MagickMorphologyImageChannel(self.wand,
5750 channel_ch,
5751 method_idx,
5752 iterations,
5753 kernel_info)
5754 else: # pragma: no cover
5755 mask = library.MagickSetImageChannelMask(self.wand,
5756 channel_ch)
5757 r = library.MagickMorphologyImage(self.wand, method_idx,
5758 iterations, kernel_info)
5759 library.MagickSetImageChannelMask(self.wand, mask)
5760 kernel_info = libmagick.DestroyKernelInfo(kernel_info)
5761 else:
5762 raise ValueError('Unable to parse kernel info for ' +
5763 repr(kernel))
5764 return r
5766 @manipulative
5767 @trap_exception
5768 def motion_blur(self, radius=0.0, sigma=0.0, angle=0.0, channel=None):
5769 """Apply a Gaussian blur along an ``angle`` direction. This
5770 simulates motion movement.
5772 :param radius: Aperture size of the Gaussian operator.
5773 :type radius: :class:`numbers.Real`
5774 :param sigma: Standard deviation of the Gaussian operator.
5775 :type sigma: :class:`numbers.Real`
5776 :param angle: Apply the effect along this angle.
5777 :type angle: :class:`numbers.Real`
5779 .. versionadded:: 0.5.4
5780 """
5781 assertions.assert_real(radius=radius, sigma=sigma, angle=angle)
5782 if channel is None:
5783 r = library.MagickMotionBlurImage(self.wand, radius, sigma, angle)
5784 else:
5785 ch_const = self._channel_to_mask(channel)
5786 if MAGICK_VERSION_NUMBER < 0x700:
5787 r = library.MagickMotionBlurImageChannel(self.wand,
5788 ch_const,
5789 radius,
5790 sigma,
5791 angle)
5792 else: # pragma: no cover
5793 # Set active channel, and capture mask to restore.
5794 channel_mask = library.MagickSetImageChannelMask(self.wand,
5795 ch_const)
5796 r = library.MagickMotionBlurImage(self.wand, radius, sigma,
5797 angle)
5798 # Restore original state of channels
5799 library.MagickSetImageChannelMask(self.wand, channel_mask)
5800 return r
5802 @manipulative
5803 @trap_exception
5804 def negate(self, grayscale=False, channel=None):
5805 """Negate the colors in the reference image.
5807 :param grayscale: if set, only negate grayscale pixels in the image.
5808 :type grayscale: :class:`bool`
5809 :param channel: the channel type. available values can be found
5810 in the :const:`CHANNELS` mapping. If ``None``,
5811 negate all channels.
5812 :type channel: :class:`basestring`
5814 .. versionadded:: 0.3.8
5816 """
5817 if channel is None:
5818 r = library.MagickNegateImage(self.wand, grayscale)
5819 else:
5820 ch_const = self._channel_to_mask(channel)
5821 if library.MagickNegateImageChannel:
5822 r = library.MagickNegateImageChannel(self.wand, ch_const,
5823 grayscale)
5824 else: # pragma: no cover
5825 # Set active channel, and capture mask to restore.
5826 channel_mask = library.MagickSetImageChannelMask(self.wand,
5827 ch_const)
5828 r = library.MagickNegateImage(self.wand, grayscale)
5829 # Restore original state of channels
5830 library.MagickSetImageChannelMask(self.wand, channel_mask)
5831 return r
5833 @manipulative
5834 @trap_exception
5835 def noise(self, noise_type='uniform', attenuate=1.0, channel=None):
5836 """Adds noise to image.
5838 :param noise_type: type of noise to apply. See :const:`NOISE_TYPES`.
5839 :type noise_type: :class:`basestring`
5840 :param attenuate: rate of distribution. Only available in
5841 ImageMagick-7. Default is ``1.0``.
5842 :type attenuate: :class:`numbers.Real`
5843 :param channel: Optionally target a color channel to apply noise to.
5844 See :const:`CHANNELS`.
5845 :type channel: :class:`basestring`
5847 .. versionadded:: 0.5.3
5849 .. versionchanged:: 0.5.5
5850 Added optional ``channel`` argument.
5851 """
5852 assertions.string_in_list(NOISE_TYPES, 'wand.image.NOISE_TYPES',
5853 noise_type=noise_type)
5854 assertions.assert_real(attenuate=attenuate)
5855 noise_type_idx = NOISE_TYPES.index(noise_type)
5856 if MAGICK_VERSION_NUMBER < 0x700:
5857 if channel is None:
5858 r = library.MagickAddNoiseImage(self.wand, noise_type_idx)
5859 else:
5860 channel_ch = self._channel_to_mask(channel)
5861 r = library.MagickAddNoiseImageChannel(self.wand,
5862 channel_ch,
5863 noise_type_idx)
5864 else: # pragma: no cover
5865 if channel is None:
5866 r = library.MagickAddNoiseImage(self.wand, noise_type_idx,
5867 attenuate)
5868 else:
5869 channel_ch = self._channel_to_mask(channel)
5870 mask = library.MagickSetImageChannelMask(self.wand,
5871 channel_ch)
5872 r = library.MagickAddNoiseImage(self.wand, noise_type_idx,
5873 attenuate)
5874 library.MagickSetImageChannelMask(self.wand, mask)
5875 return r
5877 @manipulative
5878 @trap_exception
5879 def normalize(self, channel=None):
5880 """Normalize color channels.
5882 :param channel: the channel type. available values can be found
5883 in the :const:`CHANNELS` mapping. If ``None``,
5884 normalize all channels.
5885 :type channel: :class:`basestring`
5887 """
5888 if channel is None:
5889 r = library.MagickNormalizeImage(self.wand)
5890 else:
5891 ch_const = self._channel_to_mask(channel)
5892 if library.MagickNormalizeImageChannel:
5893 r = library.MagickNormalizeImageChannel(self.wand, ch_const)
5894 else: # pragma: no cover
5895 with Image(image=self) as mask:
5896 # Set active channel, and capture mask to restore.
5897 channel_mask = library.MagickSetImageChannelMask(mask.wand,
5898 ch_const)
5899 r = library.MagickNormalizeImage(mask.wand)
5900 # Restore original state of channels.
5901 library.MagickSetImageChannelMask(mask.wand,
5902 channel_mask)
5903 # Copy adjusted mask over original value.
5904 copy_mask = COMPOSITE_OPERATORS.index('copy_' + channel)
5905 library.MagickCompositeImage(self.wand,
5906 mask.wand,
5907 copy_mask,
5908 False,
5909 0,
5910 0)
5911 return r
5913 @manipulative
5914 @trap_exception
5915 def oil_paint(self, radius=0.0, sigma=0.0):
5916 """Simulates an oil painting by replace each pixel with most frequent
5917 surrounding color.
5919 :param radius: The size of the surrounding neighbors.
5920 :type radius: :class:`numbers.Real`
5921 :param sigma: The standard deviation used by the Gaussian operator.
5922 This is only available with ImageMagick-7.
5923 :type sigma: :class:`numbers.Real`
5925 .. versionadded:: 0.5.4
5926 """
5927 assertions.assert_real(radius=radius, sigma=sigma)
5928 if MAGICK_VERSION_NUMBER < 0x700:
5929 r = library.MagickOilPaintImage(self.wand, radius)
5930 else: # pragma: no cover
5931 r = library.MagickOilPaintImage(self.wand, radius, sigma)
5932 return r
5934 @manipulative
5935 @trap_exception
5936 def opaque_paint(self, target=None, fill=None, fuzz=0.0, invert=False,
5937 channel=None):
5938 """Replace any color that matches ``target`` with ``fill``. Use
5939 ``fuzz`` to control the threshold of the target match.
5940 The ``invert`` will replace all colors *but* the pixels matching
5941 the ``target`` color.
5943 :param target: The color to match.
5944 :type target: :class:`wand.color.Color`
5945 :param fill: The color to paint with.
5946 :type fill: :class:`wand.color.Color`
5947 :param fuzz: Normalized real number between `0.0` and
5948 :attr:`quantum_range`. Default is `0.0`.
5949 :type fuzz: class:`numbers.Real`
5950 :param invert: Replace all colors that do not match target.
5951 Default is ``False``.
5952 :type invert: :class:`bool`
5953 :param channel: Optional color channel to target. See
5954 :const:`CHANNELS`
5955 :type channel: :class:`basestring`
5957 .. versionadded:: 0.5.4
5959 .. versionchanged:: 0.5.5
5960 Added ``channel`` paramater.
5961 """
5962 if isinstance(target, string_type):
5963 target = Color(target)
5964 if isinstance(fill, string_type):
5965 fill = Color(fill)
5966 assertions.assert_color(target=target, fill=fill)
5967 assertions.assert_real(fuzz=fuzz)
5968 assertions.assert_bool(invert=invert)
5969 with target:
5970 with fill:
5971 if channel is None:
5972 r = library.MagickOpaquePaintImage(self.wand,
5973 target.resource,
5974 fill.resource,
5975 fuzz,
5976 invert)
5977 else:
5978 channel_ch = self._channel_to_mask(channel)
5979 if MAGICK_VERSION_NUMBER < 0x700:
5980 r = library.MagickOpaquePaintImageChannel(
5981 self.wand, channel_ch, target.resource,
5982 fill.resource, fuzz, invert
5983 )
5984 else: # pragma: no cover
5985 mask = library.MagickSetImageChannelMask(self.wand,
5986 channel_ch)
5987 r = library.MagickOpaquePaintImage(self.wand,
5988 target.resource,
5989 fill.resource,
5990 fuzz,
5991 invert)
5992 library.MagickSetImageChannelMask(self.wand, mask)
5993 return r
5995 @manipulative
5996 @trap_exception
5997 def optimize_layers(self):
5998 """Attempts to crop each frame to the smallest image without altering
5999 the animation. For best results, call
6000 :meth:`Image.coalesce() <wand.image.BaseImage.coalesce>` before
6001 manipulating any frames. For timing accuracy, any
6002 :attr:`SingleImage.delay <wand.sequence.SingleImage.delay>` overwrites
6003 must be applied after optimizing layers.
6005 .. note::
6007 This will only affect ``GIF`` image formates.
6009 .. versionadded:: 0.5.0
6010 """
6011 r = library.MagickOptimizeImageLayers(self.wand)
6012 if r:
6013 self.wand = r
6014 self.reset_sequence()
6015 return bool(r)
6017 @manipulative
6018 @trap_exception
6019 def optimize_transparency(self):
6020 """Iterates over frames, and sets transparent values for each
6021 pixel unchanged by previous frame.
6023 .. note::
6025 This will only affect ``GIF`` image formates.
6027 .. versionadded:: 0.5.0
6028 """
6029 if library.MagickOptimizeImageTransparency:
6030 return library.MagickOptimizeImageTransparency(self.wand)
6031 else: # pragma: no cover
6032 raise AttributeError('`MagickOptimizeImageTransparency\' not '
6033 'available on current version of MagickWand '
6034 'library.')
6036 @manipulative
6037 @trap_exception
6038 def ordered_dither(self, threshold_map='threshold', channel=None):
6039 """Executes a ordered-based dither operations based on predetermined
6040 threshold maps.
6042 +-----------+-------+-----------------------------+
6043 | Map | Alias | Description |
6044 +===========+=======+=============================+
6045 | threshold | 1x1 | Threshold 1x1 (non-dither) |
6046 +-----------+-------+-----------------------------+
6047 | checks | 2x1 | Checkerboard 2x1 (dither) |
6048 +-----------+-------+-----------------------------+
6049 | o2x2 | 2x2 | Ordered 2x2 (dispersed) |
6050 +-----------+-------+-----------------------------+
6051 | o3x3 | 3x3 | Ordered 3x3 (dispersed) |
6052 +-----------+-------+-----------------------------+
6053 | o4x4 | 4x4 | Ordered 4x4 (dispersed) |
6054 +-----------+-------+-----------------------------+
6055 | o8x8 | 8x8 | Ordered 8x8 (dispersed) |
6056 +-----------+-------+-----------------------------+
6057 | h4x4a | 4x1 | Halftone 4x4 (angled) |
6058 +-----------+-------+-----------------------------+
6059 | h6x6a | 6x1 | Halftone 6x6 (angled) |
6060 +-----------+-------+-----------------------------+
6061 | h8x8a | 8x1 | Halftone 8x8 (angled) |
6062 +-----------+-------+-----------------------------+
6063 | h4x4o | | Halftone 4x4 (orthogonal) |
6064 +-----------+-------+-----------------------------+
6065 | h6x6o | | Halftone 6x6 (orthogonal) |
6066 +-----------+-------+-----------------------------+
6067 | h8x8o | | Halftone 8x8 (orthogonal) |
6068 +-----------+-------+-----------------------------+
6069 | h16x16o | | Halftone 16x16 (orthogonal) |
6070 +-----------+-------+-----------------------------+
6071 | c5x5b | c5x5 | Circles 5x5 (black) |
6072 +-----------+-------+-----------------------------+
6073 | c5x5w | | Circles 5x5 (white) |
6074 +-----------+-------+-----------------------------+
6075 | c6x6b | c6x6 | Circles 6x6 (black) |
6076 +-----------+-------+-----------------------------+
6077 | c6x6w | | Circles 6x6 (white) |
6078 +-----------+-------+-----------------------------+
6079 | c7x7b | c7x7 | Circles 7x7 (black) |
6080 +-----------+-------+-----------------------------+
6081 | c7x7w | | Circles 7x7 (white) |
6082 +-----------+-------+-----------------------------+
6084 :param threshold_map: Name of threshold dither to use, followed by
6085 optional arguments.
6086 :type threshold_map: :class:`basestring`
6087 :param channel: Optional argument to apply dither to specific color
6088 channel. See :const:`CHANNELS`.
6089 :type channel: :class:`basestring`
6091 .. versionadded:: 0.5.7
6092 """
6093 assertions.assert_string(threshold_map=threshold_map)
6094 bmap = binary(threshold_map)
6095 if MAGICK_VERSION_NUMBER <= 0x700:
6096 if channel is None:
6097 r = library.MagickOrderedPosterizeImage(self.wand, bmap)
6098 else:
6099 channel_ch = self._channel_to_mask(channel)
6100 r = library.MagickOrderedPosterizeImageChannel(self.wand,
6101 channel_ch,
6102 bmap)
6103 else: # pragma: no cover
6104 if channel is None:
6105 r = library.MagickOrderedDitherImage(self.wand, bmap)
6106 else:
6107 channel_ch = self._channel_to_mask(channel)
6108 mask = library.MagickSetImageChannelMask(self.wand,
6109 channel_ch)
6110 r = library.MagickOrderedDitherImage(self.wand, bmap)
6111 library.MagickSetImageChannelMask(self.wand, mask)
6112 return r
6114 def parse_meta_geometry(self, geometry):
6115 """Helper method to translate geometry format, and calculate
6116 meta-characters against image dimensions.
6118 See "Image Geometry" definitions & examples for more info:
6119 https://imagemagick.org/script/command-line-processing.php#geometry
6121 :param geometry: user string following ImageMagick's geometry format.
6122 :type geometry: :class:`basestring`
6123 :returns: Calculated width, height, offset-x, & offset-y.
6124 :rtype: :class:`tuple`
6125 :raises ValueError: If given geometry can not be parsed.
6127 .. versionadded:: 0.5.6
6128 """
6129 assertions.assert_string(geometry=geometry)
6130 x = ctypes.c_ssize_t(0)
6131 y = ctypes.c_ssize_t(0)
6132 width = ctypes.c_size_t(self.width)
6133 height = ctypes.c_size_t(self.height)
6134 r = libmagick.ParseMetaGeometry(binary(geometry),
6135 ctypes.byref(x),
6136 ctypes.byref(y),
6137 ctypes.byref(width),
6138 ctypes.byref(height))
6139 if not bool(r):
6140 raise ValueError('Unable to parse geometry')
6141 return (width.value, height.value, x.value, y.value)
6143 def percent_escape(self, string_format):
6144 """Convenience method that expands ImageMagick's `Percent Escape`_
6145 characters into image attribute values.
6147 .. _Percent Escape: https://imagemagick.org/script/escape.php
6149 .. code::
6151 with wand.image import Image
6153 with Image(filename='tests/assets/sasha.jpg') as img:
6154 print(img.percent_escape('%f %wx%h'))
6155 #=> sasha.jpg 204x247
6157 .. note::
6159 Not all percent escaped values can be populated as I/O operations
6160 are managed by Python, and not the CLI utility.
6162 :param string_format: The precent escaped string to be translated.
6163 :type string_format: :class:`basestring`
6164 :returns: String of expanded values.
6165 :rtype: :class:`basestring`
6167 .. versionadded:: 0.5.6
6168 """
6169 local_overwrites = {
6170 '%m': self.format,
6171 '%[magick]': self.format
6172 }
6173 for k, v in local_overwrites.items():
6174 string_format = string_format.replace(k, v)
6175 self.options['format'] = string_format
6176 return text(self.make_blob('INFO'))
6178 @manipulative
6179 @trap_exception
6180 def polaroid(self, angle=0.0, caption=None, font=None, method='undefined'):
6181 """Creates a special effect simulating a Polaroid photo.
6183 :param angle: applies a shadow effect along this angle.
6184 :type angle: :class:`numbers.Real`
6185 :param caption: Writes a message at the bottom of the photo's border.
6186 :type caption: :class:`basestring`
6187 :param font: Specify font style.
6188 :type font: :class:`wand.font.Font`
6189 :param method: Interpolation method. ImageMagick-7 only.
6190 :type method: :class:`basestring`
6192 .. versionadded:: 0.5.4
6193 """
6194 assertions.assert_real(angle=angle)
6195 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
6196 'wand.image.PIXEL_INTERPOLATE_METHODS',
6197 method=method)
6198 ctx_ptr = library.NewDrawingWand()
6199 if caption:
6200 assertions.assert_string(caption=caption)
6201 caption = binary(caption)
6202 library.MagickSetImageProperty(self.wand, b'Caption',
6203 caption)
6204 if isinstance(font, Font):
6205 if font.path:
6206 library.DrawSetFont(ctx_ptr, binary(font.path))
6207 if font.size:
6208 library.DrawSetFontSize(ctx_ptr, font.size)
6209 if font.color:
6210 with font.color:
6211 library.DrawSetFillColor(ctx_ptr, font.color.resource)
6212 library.DrawSetTextAntialias(ctx_ptr, font.antialias)
6213 if font.stroke_color:
6214 with font.stroke_color:
6215 library.DrawSetStrokeColor(ctx_ptr,
6216 font.stroke_color.resource)
6217 if font.stroke_width:
6218 library.DrawSetStrokeWidth(ctx_ptr, font.stroke_width)
6219 elif font:
6220 raise TypeError('font must be in instance of '
6221 'wand.font.Font, not ' + repr(font))
6222 if MAGICK_VERSION_NUMBER < 0x700:
6223 r = library.MagickPolaroidImage(self.wand, ctx_ptr, angle)
6224 else: # pragma: no cover
6225 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
6226 r = library.MagickPolaroidImage(self.wand, ctx_ptr, caption, angle,
6227 method_idx)
6228 ctx_ptr = library.DestroyDrawingWand(ctx_ptr)
6229 return r
6231 @manipulative
6232 @trap_exception
6233 def polynomial(self, arguments):
6234 """Replace image with the sum of all images in a sequence by
6235 calculating the pixel values a coefficient-weight value, and a
6236 polynomial-exponent.
6238 For example::
6240 with Image(filename='rose:') as img:
6241 img.polynomial(arguments=[0.5, 1.0])
6243 The output image will be calculated as:
6245 .. math::
6247 output = 0.5 * image ^ {1.0}
6249 This can work on multiple images in a sequence by calculating across
6250 each frame in the image stack.
6252 .. code::
6254 with Image(filename='2frames.gif') as img:
6255 img.polynomial(arguments=[0.5, 1.0, 0.25, 1.25])
6257 Where the results would be calculated as:
6259 .. math::
6261 output = 0.5 * frame1 ^ {1.0} + 0.25 * frame2 ^ {1.25}
6263 .. warning::
6265 This class method is only available with ImageMagick 7.0.8-41, or
6266 greater.
6268 :param arguments: A list of real numbers where at least two numbers
6269 (weight & exponent) are need for each image in the
6270 sequence.
6271 :type arguments: :class:`collections.abc.Sequence`
6272 :raises WandLibraryVersionError: If system's version of ImageMagick
6273 does not support this method.
6275 .. versionadded:: 0.5.5
6276 """
6277 if library.MagickPolynomialImage is None:
6278 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
6279 raise WandLibraryVersionError(msg)
6280 if not isinstance(arguments, abc.Sequence):
6281 raise TypeError('expected sequence of doubles, not ' +
6282 repr(arguments))
6283 argc = len(arguments)
6284 argv = (ctypes.c_double * argc)(*arguments)
6285 return library.MagickPolynomialImage(self.wand, (argc >> 1), argv)
6287 @manipulative
6288 @trap_exception
6289 def posterize(self, levels=None, dither='no'):
6290 """Reduce color levels per channel.
6292 :param levels: Number of levels per channel.
6293 :type levels: :class:`numbers.Integral`
6294 :param dither: Dither method to apply.
6295 See :const:`DITHER_METHODS`.
6296 :type dither: `basestring`
6298 .. versionadded:: 0.5.0
6299 """
6300 assertions.assert_integer(levels=levels)
6301 assertions.string_in_list(DITHER_METHODS, 'wand.image.DITHER_METHODS',
6302 dither=dither)
6303 dither_idx = DITHER_METHODS.index(dither)
6304 return library.MagickPosterizeImage(self.wand, levels, dither_idx)
6306 @manipulative
6307 @trap_exception
6308 def quantize(self, number_colors, colorspace_type=None,
6309 treedepth=0, dither=False, measure_error=False):
6310 """`quantize` analyzes the colors within a sequence of images and
6311 chooses a fixed number of colors to represent the image. The goal of
6312 the algorithm is to minimize the color difference between the input and
6313 output image while minimizing the processing time.
6315 :param number_colors: The target number of colors to reduce the image.
6316 :type number_colors: :class:`numbers.Integral`
6317 :param colorspace_type: Available value can be found
6318 in the :const:`COLORSPACE_TYPES`. Defaults
6319 :attr:`colorspace`.
6320 :type colorspace_type: :class:`basestring`
6321 :param treedepth: A value between ``0`` & ``8`` where ``0`` will
6322 allow ImageMagick to calculate the optimal depth
6323 with ``Log4(number_colors)``. Default value is ``0``.
6324 :type treedepth: :class:`numbers.Integral`
6325 :param dither: Perform dither operation between neighboring pixel
6326 values. If using ImageMagick-6, this can be a value
6327 of ``True``, or ``False``. With ImageMagick-7, use
6328 a string from :const:`DITHER_METHODS`. Default
6329 ``False``.
6330 :type dither: :class:`bool`, or :class:`basestring`
6331 :param measure_error: Include total quantization error of all pixels
6332 in an image & quantized value.
6333 :type measure_error: :class:`bool`
6335 .. versionadded:: 0.4.2
6337 .. versionchanged:: 0.5.9
6338 Fixed ImageMagick-7 ``dither`` argument, and added keyword defaults.
6339 """
6340 assertions.assert_integer(number_colors=number_colors)
6341 if colorspace_type is None:
6342 colorspace_type = self.colorspace
6343 assertions.string_in_list(COLORSPACE_TYPES,
6344 'wand.image.COLORSPACE_TYPES',
6345 colorspace_type=colorspace_type)
6346 assertions.assert_integer(treedepth=treedepth)
6347 if MAGICK_VERSION_NUMBER < 0x700:
6348 assertions.assert_bool(dither=dither)
6349 else: # pragma: no cover
6350 if dither is False:
6351 dither = 'no'
6352 elif dither is True:
6353 dither = 'riemersma'
6354 assertions.string_in_list(DITHER_METHODS,
6355 'wand.image.DITHER_METHODS',
6356 dither=dither)
6357 dither = DITHER_METHODS.index(dither)
6358 assertions.assert_bool(measure_error=measure_error)
6359 return library.MagickQuantizeImage(
6360 self.wand, number_colors,
6361 COLORSPACE_TYPES.index(colorspace_type),
6362 treedepth, dither, measure_error
6363 )
6365 @manipulative
6366 @trap_exception
6367 def random_threshold(self, low=0.0, high=1.0, channel=None):
6368 """Performs a random dither to force a pixel into a binary black &
6369 white state. Each color channel operarates independently from each
6370 other.
6372 :param low: bottom threshold. Any pixel value below the given value
6373 will be rendered "0", or no value. Given threshold value
6374 can be between ``0.0`` & ``1.0``, or ``0`` &
6375 :attr:`quantum_range`.
6376 :type low: :class:`numbers.Real`
6377 :param high: top threshold. Any pixel value above the given value
6378 will be rendered as max quantum value. Given threshold
6379 value can be between ``0.0`` & ``1.0``, or ``0`` &
6380 :attr:`quantum_range`.
6381 :type high: :class:`numbers.Real`
6382 :param channel: Optional argument to apply dither to specific color
6383 channel. See :const:`CHANNELS`.
6384 :type channel: :class:`basestring`
6386 .. versionadded:: 0.5.7
6387 """
6388 assertions.assert_real(low=low, high=high)
6389 if 0 < low <= 1.0:
6390 low *= self.quantum_range
6391 if 0 < high <= 1.0:
6392 high *= self.quantum_range
6393 if channel is None:
6394 r = library.MagickRandomThresholdImage(self.wand, low, high)
6395 else:
6396 ch_channel = self._channel_to_mask(channel)
6397 if MAGICK_VERSION_NUMBER < 0x700:
6398 r = library.MagickRandomThresholdImageChannel(self.wand,
6399 ch_channel,
6400 low,
6401 high)
6402 else: # pragma: no cover
6403 # Set active channel, and capture mask to restore.
6404 channel_mask = library.MagickSetImageChannelMask(self.wand,
6405 ch_channel)
6406 r = library.MagickRandomThresholdImage(self.wand, low, high)
6407 # Restore original state of channels
6408 library.MagickSetImageChannelMask(self.wand, channel_mask)
6409 return r
6411 def range_channel(self, channel='default_channels'):
6412 """Calculate the minimum and maximum of quantum values in image.
6414 .. code:: python
6416 from wand.image import Image
6418 with Image(filename='input.jpg') as img:
6419 minima, maxima = img.range_channel()
6421 :param channel: Select which color channel to evaluate. See
6422 :const:`CHANNELS`. Default ``'default_channels'``.
6423 :type channel: :class:`basestring`
6424 :returns: Tuple of :attr:`minima` & :attr:`maxima`
6425 values. Each value will be between 0.0 &
6426 :attr:`quantum_range`.
6427 :rtype: :class:`tuple`
6429 .. versionadded:: 0.5.3
6430 """
6431 ch_channel = self._channel_to_mask(channel)
6432 min_color = ctypes.c_double(0.0)
6433 max_color = ctypes.c_double(0.0)
6434 if MAGICK_VERSION_NUMBER < 0x700:
6435 library.MagickGetImageChannelRange(self.wand, ch_channel,
6436 ctypes.byref(min_color),
6437 ctypes.byref(max_color))
6438 else: # pragma: no cover
6439 # Set active channel, and capture mask to restore.
6440 channel_mask = library.MagickSetImageChannelMask(self.wand,
6441 ch_channel)
6442 library.MagickGetImageRange(self.wand,
6443 ctypes.byref(min_color),
6444 ctypes.byref(max_color))
6445 # Restore original state of channels
6446 library.MagickSetImageChannelMask(self.wand, channel_mask)
6447 return min_color.value, max_color.value
6449 @manipulative
6450 @trap_exception
6451 def range_threshold(self, low_black=0.0, low_white=None, high_white=None,
6452 high_black=None):
6453 """Applies soft & hard thresholding.
6455 For a soft thresholding, parameters should be monotonically increasing:
6457 with Image(filename='text.png') as img:
6458 img.range_threshold(0.2, 0.4, 0.6, 0.8)
6460 For a hard thresholding, parameters should be the same:
6462 with Image(filename='text.png') as img:
6463 img.range_threshold(0.4, 0.4, 0.6, 0.6)
6465 .. warning::
6467 This class method is only available with ImageMagick 7.0.8-41, or
6468 greater.
6470 :param low_black: Define the minimum threshold value.
6471 :type low_black: :class:`numbers.Real`
6472 :param low_white: Define the minimum threshold value.
6473 :type low_white: :class:`numbers.Real`
6474 :param high_white: Define the maximum threshold value.
6475 :type high_white: :class:`numbers.Real`
6476 :param high_black: Define the maximum threshold value.
6477 :type high_black: :class:`numbers.Real`
6478 :raises WandLibraryVersionError: If system's version of ImageMagick
6479 does not support this method.
6481 .. versionadded:: 0.5.5
6482 """
6483 if library.MagickRangeThresholdImage is None:
6484 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
6485 raise WandLibraryVersionError(msg)
6486 # Populate defaults to follow CLI behavior
6487 if low_white is None:
6488 low_white = low_black
6489 if high_white is None:
6490 high_white = low_white
6491 if high_black is None:
6492 high_black = high_white
6493 assertions.assert_real(low_black=low_black, low_white=low_white,
6494 high_white=high_white, high_black=high_black)
6495 if 0 < low_black <= 1.0:
6496 low_black *= self.quantum_range
6497 if 0 < low_white <= 1.0:
6498 low_white *= self.quantum_range
6499 if 0 < high_white <= 1.0:
6500 high_white *= self.quantum_range
6501 if 0 < high_black <= 1.0:
6502 high_black *= self.quantum_range
6503 return library.MagickRangeThresholdImage(self.wand,
6504 low_black, low_white,
6505 high_white, high_black)
6507 @trap_exception
6508 def read_mask(self, clip_mask=None):
6509 """Sets the read mask where the gray values of the clip mask
6510 are used to blend during composite operations. Call this method with
6511 a ``None`` argument to clear any previously set masks.
6513 This method is also useful for :meth:`compare` method for limiting
6514 region of interest.
6516 .. warning::
6517 This method is only available with ImageMagick-7.
6519 :param clip_mask: Image to reference as blend mask.
6520 :type clip_mask: :class:`BaseImage`
6522 .. versionadded:: 0.5.7
6523 """
6524 r = False
6525 ReadPixelMask = 0x000001
6526 if library.MagickSetImageMask is None:
6527 raise WandLibraryVersionError('Method requires ImageMagick-7.')
6528 else: # pragma: no cover
6529 if clip_mask is None:
6530 r = library.MagickSetImageMask(self.wand, ReadPixelMask, None)
6531 elif isinstance(clip_mask, BaseImage):
6532 r = library.MagickSetImageMask(self.wand, ReadPixelMask,
6533 clip_mask.wand)
6534 return r
6536 @manipulative
6537 @trap_exception
6538 def remap(self, affinity=None, method='no'):
6539 """Rebuild image palette with closest color from given affinity image.
6541 :param affinity: reference image.
6542 :type affinity: :class:`BaseImage`
6543 :param method: dither method. See :const:`DITHER_METHODS`.
6544 Default is ``'no'`` dither.
6545 :type method: :class:`basestring`
6547 .. versionadded:: 0.5.3
6548 """
6549 if not isinstance(affinity, BaseImage):
6550 raise TypeError('Expecting affinity to be a BaseImage, not ' +
6551 repr(affinity))
6552 assertions.string_in_list(DITHER_METHODS, 'wand.image.DITHER_METHODS',
6553 method=method)
6554 method_idx = DITHER_METHODS.index(method)
6555 return library.MagickRemapImage(self.wand, affinity.wand, method_idx)
6557 @manipulative
6558 @trap_exception
6559 def resample(self, x_res=None, y_res=None, filter='undefined', blur=1):
6560 """Adjust the number of pixels in an image so that when displayed at
6561 the given Resolution or Density the image will still look the same size
6562 in real world terms.
6564 :param x_res: the X resolution (density) in the scaled image. default
6565 is the original resolution.
6566 :type x_res: :class:`numbers.Real`
6567 :param y_res: the Y resolution (density) in the scaled image. default
6568 is the original resolution.
6569 :type y_res: :class:`numbers.Real`
6570 :param filter: a filter type to use for resizing. choose one in
6571 :const:`FILTER_TYPES`. default is ``'undefined'``
6572 which means IM will try to guess best one to use.
6573 :type filter: :class:`basestring`, :class:`numbers.Integral`
6574 :param blur: the blur factor where > 1 is blurry, < 1 is sharp.
6575 default is 1
6576 :type blur: :class:`numbers.Real`
6578 .. versionadded:: 0.4.5
6579 """
6580 if x_res is None:
6581 x_res, _ = self.resolution
6582 if y_res is None:
6583 _, y_res = self.resolution
6584 assertions.assert_real(x_res=x_res, y_res=y_res, blur=blur)
6585 if x_res < 1:
6586 raise ValueError('x_res must be a Real number, not ' +
6587 repr(x_res))
6588 elif y_res < 1:
6589 raise ValueError('y_res must be a Real number, not ' +
6590 repr(y_res))
6591 elif not isinstance(filter, (string_type, numbers.Integral)):
6592 raise TypeError('filter must be one string defined in wand.image.'
6593 'FILTER_TYPES or an integer, not ' + repr(filter))
6594 if isinstance(filter, string_type):
6595 try:
6596 filter = FILTER_TYPES.index(filter)
6597 except IndexError:
6598 raise ValueError(repr(filter) + ' is an invalid filter type; '
6599 'choose on in ' + repr(FILTER_TYPES))
6600 elif (isinstance(filter, numbers.Integral) and
6601 not (0 <= filter < len(FILTER_TYPES))):
6602 raise ValueError(repr(filter) + ' is an invalid filter type')
6603 blur = ctypes.c_double(float(blur))
6604 if self.animation:
6605 self.wand = library.MagickCoalesceImages(self.wand)
6606 self.reset_sequence()
6607 library.MagickSetLastIterator(self.wand)
6608 n = library.MagickGetIteratorIndex(self.wand)
6609 library.MagickResetIterator(self.wand)
6610 for i in xrange(n + 1):
6611 library.MagickSetIteratorIndex(self.wand, i)
6612 r = library.MagickResampleImage(self.wand, x_res, y_res,
6613 filter, blur)
6614 else:
6615 r = library.MagickResampleImage(self.wand, x_res, y_res,
6616 filter, blur)
6617 return r
6619 def reset_coords(self):
6620 """Reset the coordinate frame of the image so to the upper-left corner
6621 is (0, 0) again (crop and rotate operations change it).
6623 .. versionadded:: 0.2.0
6625 """
6626 library.MagickResetImagePage(self.wand, None)
6628 def reset_sequence(self):
6629 """Abstract method prototype.
6630 See :meth:`wand.image.Image.reset_sequence()`.
6632 .. versionadded:: 0.6.0
6633 """
6634 pass
6636 @manipulative
6637 @trap_exception
6638 def resize(self, width=None, height=None, filter='undefined', blur=1):
6639 """Resizes the image.
6641 :param width: the width in the scaled image. default is the original
6642 width
6643 :type width: :class:`numbers.Integral`
6644 :param height: the height in the scaled image. default is the original
6645 height
6646 :type height: :class:`numbers.Integral`
6647 :param filter: a filter type to use for resizing. choose one in
6648 :const:`FILTER_TYPES`. default is ``'undefined'``
6649 which means IM will try to guess best one to use
6650 :type filter: :class:`basestring`, :class:`numbers.Integral`
6651 :param blur: the blur factor where > 1 is blurry, < 1 is sharp.
6652 default is 1
6653 :type blur: :class:`numbers.Real`
6655 .. versionchanged:: 0.2.1
6656 The default value of ``filter`` has changed from ``'triangle'``
6657 to ``'undefined'`` instead.
6659 .. versionchanged:: 0.1.8
6660 The ``blur`` parameter changed to take :class:`numbers.Real`
6661 instead of :class:`numbers.Rational`.
6663 .. versionadded:: 0.1.1
6665 """
6666 if width is None:
6667 width = self.width
6668 if height is None:
6669 height = self.height
6670 assertions.assert_counting_number(width=width, height=height)
6671 assertions.assert_real(blur=blur)
6672 if not isinstance(filter, (string_type, numbers.Integral)):
6673 raise TypeError('filter must be one string defined in wand.image.'
6674 'FILTER_TYPES or an integer, not ' + repr(filter))
6675 if isinstance(filter, string_type):
6676 try:
6677 filter = FILTER_TYPES.index(filter)
6678 except IndexError:
6679 raise ValueError(repr(filter) + ' is an invalid filter type; '
6680 'choose on in ' + repr(FILTER_TYPES))
6681 elif (isinstance(filter, numbers.Integral) and
6682 not (0 <= filter < len(FILTER_TYPES))):
6683 raise ValueError(repr(filter) + ' is an invalid filter type')
6684 blur = ctypes.c_double(float(blur))
6685 if self.animation:
6686 self.wand = library.MagickCoalesceImages(self.wand)
6687 self.reset_sequence()
6688 library.MagickSetLastIterator(self.wand)
6689 n = library.MagickGetIteratorIndex(self.wand)
6690 library.MagickResetIterator(self.wand)
6691 for i in xrange(n + 1):
6692 library.MagickSetIteratorIndex(self.wand, i)
6693 r = library.MagickResizeImage(self.wand, width, height,
6694 filter, blur)
6695 library.MagickSetSize(self.wand, width, height)
6696 else:
6697 r = library.MagickResizeImage(self.wand, width, height,
6698 filter, blur)
6699 library.MagickSetSize(self.wand, width, height)
6700 return r
6702 @manipulative
6703 @trap_exception
6704 def rotate(self, degree, background=None, reset_coords=True):
6705 """Rotates the image right. It takes a ``background`` color
6706 for ``degree`` that isn't a multiple of 90.
6708 :param degree: a degree to rotate. multiples of 360 affect nothing
6709 :type degree: :class:`numbers.Real`
6710 :param background: an optional background color.
6711 default is transparent
6712 :type background: :class:`wand.color.Color`
6713 :param reset_coords: optional flag. If set, after the rotation, the
6714 coordinate frame will be relocated to the upper-left corner of
6715 the new image. By default is `True`.
6716 :type reset_coords: :class:`bool`
6718 .. versionadded:: 0.2.0
6719 The ``reset_coords`` parameter.
6721 .. versionadded:: 0.1.8
6723 """
6724 if background is None:
6725 background = Color('transparent')
6726 elif isinstance(background, string_type):
6727 background = Color(background)
6728 assertions.assert_color(background=background)
6729 assertions.assert_real(degree=degree)
6730 with background:
6731 if self.animation:
6732 self.wand = library.MagickCoalesceImages(self.wand)
6733 self.reset_sequence()
6734 library.MagickSetLastIterator(self.wand)
6735 n = library.MagickGetIteratorIndex(self.wand)
6736 library.MagickResetIterator(self.wand)
6737 for i in range(0, n + 1):
6738 library.MagickSetIteratorIndex(self.wand, i)
6739 result = library.MagickRotateImage(self.wand,
6740 background.resource,
6741 degree)
6742 if reset_coords:
6743 library.MagickResetImagePage(self.wand, None)
6744 else:
6745 result = library.MagickRotateImage(self.wand,
6746 background.resource,
6747 degree)
6748 if reset_coords:
6749 self.reset_coords()
6750 return result
6752 @manipulative
6753 @trap_exception
6754 def rotational_blur(self, angle=0.0, channel=None):
6755 """Blur an image in a radius around the center of an image.
6757 .. warning:: Requires ImageMagick-6.8.8 or greater.
6759 :param angle: Degrees of rotation to blur with.
6760 :type angle: :class:`numbers.Real`
6761 :param channel: Optional channel to apply the effect against. See
6762 :const:`CHANNELS` for a list of possible values.
6763 :type channel: :class:`basestring`
6764 :raises WandLibraryVersionError: If system's version of ImageMagick
6765 does not support this method.
6767 .. versionadded:: 0.5.4
6768 """
6769 if not library.MagickRotationalBlurImage: # pragma: no cover
6770 msg = ("Method `rotational_blur` not available on installed "
6771 "version of ImageMagick library. ")
6772 raise WandLibraryVersionError(msg)
6773 assertions.assert_real(angle=angle)
6774 if channel:
6775 channel_ch = self._channel_to_mask(channel)
6776 if MAGICK_VERSION_NUMBER < 0x700:
6777 r = library.MagickRotationalBlurImageChannel(self.wand,
6778 channel_ch,
6779 angle)
6780 else: # pragma: no cover
6781 channel_mask = library.MagickSetImageChannelMask(self.wand,
6782 channel_ch)
6783 r = library.MagickRotationalBlurImage(self.wand, angle)
6784 library.MagickSetImageChannelMask(self.wand, channel_mask)
6785 else:
6786 r = library.MagickRotationalBlurImage(self.wand, angle)
6787 return r
6789 @manipulative
6790 @trap_exception
6791 def sample(self, width=None, height=None):
6792 """Resizes the image by sampling the pixels. It's basically quicker
6793 than :meth:`resize()` except less quality as a trade-off.
6795 :param width: the width in the scaled image. default is the original
6796 width
6797 :type width: :class:`numbers.Integral`
6798 :param height: the height in the scaled image. default is the original
6799 height
6800 :type height: :class:`numbers.Integral`
6802 .. versionadded:: 0.3.4
6804 """
6805 if width is None:
6806 width = self.width
6807 if height is None:
6808 height = self.height
6809 assertions.assert_counting_number(width=width, height=height)
6810 if self.animation:
6811 self.wand = library.MagickCoalesceImages(self.wand)
6812 self.reset_sequence()
6813 library.MagickSetLastIterator(self.wand)
6814 n = library.MagickGetIteratorIndex(self.wand)
6815 library.MagickResetIterator(self.wand)
6816 for i in xrange(n + 1):
6817 library.MagickSetIteratorIndex(self.wand, i)
6818 r = library.MagickSampleImage(self.wand, width, height)
6819 library.MagickSetSize(self.wand, width, height)
6820 else:
6821 r = library.MagickSampleImage(self.wand, width, height)
6822 library.MagickSetSize(self.wand, width, height)
6823 return bool(r)
6825 @manipulative
6826 @trap_exception
6827 def scale(self, columns=1, rows=1):
6828 """Increase image size by scaling each pixel value by given ``columns``
6829 and ``rows``.
6831 :param columns: The number of columns, in pixels, to scale the image
6832 horizontally.
6833 :type columns: :class:`numbers.Integral`
6834 :param rows: The number of rows, in pixels, to scale the image
6835 vertically.
6836 :type rows: :class:`numbers.Integral`
6838 .. versionadded:: 0.5.7
6839 """
6840 assertions.assert_counting_number(columns=columns, rows=rows)
6841 return library.MagickScaleImage(self.wand, columns, rows)
6843 @manipulative
6844 @trap_exception
6845 def selective_blur(self, radius=0.0, sigma=0.0, threshold=0.0,
6846 channel=None):
6847 """Blur an image within a given threshold.
6849 For best effects, use a value between 10% and 50% of
6850 :attr:`quantum_range`
6852 .. code::
6854 from wand.image import Image
6856 with Image(filename='photo.jpg') as img:
6857 # Apply 8x3 blur with a 10% threshold
6858 img.selective_blur(8.0, 3.0, 0.1 * img.quantum_range)
6860 :param radius: Size of gaussian aperture.
6861 :type radius: :class:`numbers.Real`
6862 :param sigma: Standard deviation of gaussian operator.
6863 :type sigma: :class:`numbers.Real`
6864 :param threshold: Only pixels within contrast threshold are effected.
6865 Value should be between ``0.0`` and
6866 :attr:`quantum_range`.
6867 :type threshold: :class:`numbers.Real`
6868 :param channel: Optional color channel to target. See
6869 :const:`CHANNELS`
6870 :type channel: :class:`basestring`
6872 .. versionadded:: 0.5.3
6874 .. versionchanged:: 0.5.5
6875 Added ``channel`` argument.
6876 """
6877 assertions.assert_real(radius=radius, sigma=sigma, threshold=threshold)
6878 if channel is None:
6879 r = library.MagickSelectiveBlurImage(self.wand,
6880 radius,
6881 sigma,
6882 threshold)
6883 else:
6884 channel_ch = self._channel_to_mask(channel)
6885 if MAGICK_VERSION_NUMBER < 0x700:
6886 r = library.MagickSelectiveBlurImageChannel(self.wand,
6887 channel_ch,
6888 radius,
6889 sigma,
6890 threshold)
6891 else: # pragma: no cover
6892 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
6893 r = library.MagickSelectiveBlurImage(self.wand,
6894 radius,
6895 sigma,
6896 threshold)
6897 library.MagickSetImageChannelMask(self.wand, mask)
6898 return r
6900 @manipulative
6901 @trap_exception
6902 def sepia_tone(self, threshold=0.8):
6903 """Creates a Sepia Tone special effect similar to a darkroom chemical
6904 toning.
6906 :param threshold: The extent of the toning. Value can be between ``0``
6907 & :attr:`quantum_range`, or ``0`` & ``1.0``.
6908 Default value is ``0.8`` or "80%".
6909 :type threshold: :class:`numbers.Real`
6911 .. versionadded:: 0.5.7
6912 """
6913 assertions.assert_real(threshold=threshold)
6914 if 0.0 < threshold <= 1.0:
6915 threshold *= self.quantum_range
6916 return library.MagickSepiaToneImage(self.wand, threshold)
6918 @manipulative
6919 @trap_exception
6920 def shade(self, gray=False, azimuth=0.0, elevation=0.0):
6921 """Creates a 3D effect by simulating a light from an
6922 elevated angle.
6924 :param gray: Isolate the effect on pixel intensity.
6925 Default is False.
6926 :type gray: :class:`bool`
6927 :param azimuth: Angle from x-axis.
6928 :type azimuth: :class:`numbers.Real`
6929 :param elevation: Amount of pixels from the z-axis.
6930 :type elevation: :class:`numbers.Real`
6932 .. versionadded:: 0.5.0
6933 """
6934 assertions.assert_real(azimuth=azimuth, elevation=elevation)
6935 return library.MagickShadeImage(self.wand, gray,
6936 azimuth, elevation)
6938 @manipulative
6939 @trap_exception
6940 def shadow(self, alpha=0.0, sigma=0.0, x=0, y=0):
6941 """Generates an image shadow.
6943 :param alpha: Ratio of transparency.
6944 :type alpha: :class:`numbers.Real`
6945 :param sigma: Standard deviation of the gaussian filter.
6946 :type sigma: :class:`numbers.Real`
6947 :param x: x-offset.
6948 :type x: :class:`numbers.Integral`
6949 :param y: y-offset.
6950 :type y: :class:`numbers.Integral`
6952 .. versionadded:: 0.5.0
6953 """
6954 assertions.assert_real(alpha=alpha, sigma=sigma)
6955 assertions.assert_integer(x=x, y=y)
6956 return library.MagickShadowImage(self.wand, alpha, sigma, x, y)
6958 @manipulative
6959 @trap_exception
6960 def sharpen(self, radius=0.0, sigma=0.0, channel=None):
6961 """Applies a gaussian effect to enhance the sharpness of an
6962 image.
6964 .. note::
6966 For best results, ensure ``radius`` is larger than
6967 ``sigma``.
6969 Defaults values of zero will have ImageMagick attempt
6970 to auto-select suitable values.
6972 :param radius: size of gaussian aperture.
6973 :type radius: :class:`numbers.Real`
6974 :param sigma: Standard deviation of the gaussian filter.
6975 :type sigma: :class:`numbers.Real`
6976 :param channel: Optional color channel to target. See
6977 :const:`CHANNELS`.
6978 :type channel: :class:`basestring`
6980 .. versionadded:: 0.5.0
6982 .. versionchanged:: 0.5.5
6983 Added ``channel`` argument.
6984 """
6985 assertions.assert_real(radius=radius, sigma=sigma)
6986 if channel is None:
6987 r = library.MagickSharpenImage(self.wand, radius, sigma)
6988 else:
6989 channel_ch = self._channel_to_mask(channel)
6990 if MAGICK_VERSION_NUMBER < 0x700:
6991 r = library.MagickSharpenImageChannel(self.wand,
6992 channel_ch,
6993 radius, sigma)
6994 else: # pragma: no cover
6995 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
6996 r = library.MagickSharpenImage(self.wand, radius, sigma)
6997 library.MagickSetImageChannelMask(self.wand, mask)
6998 return r
7000 @manipulative
7001 @trap_exception
7002 def shave(self, columns=0, rows=0):
7003 """Remove pixels from the edges.
7005 :param columns: amount to shave off both sides of the x-axis.
7006 :type columns: :class:`numbers.Integral`
7007 :param rows: amount to shave off both sides of the y-axis.
7008 :type rows: :class:`numbers.Integral`
7010 .. versionadded:: 0.5.0
7011 """
7012 assertions.assert_integer(columns=columns, row=rows)
7013 return library.MagickShaveImage(self.wand, columns, rows)
7015 @manipulative
7016 @trap_exception
7017 def shear(self, background='WHITE', x=0.0, y=0.0):
7018 """Shears the image to create a parallelogram, and fill the space
7019 created with a ``background`` color.
7021 :param background: Color to fill the void created by shearing the
7022 image.
7023 :type background: :class:`wand.color.Color`
7024 :param x: Slide the image along the X-axis.
7025 :type x: :class:`numbers.Real`
7026 :param y: Slide the image along the Y-axis.
7027 :type y: :class:`numbers.Real`
7029 .. versionadded:: 0.5.4
7030 """
7031 if isinstance(background, string_type):
7032 background = Color(background)
7033 assertions.assert_color(background=background)
7034 assertions.assert_real(x=x, y=y)
7035 with background:
7036 r = library.MagickShearImage(self.wand, background.resource, x, y)
7037 return r
7039 @manipulative
7040 @trap_exception
7041 def sigmoidal_contrast(self, sharpen=True, strength=0.0, midpoint=0.0,
7042 channel=None):
7043 """Modifies the contrast of the image by applying non-linear sigmoidal
7044 algorithm.
7046 .. code:: python
7048 with Image(filename='photo.jpg') as img:
7049 img.sigmoidal_contrast(sharpen=True,
7050 strength=3,
7051 midpoint=0.65 * img.quantum_range)
7053 :param sharpen: Increase the contrast when ``True`` (default), else
7054 reduces contrast.
7055 :type sharpen: :class:`bool`
7056 :param strength: How much to adjust the contrast. Where a value of
7057 ``0.0`` has no effect, ``3.0`` is typical, and
7058 ``20.0`` is extreme.
7059 :type strength: :class:`numbers.Real`
7060 :param midpoint: Normalized value between `0.0` & :attr:`quantum_range`
7061 :type midpoint: :class:`numbers.Real`
7062 :param channel: Optional color channel to target. See
7063 :const:`CHANNELS`.
7064 :type channel: :class:`basestring`
7066 .. versionadded:: 0.5.4
7068 .. versionchanged:: 0.5.5
7069 Added ``channel`` argument.
7070 """
7071 assertions.assert_bool(sharpen=sharpen)
7072 assertions.assert_real(strength=strength, midpoint=midpoint)
7073 if channel is None:
7074 r = library.MagickSigmoidalContrastImage(self.wand,
7075 sharpen,
7076 strength,
7077 midpoint)
7078 else:
7079 channel_ch = self._channel_to_mask(channel)
7080 if MAGICK_VERSION_NUMBER < 0x700:
7081 r = library.MagickSigmoidalContrastImageChannel(
7082 self.wand, channel_ch, sharpen, strength, midpoint
7083 )
7084 else: # pragma: no cover
7085 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
7086 r = library.MagickSigmoidalContrastImage(self.wand,
7087 sharpen,
7088 strength,
7089 midpoint)
7090 library.MagickSetImageChannelMask(self.wand, mask)
7091 return r
7093 def similarity(self, reference, threshold=0.0,
7094 metric='undefined'):
7095 """Scan image for best matching ``reference`` image, and
7096 return location & similarity.
7098 Use parameter ``threshold`` to stop subimage scanning if the matching
7099 similarity value is below the given value. This is the same as the CLI
7100 ``-similarity-threshold`` option.
7102 This method will always return a location & the lowest computed
7103 similarity value. Users are responsible for checking the similarity
7104 value to determine if a matching location is valid. Traditionally, a
7105 similarity value greater than `0.3183099` is considered dissimilar.
7107 .. code:: python
7109 from wand.image import Image
7111 dissimilarity_threshold = 0.318
7112 similarity_threshold = 0.05
7113 with Image(filename='subject.jpg') as img:
7114 with Image(filename='object.jpg') as reference:
7115 location, diff = img.similarity(reference,
7116 similarity_threshold)
7117 if diff > dissimilarity_threshold:
7118 print('Images too dissimilar to match')
7119 elif diff <= similarity_threshold:
7120 print('First match @ {left}x{top}'.format(**location))
7121 else:
7122 print('Best match @ {left}x{top}'.format(**location))
7124 .. warning::
7126 This operation can be slow to complete.
7128 :param reference: Image to search for.
7129 :type reference: :class:`wand.image.Image`
7130 :param threshold: Stop scanning if reference similarity is
7131 below given threshold. Value can be between ``0.0``
7132 and :attr:`quantum_range`. Default is ``0.0``.
7133 :type threshold: :class:`numbers.Real`
7134 :param metric: specify which comparison algorithm to use. See
7135 :const:`COMPARE_METRICS` for a list of values.
7136 Only used by ImageMagick-7.
7137 :type metric: :class:`basestring`
7138 :returns: List of location & similarity value. Location being a
7139 dictionary of ``width``, ``height``, ``left``, & ``top``.
7140 The similarity value is the compare distance, so a value of
7141 ``0.0`` means an exact match.
7142 :rtype: :class:`tuple` (:class:`dict`, :class:`numbers.Real`)
7144 .. versionadded:: 0.5.4
7146 has been added.
7147 """
7148 assertions.assert_real(threshold=threshold)
7149 if not isinstance(reference, BaseImage):
7150 raise TypeError('reference must be in instance of '
7151 'wand.image.Image, not ' + repr(reference))
7152 rio = RectangleInfo(0, 0, 0, 0)
7153 diff = ctypes.c_double(0.0)
7154 if MAGICK_VERSION_NUMBER < 0x700:
7155 artifact_value = binary(str(threshold)) # FIXME
7156 library.MagickSetImageArtifact(self.wand,
7157 b'compare:similarity-threshold',
7158 artifact_value)
7159 r = library.MagickSimilarityImage(self.wand,
7160 reference.wand,
7161 ctypes.byref(rio),
7162 ctypes.byref(diff))
7163 else: # pragma: no cover
7164 assertions.string_in_list(COMPARE_METRICS,
7165 'wand.image.COMPARE_METRICS',
7166 metric=metric)
7167 metric_idx = COMPARE_METRICS.index(metric)
7168 r = library.MagickSimilarityImage(self.wand,
7169 reference.wand,
7170 metric_idx,
7171 threshold,
7172 ctypes.byref(rio),
7173 ctypes.byref(diff))
7174 if not r: # pragma: no cover
7175 self.raise_exception()
7176 else:
7177 r = library.DestroyMagickWand(r)
7178 location = dict(width=rio.width, height=rio.height,
7179 top=rio.y, left=rio.x)
7180 return (location, diff.value)
7182 @manipulative
7183 @trap_exception
7184 def sketch(self, radius=0.0, sigma=0.0, angle=0.0):
7185 """Simulates a pencil sketch effect. For best results, ``radius``
7186 value should be larger than ``sigma``.
7188 :param radius: size of Gaussian aperture.
7189 :type radius: :class:`numbers.Real`
7190 :param sigma: standard deviation of the Gaussian operator.
7191 :type sigma: :class:`numbers.Real`
7192 :param angle: direction of blur.
7193 :type angle: :class:`numbers.Real`
7195 .. versionadded:: 0.5.3
7196 """
7197 assertions.assert_real(radius=radius, sigma=sigma, angle=angle)
7198 return library.MagickSketchImage(self.wand, radius, sigma, angle)
7200 @trap_exception
7201 def smush(self, stacked=False, offset=0):
7202 """Appends all images together. Similar behavior to :meth:`concat`,
7203 but with an optional offset between images.
7205 :param stacked: If True, will join top-to-bottom. If False, join images
7206 from left-to-right (default).
7207 :type stacked: :class:`bool`
7208 :param offset: Minimum space (in pixels) between each join.
7209 :type offset: :class:`numbers.Integral`
7211 .. versionadded:: 0.5.3
7212 """
7213 assertions.assert_integer(offset=offset)
7214 library.MagickResetIterator(self.wand)
7215 result = library.MagickSmushImages(self.wand, bool(stacked), offset)
7216 if result:
7217 self.wand = result
7218 self.reset_sequence()
7219 return bool(result)
7221 @manipulative
7222 @trap_exception
7223 def solarize(self, threshold=0.0, channel=None):
7224 """Simulates extreme overexposure.
7226 :param threshold: between ``0.0`` and :attr:`quantum_range`.
7227 :type threshold: :class:`numbers.Real`
7228 :param channel: Optional color channel to target. See
7229 :const:`CHANNELS`
7230 :type channel: :class:`basestring`
7232 .. versionadded:: 0.5.3
7234 .. versionchanged:: 0.5.5
7235 Added ``channel`` argument.
7236 """
7237 assertions.assert_real(threshold=threshold)
7238 if channel is None:
7239 r = library.MagickSolarizeImage(self.wand, threshold)
7240 else:
7241 channel_ch = self._channel_to_mask(channel)
7242 if MAGICK_VERSION_NUMBER < 0x700:
7243 r = library.MagickSolarizeImageChannel(self.wand,
7244 channel_ch,
7245 threshold)
7246 else: # pragma: no cover
7247 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
7248 r = library.MagickSolarizeImage(self.wand, threshold)
7249 library.MagickSetImageChannelMask(self.wand, mask)
7250 return r
7252 @manipulative
7253 @trap_exception
7254 def sparse_color(self, method, colors, channel_mask=0x7):
7255 """Interpolates color values between points on an image.
7257 The ``colors`` argument should be a dict mapping
7258 :class:`~wand.color.Color` keys to coordinate tuples.
7260 For example::
7262 from wand.color import Color
7263 from wand.image import Image
7265 colors = {
7266 Color('RED'): (10, 50),
7267 Color('YELLOW'): (174, 32),
7268 Color('ORANGE'): (74, 123)
7269 }
7270 with Image(filename='input.png') as img:
7271 img.sparse_colors('bilinear', colors)
7273 The available interpolate methods are:
7275 - ``'barycentric'``
7276 - ``'bilinear'``
7277 - ``'shepards'``
7278 - ``'voronoi'``
7279 - ``'inverse'``
7280 - ``'manhattan'``
7282 You can control which color channels are effected by building a custom
7283 channel mask. For example::
7285 from wand.image import Image, CHANNELS
7287 with Image(filename='input.png') as img:
7288 colors = {
7289 img[50, 50]: (50, 50),
7290 img[100, 50]: (100, 50),
7291 img[50, 75]: (50, 75),
7292 img[100, 100]: (100, 100)
7293 }
7294 # Only apply Voronoi to Red & Alpha channels
7295 mask = CHANNELS['red'] | CHANNELS['alpha']
7296 img.sparse_colors('voronoi', colors, channel_mask=mask)
7298 :param method: Interpolate method. See :const:`SPARSE_COLOR_METHODS`
7299 :type method: :class:`basestring`
7300 :param colors: A dictionary of :class:`~wand.color.Color` keys mapped
7301 to an (x, y) coordinate tuple.
7302 :type colors: :class:`abc.Mapping`
7303 { :class:`~wand.color.Color`: (int, int) }
7304 :param channel_mask: Isolate specific color channels to apply
7305 interpolation. Default to RGB channels.
7306 :type channel_mask: :class:`numbers.Integral`
7308 .. versionadded:: 0.5.3
7309 """
7310 assertions.string_in_list(SPARSE_COLOR_METHODS,
7311 'wand.image.SPARSE_COLOR_METHODS',
7312 method=method)
7313 if not isinstance(colors, abc.Mapping):
7314 raise TypeError('Colors must be a dict, not' + repr(colors))
7315 assertions.assert_unsigned_integer(channel_mask=channel_mask)
7316 method_idx = SPARSE_COLOR_METHODS[method]
7317 arguments = list()
7318 for color, point in colors.items():
7319 if isinstance(color, string_type):
7320 color = Color(color)
7321 x, y = point
7322 arguments.append(x)
7323 arguments.append(y)
7324 with color as c:
7325 if channel_mask & CHANNELS['red']:
7326 arguments.append(c.red)
7327 if channel_mask & CHANNELS['green']:
7328 arguments.append(c.green)
7329 if channel_mask & CHANNELS['blue']:
7330 arguments.append(c.blue)
7331 if channel_mask & CHANNELS['alpha']:
7332 arguments.append(c.alpha)
7333 argc = len(arguments)
7334 args = (ctypes.c_double * argc)(*arguments)
7335 if MAGICK_VERSION_NUMBER < 0x700:
7336 r = library.MagickSparseColorImage(self.wand,
7337 channel_mask,
7338 method_idx,
7339 argc,
7340 args)
7341 else: # pragma: no cover
7342 # Set active channel, and capture mask to restore.
7343 channel_mask = library.MagickSetImageChannelMask(self.wand,
7344 channel_mask)
7345 r = library.MagickSparseColorImage(self.wand,
7346 method_idx,
7347 argc,
7348 args)
7349 # Restore original state of channels
7350 library.MagickSetImageChannelMask(self.wand, channel_mask)
7351 return r
7353 @manipulative
7354 @trap_exception
7355 def splice(self, width=None, height=None, x=None, y=None):
7356 """Partitions image by splicing a ``width`` x ``height`` rectangle at
7357 (``x``, ``y``) offset coordinate. The space inserted will be replaced
7358 by the :attr:`background_color` value.
7360 :param width: number of pixel columns.
7361 :type width: :class:`numbers.Integral`
7362 :param height: number of pixel rows.
7363 :type height: :class:`numbers.Integral`
7364 :param x: offset on the X-axis.
7365 :type x: :class:`numbers.Integral`
7366 :param y: offset on the Y-axis.
7367 :type y: :class:`numbers.Integral`
7369 .. versionadded:: 0.5.3
7370 """
7371 assertions.assert_integer(width=width, height=height, x=x, y=y)
7372 return library.MagickSpliceImage(self.wand, width, height, x, y)
7374 @manipulative
7375 @trap_exception
7376 def spread(self, radius=0.0, method='undefined'):
7377 """Randomly displace pixels within a defined radius.
7379 :param radius: Distance a pixel can be displaced from source. Default
7380 value is ``0.0``, which will allow ImageMagick to auto
7381 select a radius.
7382 :type radius: :class:`numbers.Real`
7383 :param method: Interpolation method. Only available with ImageMagick-7.
7384 See :const:`PIXEL_INTERPOLATE_METHODS`.
7386 .. versionadded:: 0.5.3
7388 .. versionchanged:: 0.5.7
7389 Added default value to ``radius``.
7390 """
7391 assertions.assert_real(radius=radius)
7392 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
7393 'wand.image.PIXEL_INTERPOLATE_METHODS',
7394 method=method)
7395 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
7396 if MAGICK_VERSION_NUMBER < 0x700:
7397 r = library.MagickSpreadImage(self.wand, radius)
7398 else: # pragma: no cover
7399 r = library.MagickSpreadImage(self.wand, method_idx, radius)
7400 return r
7402 @manipulative
7403 @trap_exception
7404 def statistic(self, stat='undefined', width=None, height=None,
7405 channel=None):
7406 """Replace each pixel with the statistic results from neighboring pixel
7407 values. The ``width`` & ``height`` defines the size, or aperture, of
7408 the neighboring pixels.
7410 :param stat: The type of statistic to calculate. See
7411 :const:`STATISTIC_TYPES`.
7412 :type stat: :class:`basestring`
7413 :param width: The size of neighboring pixels on the X-axis.
7414 :type width: :class:`numbers.Integral`
7415 :param height: The size of neighboring pixels on the Y-axis.
7416 :type height: :class:`numbers.Integral`
7417 :param channel: Optional color channel to target. See
7418 :const:`CHANNELS`
7419 :type channel: :class:`basestring`
7421 .. versionadded:: 0.5.3
7423 .. versionchanged:: 0.5.5
7424 Added optional ``channel`` argument.
7425 """
7426 assertions.string_in_list(STATISTIC_TYPES,
7427 'wand.image.STATISTIC_TYPES',
7428 statistic=stat)
7429 assertions.assert_integer(width=width, height=height)
7430 stat_idx = STATISTIC_TYPES.index(stat)
7431 if channel is None:
7432 r = library.MagickStatisticImage(self.wand, stat_idx,
7433 width, height)
7434 else:
7435 channel_ch = self._channel_to_mask(channel)
7436 if MAGICK_VERSION_NUMBER < 0x700:
7437 r = library.MagickStatisticImageChannel(self.wand,
7438 channel_ch,
7439 stat_idx,
7440 width,
7441 height)
7442 else: # pragma: no cover
7443 mask = library.MagickSetImageChannelMask(self.wand,
7444 channel_ch)
7445 r = library.MagickStatisticImage(self.wand, stat_idx,
7446 width, height)
7447 library.MagickSetImageChannelMask(self.wand, mask)
7448 return r
7450 @manipulative
7451 @trap_exception
7452 def stegano(self, watermark, offset=0):
7453 """Hide a digital watermark of an image within the image.
7455 .. code-block:: python
7457 from wand.image import Image
7459 # Embed watermark
7460 with Image(filename='source.png') as img:
7461 with Image(filename='gray_watermark.png') as watermark:
7462 print('watermark size (for recovery)', watermark.size)
7463 img.stegano(watermark)
7464 img.save(filename='public.png')
7466 # Recover watermark
7467 with Image(width=w, height=h, pseudo='stegano:public.png') as img:
7468 img.save(filename='recovered_watermark.png')
7470 :param watermark: Image to hide within image.
7471 :type watermark: :class:`wand.image.Image`
7472 :param offset: Start embedding image after a number of pixels.
7473 :type offset: :class:`numbers.Integral`
7475 .. versionadded:: 0.5.4
7476 """
7477 if not isinstance(watermark, BaseImage):
7478 raise TypeError('Watermark image must be in instance of '
7479 'wand.image.Image, not ' + repr(watermark))
7480 assertions.assert_integer(offset=offset)
7481 new_wand = library.MagickSteganoImage(self.wand, watermark.wand,
7482 offset)
7483 if new_wand:
7484 self.wand = new_wand
7485 self.reset_sequence()
7486 return bool(new_wand)
7488 @trap_exception
7489 def strip(self):
7490 """Strips an image of all profiles and comments.
7492 .. versionadded:: 0.2.0
7493 """
7494 return library.MagickStripImage(self.wand)
7496 @manipulative
7497 @trap_exception
7498 def swirl(self, degree=0.0, method="undefined"):
7499 """Swirls pixels around the center of the image. The larger the degree
7500 the more pixels will be effected.
7502 :param degree: Defines the amount of pixels to be effected. Value
7503 between ``-360.0`` and ``360.0``.
7504 :type degree: :class:`numbers.Real`
7505 :param method: Controls interpolation of the effected pixels. Only
7506 available for ImageMagick-7. See
7507 :const:`PIXEL_INTERPOLATE_METHODS`.
7508 :type method: :class:`basestring`
7510 .. versionadded:: 0.5.7
7511 """
7512 assertions.assert_real(degree=degree)
7513 if MAGICK_VERSION_NUMBER < 0x700:
7514 r = library.MagickSwirlImage(self.wand, degree)
7515 else: # pragma: no cover
7516 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
7517 'wand.image.PIXEL_INTERPOLATE_METHODS',
7518 method=method)
7519 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
7520 r = library.MagickSwirlImage(self.wand, degree, method_idx)
7521 return r
7523 @manipulative
7524 @trap_exception
7525 def texture(self, tile):
7526 """Repeat tile-image across the width & height of the image.
7528 .. code:: python
7530 from wand.image import Image
7532 with Image(width=100, height=100) as canvas:
7533 with Image(filename='tile.png') as tile:
7534 canvas.texture(tile)
7535 canvas.save(filename='output.png')
7537 :param tile: image to repeat across canvas.
7538 :type tile: :class:`Image <wand.image.BaseImage>`
7540 .. versionadded:: 0.5.4
7541 """
7542 if not isinstance(tile, BaseImage):
7543 raise TypeError('Tile image must be an instance of '
7544 'wand.image.Image, not ' + repr(tile))
7545 r = library.MagickTextureImage(self.wand, tile.wand)
7546 if r:
7547 self.wand = r
7548 return bool(r)
7550 @manipulative
7551 @trap_exception
7552 def threshold(self, threshold=0.5, channel=None):
7553 """Changes the value of individual pixels based on the intensity
7554 of each pixel compared to threshold. The result is a high-contrast,
7555 two color image. It manipulates the image in place.
7557 :param threshold: threshold as a factor of quantum. A normalized float
7558 between ``0.0`` and ``1.0``.
7559 :type threshold: :class:`numbers.Real`
7560 :param channel: the channel type. available values can be found
7561 in the :const:`CHANNELS` mapping. If ``None``,
7562 threshold all channels.
7563 :type channel: :class:`basestring`
7565 .. versionadded:: 0.3.10
7567 """
7568 assertions.assert_real(threshold=threshold)
7569 threshold *= self.quantum_range + 1
7570 if channel is None:
7571 r = library.MagickThresholdImage(self.wand, threshold)
7572 else:
7573 ch_const = self._channel_to_mask(channel)
7574 r = library.MagickThresholdImageChannel(
7575 self.wand, ch_const,
7576 threshold
7577 )
7578 return r
7580 @manipulative
7581 @trap_exception
7582 def thumbnail(self, width=None, height=None):
7583 """Changes the size of an image to the given dimensions and removes any
7584 associated profiles. The goal is to produce small low cost thumbnail
7585 images suited for display on the web.
7587 :param width: the width in the scaled image. default is the original
7588 width
7589 :type width: :class:`numbers.Integral`
7590 :param height: the height in the scaled image. default is the original
7591 height
7592 :type height: :class:`numbers.Integral`
7594 .. versionadded:: 0.5.4
7595 """
7596 if width is None:
7597 width = self.width
7598 if height is None:
7599 height = self.height
7600 assertions.assert_unsigned_integer(width=width, height=height)
7601 return library.MagickThumbnailImage(self.wand, width, height)
7603 @manipulative
7604 @trap_exception
7605 def tint(self, color=None, alpha=None):
7606 """Applies a color vector to each pixel in the image.
7608 :param color: Color to calculate midtone.
7609 :type color: :class:`~wand.color.Color`
7610 :param alpha: Determine how to blend.
7611 :type alpha: :class:`~wand.color.Color`
7613 .. versionadded:: 0.5.3
7614 """
7615 if isinstance(color, string_type):
7616 color = Color(color)
7617 if isinstance(alpha, string_type):
7618 alpha = Color(alpha)
7619 assertions.assert_color(color=color, alpha=alpha)
7620 with color:
7621 with alpha:
7622 r = library.MagickTintImage(self.wand,
7623 color.resource,
7624 alpha.resource)
7625 return r
7627 @manipulative
7628 @trap_exception
7629 def transform(self, crop='', resize=''):
7630 """Transforms the image using :c:func:`MagickTransformImage`,
7631 which is a convenience function accepting geometry strings to
7632 perform cropping and resizing. Cropping is performed first,
7633 followed by resizing. Either or both arguments may be omitted
7634 or given an empty string, in which case the corresponding action
7635 will not be performed. Geometry specification strings are
7636 defined as follows:
7638 A geometry string consists of a size followed by an optional offset.
7639 The size is specified by one of the options below,
7640 where **bold** terms are replaced with appropriate integer values:
7642 **scale**\\ ``%``
7643 Height and width both scaled by specified percentage
7645 **scale-x**\\ ``%x``\\ \\ **scale-y**\\ ``%``
7646 Height and width individually scaled by specified percentages.
7647 Only one % symbol is needed.
7649 **width**
7650 Width given, height automagically selected to preserve aspect ratio.
7652 ``x``\\ \\ **height**
7653 Height given, width automagically selected to preserve aspect ratio.
7655 **width**\\ ``x``\\ **height**
7656 Maximum values of width and height given; aspect ratio preserved.
7658 **width**\\ ``x``\\ **height**\\ ``!``
7659 Width and height emphatically given; original aspect ratio ignored.
7661 **width**\\ ``x``\\ **height**\\ ``>``
7662 Shrinks images with dimension(s) larger than the corresponding
7663 width and/or height dimension(s).
7665 **width**\\ ``x``\\ **height**\\ ``<``
7666 Enlarges images with dimensions smaller than the corresponding
7667 width and/or height dimension(s).
7669 **area**\\ ``@``
7670 Resize image to have the specified area in pixels.
7671 Aspect ratio is preserved.
7673 The offset, which only applies to the cropping geometry string,
7674 is given by ``{+-}``\\ **x**\\ ``{+-}``\\ **y**\\ , that is,
7675 one plus or minus sign followed by an **x** offset,
7676 followed by another plus or minus sign, followed by a **y** offset.
7677 Offsets are in pixels from the upper left corner of the image.
7678 Negative offsets will cause the corresponding number of pixels to
7679 be removed from the right or bottom edge of the image, meaning the
7680 cropped size will be the computed size minus the absolute value
7681 of the offset.
7683 For example, if you want to crop your image to 300x300 pixels
7684 and then scale it by 2x for a final size of 600x600 pixels,
7685 you can call::
7687 image.transform('300x300', '200%')
7689 This method is a fairly thin wrapper for the C API, and does not
7690 perform any additional checking of the parameters except insofar as
7691 verifying that they are of the correct type. Thus, like the C
7692 API function, the method is very permissive in terms of what
7693 it accepts for geometry strings; unrecognized strings and
7694 trailing characters will be ignored rather than raising an error.
7696 :param crop: A geometry string defining a subregion of the image
7697 to crop to
7698 :type crop: :class:`basestring`
7699 :param resize: A geometry string defining the final size of the image
7700 :type resize: :class:`basestring`
7702 .. seealso::
7704 `ImageMagick Geometry Specifications`__
7705 Cropping and resizing geometry for the ``transform`` method are
7706 specified according to ImageMagick's geometry string format.
7707 The ImageMagick documentation provides more information about
7708 geometry strings.
7710 __ http://www.imagemagick.org/script/command-line-processing.php#geometry
7712 .. versionadded:: 0.2.2
7713 .. versionchanged:: 0.5.0
7714 Will call :meth:`crop()` followed by :meth:`resize()` in the event
7715 that :c:func:`MagickTransformImage` is not available.
7716 .. deprecated:: 0.6.0
7717 Use :meth:`crop()` and :meth:`resize()` instead.
7718 """ # noqa
7719 # Check that the values given are the correct types. ctypes will do
7720 # this automatically, but we can make the error message more friendly
7721 # here.
7722 assertions.assert_string(crop=crop, resize=resize)
7723 # Also verify that only ASCII characters are included
7724 try:
7725 crop = crop.encode('ascii')
7726 except UnicodeEncodeError:
7727 raise ValueError('crop must only contain ascii-encodable ' +
7728 'characters.')
7729 try:
7730 resize = resize.encode('ascii')
7731 except UnicodeEncodeError:
7732 raise ValueError('resize must only contain ascii-encodable ' +
7733 'characters.')
7734 if not library.MagickTransformImage: # pragma: no cover
7735 # Method removed from ImageMagick-7.
7736 if crop:
7737 x = ctypes.c_ssize_t(0)
7738 y = ctypes.c_ssize_t(0)
7739 width = ctypes.c_size_t(self.width)
7740 height = ctypes.c_size_t(self.height)
7741 libmagick.GetGeometry(crop,
7742 ctypes.byref(x),
7743 ctypes.byref(y),
7744 ctypes.byref(width),
7745 ctypes.byref(height))
7746 self.crop(top=y.value,
7747 left=x.value,
7748 width=width.value,
7749 height=height.value,
7750 reset_coords=False)
7751 if resize:
7752 x = ctypes.c_ssize_t()
7753 y = ctypes.c_ssize_t()
7754 width = ctypes.c_size_t(self.width)
7755 height = ctypes.c_size_t(self.height)
7756 libmagick.ParseMetaGeometry(resize,
7757 ctypes.byref(x),
7758 ctypes.byref(y),
7759 ctypes.byref(width),
7760 ctypes.byref(height))
7761 self.resize(width=width.value,
7762 height=height.value)
7763 # Both `BaseImage.crop` & `BaseImage.resize` will handle
7764 # animation & error handling, so we can stop here.
7765 return True
7766 if self.animation:
7767 new_wand = library.MagickCoalesceImages(self.wand)
7768 length = len(self.sequence)
7769 for i in xrange(length):
7770 library.MagickSetIteratorIndex(new_wand, i)
7771 if i:
7772 library.MagickAddImage(
7773 new_wand,
7774 library.MagickTransformImage(new_wand, crop, resize)
7775 )
7776 else:
7777 new_wand = library.MagickTransformImage(new_wand,
7778 crop,
7779 resize)
7780 self.sequence.instances = []
7781 else:
7782 new_wand = library.MagickTransformImage(self.wand, crop, resize)
7783 if new_wand:
7784 self.wand = new_wand
7785 return bool(new_wand)
7787 @manipulative
7788 @trap_exception
7789 def transform_colorspace(self, colorspace_type):
7790 """Transform image's colorspace.
7792 :param colorspace_type: colorspace_type. available value can be found
7793 in the :const:`COLORSPACE_TYPES`
7794 :type colorspace_type: :class:`basestring`
7796 .. versionadded:: 0.4.2
7798 """
7799 assertions.string_in_list(COLORSPACE_TYPES,
7800 'wand.image.COLORSPACE_TYPES',
7801 colorspace=colorspace_type)
7802 return library.MagickTransformImageColorspace(
7803 self.wand,
7804 COLORSPACE_TYPES.index(colorspace_type)
7805 )
7807 @manipulative
7808 @trap_exception
7809 def transparent_color(self, color, alpha, fuzz=0, invert=False):
7810 """Makes the color ``color`` a transparent color with a tolerance of
7811 fuzz. The ``alpha`` parameter specify the transparency level and the
7812 parameter ``fuzz`` specify the tolerance.
7814 :param color: The color that should be made transparent on the image,
7815 color object
7816 :type color: :class:`wand.color.Color`
7817 :param alpha: the level of transparency: 1.0 is fully opaque
7818 and 0.0 is fully transparent.
7819 :type alpha: :class:`numbers.Real`
7820 :param fuzz: By default target must match a particular pixel color
7821 exactly. However, in many cases two colors may differ
7822 by a small amount. The fuzz member of image defines how
7823 much tolerance is acceptable to consider two colors as the
7824 same. For example, set fuzz to 10 and the color red at
7825 intensities of 100 and 102 respectively are now
7826 interpreted as the same color for the color.
7827 :type fuzz: :class:`numbers.Integral`
7828 :param invert: Boolean to tell to paint the inverse selection.
7829 :type invert: :class:`bool`
7831 .. versionadded:: 0.3.0
7833 """
7834 assertions.assert_real(alpha=alpha)
7835 assertions.assert_integer(fuzz=fuzz)
7836 if isinstance(color, string_type):
7837 color = Color(color)
7838 assertions.assert_color(color=color)
7839 with color:
7840 r = library.MagickTransparentPaintImage(self.wand, color.resource,
7841 alpha, fuzz, invert)
7842 return r
7844 @manipulative
7845 def transparentize(self, transparency):
7846 """Makes the image transparent by subtracting some percentage of
7847 the black color channel. The ``transparency`` parameter specifies the
7848 percentage.
7850 :param transparency: the percentage fade that should be performed on
7851 the image, from 0.0 to 1.0
7852 :type transparency: :class:`numbers.Real`
7854 .. versionadded:: 0.2.0
7856 """
7857 if transparency:
7858 t = ctypes.c_double(float(self.quantum_range *
7859 float(transparency)))
7860 if t.value > self.quantum_range or t.value < 0:
7861 raise ValueError('transparency must be a numbers.Real value ' +
7862 'between 0.0 and 1.0')
7863 # Set the wand to image zero, in case there are multiple images
7864 # in it
7865 library.MagickSetIteratorIndex(self.wand, 0)
7866 # Change the pixel representation of the image
7867 # to RGB with an alpha channel
7868 if MAGICK_VERSION_NUMBER < 0x700:
7869 image_type = 'truecolormatte'
7870 else: # pragma: no cover
7871 image_type = 'truecoloralpha'
7872 library.MagickSetImageType(self.wand,
7873 IMAGE_TYPES.index(image_type))
7874 # Perform the black channel subtraction
7875 self.evaluate(operator='subtract',
7876 value=t.value,
7877 channel='opacity')
7878 self.raise_exception()
7880 @manipulative
7881 @trap_exception
7882 def transpose(self):
7883 """Creates a vertical mirror image by reflecting the pixels around
7884 the central x-axis while rotating them 90-degrees.
7886 .. versionadded:: 0.4.1
7887 """
7888 return library.MagickTransposeImage(self.wand)
7890 @manipulative
7891 @trap_exception
7892 def transverse(self):
7893 """Creates a horizontal mirror image by reflecting the pixels around
7894 the central y-axis while rotating them 270-degrees.
7896 .. versionadded:: 0.4.1
7897 """
7898 return library.MagickTransverseImage(self.wand)
7900 @manipulative
7901 @trap_exception
7902 def trim(self, color=None, fuzz=0.0, reset_coords=False):
7903 """Remove solid border from image. Uses top left pixel as a guide
7904 by default, or you can also specify the ``color`` to remove.
7906 :param color: the border color to remove.
7907 if it's omitted top left pixel is used by default
7908 :type color: :class:`~wand.color.Color`
7909 :param fuzz: Defines how much tolerance is acceptable to consider
7910 two colors as the same. Value can be between ``0.0``,
7911 and :attr:`quantum_range`.
7912 :type fuzz: :class:`numbers.Real`
7913 :param reset_coords: Reset coordinates after triming image. Default
7914 ``False``.
7915 :type reset_coords: :class:`bool`
7918 .. versionadded:: 0.2.1
7920 .. versionchanged:: 0.3.0
7921 Optional ``color`` and ``fuzz`` parameters.
7923 .. versionchanged:: 0.5.2
7924 The ``color`` parameter may except color-compliant strings.
7926 .. versionchanged:: 0.6.0
7927 Optional ``reset_coords`` parameter added.
7928 """
7929 if color is None:
7930 color = self[0, 0]
7931 elif isinstance(color, string_type):
7932 color = Color(color)
7933 assertions.assert_color(color=color)
7934 assertions.assert_real(fuzz=fuzz)
7935 assertions.assert_bool(reset_coords=reset_coords)
7936 with color:
7937 self.border(color, 1, 1, compose="copy")
7938 r = library.MagickTrimImage(self.wand, fuzz)
7939 if reset_coords:
7940 self.reset_coords()
7941 else:
7942 # Re-calculate page coordinates as we added a 1x1 border before
7943 # applying the trim.
7944 adjusted_coords = list(self.page)
7945 # Width & height are unsigned.
7946 adjusted_coords[0] = max(adjusted_coords[0] - 2, 0)
7947 adjusted_coords[1] = max(adjusted_coords[1] - 2, 0)
7948 # X & Y are signed. It's common for page offsets to be negative.
7949 adjusted_coords[2] -= 1
7950 adjusted_coords[3] -= 1
7951 self.page = adjusted_coords
7952 return r
7954 @manipulative
7955 @trap_exception
7956 def unique_colors(self):
7957 """Discards all duplicate pixels, and rebuilds the image
7958 as a single row.
7960 .. versionadded:: 0.5.0
7961 """
7962 return library.MagickUniqueImageColors(self.wand)
7964 @manipulative
7965 @trap_exception
7966 def unsharp_mask(self, radius=0.0, sigma=1.0, amount=1.0, threshold=0.0,
7967 channel=None):
7968 """Sharpens the image using unsharp mask filter. We convolve the image
7969 with a Gaussian operator of the given ``radius`` and standard deviation
7970 (``sigma``). For reasonable results, ``radius`` should be larger than
7971 ``sigma``. Use a radius of 0 and :meth:`unsharp_mask()` selects
7972 a suitable radius for you.
7974 :param radius: the radius of the Gaussian, in pixels,
7975 not counting the center pixel
7976 :type radius: :class:`numbers.Real`
7977 :param sigma: the standard deviation of the Gaussian, in pixels
7978 :type sigma: :class:`numbers.Real`
7979 :param amount: the percentage of the difference between the original
7980 and the blur image that is added back into the original
7981 :type amount: :class:`numbers.Real`
7982 :param threshold: the threshold in pixels needed to apply
7983 the difference amount.
7984 :type threshold: :class:`numbers.Real`
7985 :param channel: Optional color channel to target. See
7986 :const:`CHANNELS`
7987 :type channel: :class:`basestring`
7989 .. versionadded:: 0.3.4
7991 .. versionchanged:: 0.5.5
7992 Added optional ``channel`` argument.
7994 .. versionchanged:: 0.5.7
7995 Added default values to match CLI behavior.
7996 """
7997 assertions.assert_real(radius=radius, sigma=sigma,
7998 amount=amount, threshold=threshold)
7999 if channel is None:
8000 r = library.MagickUnsharpMaskImage(self.wand, radius, sigma,
8001 amount, threshold)
8002 else:
8003 channel_ch = self._channel_to_mask(channel)
8004 if MAGICK_VERSION_NUMBER < 0x700:
8005 r = library.MagickUnsharpMaskImageChannel(
8006 self.wand, channel_ch, radius, sigma, amount, threshold
8007 )
8008 else: # pragma: no cover
8009 mask = library.MagickSetImageChannelMask(self.wand, channel_ch)
8010 r = library.MagickUnsharpMaskImage(self.wand, radius, sigma,
8011 amount, threshold)
8012 library.MagickSetImageChannelMask(self.wand, mask)
8013 return r
8015 @manipulative
8016 @trap_exception
8017 def vignette(self, radius=0.0, sigma=0.0, x=0, y=0):
8018 """Creates a soft vignette style effect on the image.
8020 :param radius: the radius of the Gaussian blur effect.
8021 :type radius: :class:`numbers.Real`
8022 :param sigma: the standard deviation of the Gaussian effect.
8023 :type sigma: :class:`numbers.Real`
8024 :param x: Number of pixels to offset inward from the top & bottom of
8025 the image before drawing effect.
8026 :type x: :class:`numbers.Integral`
8027 :param y: Number of pixels to offset inward from the left & right of
8028 the image before drawing effect.
8029 :type y: :class:`numbers.Integral`
8031 .. versionadded:: 0.5.2
8032 """
8033 assertions.assert_real(radius=radius, sigma=sigma)
8034 return library.MagickVignetteImage(self.wand, radius, sigma, x, y)
8036 @manipulative
8037 def watermark(self, image, transparency=0.0, left=0, top=0):
8038 """Transparentized the supplied ``image`` and places it over the
8039 current image, with the top left corner of ``image`` at coordinates
8040 ``left``, ``top`` of the current image. The dimensions of the
8041 current image are not changed.
8043 :param image: the image placed over the current image
8044 :type image: :class:`wand.image.Image`
8045 :param transparency: the percentage fade that should be performed on
8046 the image, from 0.0 to 1.0
8047 :type transparency: :class:`numbers.Real`
8048 :param left: the x-coordinate where `image` will be placed
8049 :type left: :class:`numbers.Integral`
8050 :param top: the y-coordinate where `image` will be placed
8051 :type top: :class:`numbers.Integral`
8053 .. versionadded:: 0.2.0
8055 """
8056 with image.clone() as watermark_image:
8057 watermark_image.transparentize(transparency)
8058 watermark_image.clamp()
8059 self.composite(watermark_image, left=left, top=top)
8060 self.raise_exception()
8062 @manipulative
8063 @trap_exception
8064 def wave(self, amplitude=0.0, wave_length=0.0, method='undefined'):
8065 """Creates a ripple effect within the image.
8067 :param amplitude: height of wave form.
8068 :type amplitude: :class:`numbers.Real`
8069 :param wave_length: width of wave form.
8070 :type wave_length: :class:`numbers.Real`
8071 :param method: pixel interpolation method. Only available with
8072 ImageMagick-7. See :const:`PIXEL_INTERPOLATE_METHODS`
8073 :type method: :class:`basestring`
8075 .. versionadded:: 0.5.2
8076 """
8077 assertions.assert_real(amplitude=amplitude, wave_length=wave_length)
8078 assertions.string_in_list(PIXEL_INTERPOLATE_METHODS,
8079 'wand.image.PIXEL_INTERPOLATE_METHODS',
8080 method=method)
8081 if MAGICK_VERSION_NUMBER < 0x700:
8082 r = library.MagickWaveImage(self.wand, amplitude, wave_length)
8083 else: # pragma: no cover
8084 method_idx = PIXEL_INTERPOLATE_METHODS.index(method)
8085 r = library.MagickWaveImage(self.wand, amplitude, wave_length,
8086 method_idx)
8087 return r
8089 @manipulative
8090 @trap_exception
8091 def wavelet_denoise(self, threshold=0.0, softness=0.0):
8092 """Removes noise by applying a `wavelet transform`_.
8094 .. _`wavelet transform`:
8095 https://en.wikipedia.org/wiki/Wavelet_transform
8097 .. warning::
8099 This class method is only available with ImageMagick 7.0.8-41, or
8100 greater.
8102 :param threshold: Smoothing limit.
8103 :type threshold: :class:`numbers.Real`
8104 :param softness: Attenuate of the smoothing threshold.
8105 :type softness: :class:`numbers.Real`
8106 :raises WandLibraryVersionError: If system's version of ImageMagick
8107 does not support this method.
8109 .. versionadded:: 0.5.5
8110 """
8111 if library.MagickWaveletDenoiseImage is None:
8112 msg = 'Method requires ImageMagick version 7.0.8-41 or greater.'
8113 raise WandLibraryVersionError(msg)
8114 assertions.assert_real(threshold=threshold, softness=softness)
8115 if 0.0 < threshold <= 1.0:
8116 threshold *= self.quantum_range
8117 if 0.0 < softness <= 1.0:
8118 softness *= self.quantum_range
8119 return library.MagickWaveletDenoiseImage(self.wand, threshold,
8120 softness)
8122 @manipulative
8123 @trap_exception
8124 def white_threshold(self, threshold):
8125 """Forces all pixels above a given color as white. Leaves pixels
8126 below threshold unaltered.
8128 :param threshold: Color to be referenced as a threshold.
8129 :type threshold: :class:`Color`
8131 .. versionadded:: 0.5.2
8132 """
8133 if isinstance(threshold, string_type):
8134 threshold = Color(threshold)
8135 assertions.assert_color(threshold=threshold)
8136 with threshold:
8137 r = library.MagickWhiteThresholdImage(self.wand,
8138 threshold.resource)
8139 return r
8141 @trap_exception
8142 def write_mask(self, clip_mask=None):
8143 """Sets the write mask which prevents pixel-value updates to the image.
8144 Call this method with a ``None`` argument to clear any previously set
8145 masks.
8147 .. warning::
8148 This method is only available with ImageMagick-7.
8150 :param clip_mask: Image to reference as blend mask.
8151 :type clip_mask: :class:`BaseImage`
8153 .. versionadded:: 0.5.7
8154 """
8155 r = False
8156 WritePixelMask = 0x000002
8157 if library.MagickSetImageMask is None:
8158 raise WandLibraryVersionError('Method requires ImageMagick-7.')
8159 else: # pragma: no cover
8160 if clip_mask is None:
8161 r = library.MagickSetImageMask(self.wand, WritePixelMask, None)
8162 elif isinstance(clip_mask, BaseImage):
8163 r = library.MagickSetImageMask(self.wand, WritePixelMask,
8164 clip_mask.wand)
8165 return r
8168class Image(BaseImage):
8169 """An image object.
8171 :param image: makes an exact copy of the ``image``
8172 :type image: :class:`Image`
8173 :param blob: opens an image of the ``blob`` byte array
8174 :type blob: :class:`bytes`
8175 :param file: opens an image of the ``file`` object
8176 :type file: file object
8177 :param filename: opens an image of the ``filename`` string. Additional
8178 :ref:`read_mods` are supported.
8179 :type filename: :class:`basestring`
8180 :param format: forces filename to buffer. ``format`` to help
8181 ImageMagick detect the file format. Used only in
8182 ``blob`` or ``file`` cases
8183 :type format: :class:`basestring`
8184 :param width: the width of new blank image or an image loaded from raw
8185 data.
8186 :type width: :class:`numbers.Integral`
8187 :param height: the height of new blank image or an image loaded from
8188 raw data.
8189 :type height: :class:`numbers.Integral`
8190 :param depth: the depth used when loading raw data.
8191 :type depth: :class:`numbers.Integral`
8192 :param background: an optional background color.
8193 default is transparent
8194 :type background: :class:`wand.color.Color`
8195 :param resolution: set a resolution value (dpi),
8196 useful for vectorial formats (like pdf)
8197 :type resolution: :class:`collections.abc.Sequence`,
8198 :Class:`numbers.Integral`
8199 :param colorspace: sets the stack's default colorspace value before
8200 reading any images.
8201 See :const:`COLORSPACE_TYPES`.
8202 :type colorspace: :class:`basestring`,
8203 :param units: paired with ``resolution`` for defining an image's pixel
8204 density. See :const:`UNIT_TYPES`.
8205 :type units: :class:`basestring`
8207 .. versionadded:: 0.1.5
8208 The ``file`` parameter.
8210 .. versionadded:: 0.1.1
8211 The ``blob`` parameter.
8213 .. versionadded:: 0.2.1
8214 The ``format`` parameter.
8216 .. versionadded:: 0.2.2
8217 The ``width``, ``height``, ``background`` parameters.
8219 .. versionadded:: 0.3.0
8220 The ``resolution`` parameter.
8222 .. versionadded:: 0.4.2
8223 The ``depth`` parameter.
8225 .. versionchanged:: 0.4.2
8226 The ``depth``, ``width`` and ``height`` parameters can be used
8227 with the ``filename``, ``file`` and ``blob`` parameters to load
8228 raw pixel data.
8230 .. versionadded:: 0.5.0
8231 The ``pseudo`` parameter.
8233 .. versionchanged:: 0.5.4
8234 Read constructor no longer sets "transparent" background by default.
8235 Use the ``background`` paramater to specify canvas color when reading
8236 in image.
8238 .. versionchanged:: 0.5.7
8239 Added the ``colorspace`` & ``units`` parameter.
8241 .. describe:: [left:right, top:bottom]
8243 Crops the image by its ``left``, ``right``, ``top`` and ``bottom``,
8244 and then returns the cropped one. ::
8246 with img[100:200, 150:300] as cropped:
8247 # manipulated the cropped image
8248 pass
8250 Like other subscriptable objects, default is 0 or its width/height::
8252 img[:, :] #--> just clone
8253 img[:100, 200:] #--> equivalent to img[0:100, 200:img.height]
8255 Negative integers count from the end (width/height)::
8257 img[-70:-50, -20:-10]
8258 #--> equivalent to img[width-70:width-50, height-20:height-10]
8260 :returns: the cropped image
8261 :rtype: :class:`Image`
8263 .. versionadded:: 0.1.2
8265 """
8267 #: (:class:`ArtifactTree`) A dict mapping to image artifacts.
8268 #: Similar to :attr:`metadata`, but used to alter behavior of various
8269 #: internal operations.
8270 #:
8271 #: .. versionadded:: 0.5.0
8272 artifacts = None
8274 #: (:class:`ChannelImageDict`) The mapping of separated channels
8275 #: from the image. ::
8276 #:
8277 #: with image.channel_images['red'] as red_image:
8278 #: display(red_image)
8279 channel_images = None
8281 #: (:class:`ChannelDepthDict`) The mapping of channels to their depth.
8282 #: Read only.
8283 #:
8284 #: .. versionadded:: 0.3.0
8285 channel_depths = None
8287 #: (:class:`Metadata`) The metadata mapping of the image. Read only.
8288 #:
8289 #: .. versionadded:: 0.3.0
8290 metadata = None
8292 #: (:class:`ProfileDict`) The mapping of image profiles.
8293 #:
8294 #: .. versionadded:: 0.5.1
8295 profiles = None
8297 def __init__(self, image=None, blob=None, file=None, filename=None,
8298 format=None, width=None, height=None, depth=None,
8299 background=None, resolution=None, pseudo=None,
8300 colorspace=None, units=None):
8301 new_args = width, height, background, depth
8302 open_args = blob, file, filename
8303 if any(a is not None for a in new_args) and image is not None:
8304 raise TypeError("blank image parameters can't be used with image "
8305 'parameter')
8306 if sum(a is not None for a in open_args + (image,)) > 1:
8307 raise TypeError(', '.join(open_args) +
8308 ' and image parameters are exclusive each other; '
8309 'use only one at once')
8310 if not (format is None):
8311 if not isinstance(format, string_type):
8312 raise TypeError('format must be a string, not ' + repr(format))
8313 if not any(a is not None for a in open_args):
8314 raise TypeError('format can only be used with the blob, file '
8315 'or filename parameter')
8316 if depth not in [None, 8, 16, 32]:
8317 raise ValueError('Depth must be 8, 16 or 32')
8318 with self.allocate():
8319 if image is None:
8320 wand = library.NewMagickWand()
8321 super(Image, self).__init__(wand)
8322 if image is not None:
8323 if not isinstance(image, BaseImage):
8324 raise TypeError('image must be a wand.image.Image '
8325 'instance, not ' + repr(image))
8326 wand = library.CloneMagickWand(image.wand)
8327 super(Image, self).__init__(wand)
8328 elif any(a is not None for a in open_args):
8329 if format:
8330 format = binary(format)
8331 if background:
8332 if isinstance(background, string_type):
8333 background = Color(background)
8334 assertions.assert_color(background=background)
8335 with background:
8336 r = library.MagickSetBackgroundColor(
8337 self.wand,
8338 background.resource
8339 )
8340 if not r:
8341 self.raise_exception()
8342 if colorspace is not None:
8343 assertions.string_in_list(
8344 COLORSPACE_TYPES,
8345 'wand.image.COLORSPACE_TYPES',
8346 colorspace=colorspace
8347 )
8348 colorspace_idx = COLORSPACE_TYPES.index(colorspace)
8349 library.MagickSetColorspace(self.wand,
8350 colorspace_idx)
8351 if width is not None and height is not None:
8352 assertions.assert_counting_number(width=width,
8353 height=height)
8354 library.MagickSetSize(self.wand, width, height)
8355 if depth is not None:
8356 library.MagickSetDepth(self.wand, depth)
8357 if format:
8358 library.MagickSetFormat(self.wand, format)
8359 if not filename:
8360 library.MagickSetFilename(self.wand,
8361 b'buffer.' + format)
8362 if file is not None:
8363 self.read(file=file, resolution=resolution, units=units)
8364 elif blob is not None:
8365 self.read(blob=blob, resolution=resolution, units=units)
8366 elif filename is not None:
8367 self.read(filename=filename, resolution=resolution,
8368 units=units)
8369 # clear the wand format, otherwise any subsequent call to
8370 # MagickGetImageBlob will silently change the image to this
8371 # format again.
8372 library.MagickSetFormat(self.wand, binary(""))
8373 elif width is not None and height is not None:
8374 if pseudo is None:
8375 self.blank(width, height, background)
8376 else:
8377 self.pseudo(width, height, pseudo)
8378 if depth:
8379 r = library.MagickSetImageDepth(self.wand, depth)
8380 if not r:
8381 raise self.raise_exception()
8382 self.metadata = Metadata(self)
8383 self.artifacts = ArtifactTree(self)
8384 from .sequence import Sequence
8385 self.sequence = Sequence(self)
8386 self.profiles = ProfileDict(self)
8387 self.raise_exception()
8389 def __repr__(self):
8390 return super(Image, self).__repr__(
8391 extra_format=' {self.format!r} ({self.width}x{self.height})'
8392 )
8394 def _repr_png_(self):
8395 with self.convert('png') as cloned:
8396 return cloned.make_blob()
8398 @classmethod
8399 def from_array(cls, array, channel_map=None, storage=None):
8400 """Create an image instance from a :mod:`numpy` array, or any other datatype
8401 that implements `__array_interface__`__ protocol.
8403 .. code::
8405 import numpy
8406 from wand.image import Image
8408 matrix = numpy.random.rand(100, 100, 3)
8409 with Image.from_array(matrix) as img:
8410 img.save(filename='noise.png')
8412 Use the optional ``channel_map`` & ``storage`` arguments to specify
8413 the order of color channels & data size. If ``channel_map`` is omitted,
8414 this method will will guess ``"RGB"``, ``"I"``, or ``"CMYK"`` based on
8415 array shape. If ``storage`` is omitted, this method will reference the
8416 array's ``typestr`` value, and raise a :class:`ValueError` if
8417 storage-type can not be mapped.
8419 Float values must be normalized between `0.0` and `1.0`, and signed
8420 integers should be converted to unsigned values between `0` and
8421 max value of type.
8423 Instances of :class:`Image` can also be exported to numpy arrays::
8425 with Image(filename='rose:') as img:
8426 matrix = numpy.array(img)
8428 __ https://docs.scipy.org/doc/numpy/reference/arrays.interface.html
8430 :param array: Numpy array of pixel values.
8431 :type array: :class:`numpy.array`
8432 :param channel_map: Color channel layout.
8433 :type channel_map: :class:`basestring`
8434 :param storage: Datatype per pixel part.
8435 :type storage: :class:`basestring`
8436 :returns: New instance of an image.
8437 :rtype: :class:`~wand.image.Image`
8439 .. versionadded:: 0.5.3
8440 .. versionchanged:: 0.6.0
8441 Input ``array`` now expects the :attr:`shape` property to be defined
8442 as ```( 'height', 'width', 'channels' )```.
8443 """
8444 arr_itr = array.__array_interface__
8445 typestr = arr_itr['typestr'] # Required by interface.
8446 shape = arr_itr['shape'] # Required by interface.
8447 if storage is None:
8448 # Attempt to guess storage
8449 storage_map = dict(u1='char', i1='char',
8450 u2='short', i2='short',
8451 u4='integer', i4='integer',
8452 u8='long', i8='integer',
8453 f4='float', f8='double')
8454 for token in storage_map:
8455 if token in typestr:
8456 storage = storage_map[token]
8457 break
8458 if storage is None:
8459 raise ValueError('Unable to determine storage type.')
8460 if channel_map is None:
8461 # Attempt to guess channel map
8462 if len(shape) == 3:
8463 if shape[2] < 5:
8464 channel_map = 'RGBA'[0:shape[2]]
8465 else:
8466 channel_map = 'CMYKA'[0:shape[2]]
8467 else:
8468 channel_map = 'I'
8469 if hasattr(array, 'ctypes'):
8470 data_ptr = array.ctypes.data_as(ctypes.c_void_p)
8471 elif hasattr(array, 'tobytes'):
8472 data_ptr = array.tobytes()
8473 elif hasattr(array, 'tostring'):
8474 data_ptr = array.tostring()
8475 else:
8476 data_ptr, _ = arr_itr.get('data')
8477 storage_idx = STORAGE_TYPES.index(storage)
8478 height, width = shape[:2]
8479 wand = library.NewMagickWand()
8480 instance = cls(BaseImage(wand))
8481 r = library.MagickConstituteImage(instance.wand,
8482 width,
8483 height,
8484 binary(channel_map),
8485 storage_idx,
8486 data_ptr)
8487 if not r:
8488 instance.raise_exception(cls)
8489 return instance
8491 @classmethod
8492 def ping(cls, file=None, filename=None, blob=None, resolution=None,
8493 format=None):
8494 """Ping image header into Image() object, but without any pixel data.
8495 This is useful for inspecting image meta-data without decoding the
8496 whole image.
8498 :param blob: reads an image from the ``blob`` byte array
8499 :type blob: :class:`bytes`
8500 :param file: reads an image from the ``file`` object
8501 :type file: file object
8502 :param filename: reads an image from the ``filename`` string
8503 :type filename: :class:`basestring`
8504 :param resolution: set a resolution value (DPI),
8505 useful for vector formats (like PDF)
8506 :type resolution: :class:`collections.abc.Sequence`,
8507 :class:`numbers.Integral`
8508 :param format: suggest image file format when reading from a ``blob``,
8509 or ``file`` property.
8510 :type format: :class:`basestring`
8512 .. versionadded:: 0.5.6
8514 """
8515 r = None
8516 instance = cls()
8517 # Resolution must be set after image reading.
8518 if resolution is not None:
8519 if (isinstance(resolution, abc.Sequence) and
8520 len(resolution) == 2):
8521 library.MagickSetResolution(instance.wand, *resolution)
8522 elif isinstance(resolution, numbers.Integral):
8523 library.MagickSetResolution(instance.wand, resolution,
8524 resolution)
8525 else:
8526 raise TypeError('resolution must be a (x, y) pair or an '
8527 'integer of the same x/y')
8528 if format:
8529 library.MagickSetFormat(instance.wand, format)
8530 if not filename:
8531 library.MagickSetFilename(instance.wand,
8532 b'buffer.' + format)
8533 if file is not None:
8534 if (isinstance(file, file_types) and
8535 hasattr(libc, 'fdopen') and hasattr(file, 'mode')):
8536 fd = libc.fdopen(file.fileno(), file.mode)
8537 r = library.MagickPingImageFile(instance.wand, fd)
8538 elif not callable(getattr(file, 'read', None)):
8539 raise TypeError('file must be a readable file object'
8540 ', but the given object does not '
8541 'have read() method')
8542 else:
8543 blob = file.read()
8544 file = None
8545 if blob is not None:
8546 if not isinstance(blob, abc.Iterable):
8547 raise TypeError('blob must be iterable, not ' +
8548 repr(blob))
8549 if not isinstance(blob, binary_type):
8550 blob = b''.join(blob)
8551 r = library.MagickPingImageBlob(instance.wand, blob, len(blob))
8552 elif filename is not None:
8553 filename = encode_filename(filename)
8554 r = library.MagickPingImage(instance.wand, filename)
8555 if not r:
8556 instance.raise_exception()
8557 msg = ('MagickPingImage returns false, but did raise ImageMagick '
8558 'exception. This can occur when a delegate is missing, or '
8559 'returns EXIT_SUCCESS without generating a raster.')
8560 raise WandRuntimeError(msg)
8561 else:
8562 instance.metadata = Metadata(instance)
8563 instance.artifacts = ArtifactTree(instance)
8564 from .sequence import Sequence
8565 instance.sequence = Sequence(instance)
8566 instance.profiles = ProfileDict(instance)
8567 return instance
8569 @classmethod
8570 def stereogram(cls, left, right):
8571 """Create a new stereogram image from two existing images.
8573 :param left: Left-eye image.
8574 :type left: :class:`wand.image.Image`
8575 :param right: Right-eye image.
8576 :type right: :class:`wand.image.Image`
8578 .. versionadded:: 0.5.4
8579 """
8580 if not isinstance(left, BaseImage):
8581 raise TypeError('Left image must be in instance of '
8582 'wand.image.Image, not ' + repr(left))
8583 if not isinstance(right, BaseImage):
8584 raise TypeError('Right image must be in instance of '
8585 'wand.image.Image, not ' + repr(right))
8586 wand = library.MagickStereoImage(left.wand, right.wand)
8587 if not wand: # pragma: no cover
8588 left.raise_exception()
8589 return cls(BaseImage(wand))
8591 @property
8592 def animation(self):
8593 is_gif = self.mimetype in ('image/gif', 'image/x-gif')
8594 frames = library.MagickGetNumberImages(self.wand)
8595 return is_gif and frames > 1
8597 @property
8598 def mimetype(self):
8599 """(:class:`basestring`) The MIME type of the image
8600 e.g. ``'image/jpeg'``, ``'image/png'``.
8602 .. versionadded:: 0.1.7
8604 """
8605 rp = libmagick.MagickToMime(binary(self.format))
8606 if not bool(rp):
8607 self.raise_exception()
8608 mimetype = rp.value
8609 return text(mimetype)
8611 def blank(self, width, height, background=None):
8612 """Creates blank image.
8614 :param width: the width of new blank image.
8615 :type width: :class:`numbers.Integral`
8616 :param height: the height of new blank image.
8617 :type height: :class:`numbers.Integral`
8618 :param background: an optional background color.
8619 default is transparent
8620 :type background: :class:`wand.color.Color`
8621 :returns: blank image
8622 :rtype: :class:`Image`
8624 .. versionadded:: 0.3.0
8626 """
8627 assertions.assert_counting_number(width=width, height=height)
8628 if background is None:
8629 background = Color('transparent')
8630 elif isinstance(background, string_type):
8631 background = Color(background)
8632 assertions.assert_color(background=background)
8633 with background:
8634 r = library.MagickNewImage(self.wand, width, height,
8635 background.resource)
8636 if not r:
8637 self.raise_exception()
8638 return self
8640 def clear(self):
8641 """Clears resources associated with the image, leaving the image blank,
8642 and ready to be used with new image.
8644 .. versionadded:: 0.3.0
8646 """
8647 library.ClearMagickWand(self.wand)
8649 def close(self):
8650 """Closes the image explicitly. If you use the image object in
8651 :keyword:`with` statement, it was called implicitly so don't have to
8652 call it.
8654 .. note::
8656 It has the same functionality of :attr:`destroy()` method.
8658 """
8659 self.destroy()
8661 def compare_layers(self, method):
8662 """Generates new images showing the delta pixels between
8663 layers. Similar pixels are converted to transparent.
8664 Useful for debugging complex animations. ::
8666 with img.compare_layers('compareany') as delta:
8667 delta.save(filename='framediff_%02d.png')
8669 .. note::
8671 May not work as expected if animations are already
8672 optimized.
8674 :param method: Can be ``'compareany'``,
8675 ``'compareclear'``, or ``'compareoverlay'``
8676 :type method: :class:`basestring`
8677 :returns: new image stack.
8678 :rtype: :class:`Image`
8680 .. versionadded:: 0.5.0
8681 """
8682 if not isinstance(method, string_type):
8683 raise TypeError('method must be a string from IMAGE_LAYER_METHOD, '
8684 'not ' + repr(method))
8685 if method not in ('compareany', 'compareclear', 'compareoverlay'):
8686 raise ValueError('method can only be \'compareany\', '
8687 '\'compareclear\', or \'compareoverlay\'')
8688 r = None
8689 m = IMAGE_LAYER_METHOD.index(method)
8690 if MAGICK_VERSION_NUMBER >= 0x700: # pragma: no cover
8691 r = library.MagickCompareImagesLayers(self.wand, m)
8692 elif library.MagickCompareImageLayers:
8693 r = library.MagickCompareImageLayers(self.wand, m)
8694 elif library.MagickCompareImagesLayers: # pragma: no cover
8695 r = library.MagickCompareImagesLayers(self.wand, m)
8696 else:
8697 raise AttributeError('MagickCompareImageLayers method '
8698 'not available on system.')
8699 if not r:
8700 self.raise_exception()
8701 return Image(image=BaseImage(r))
8703 def convert(self, format):
8704 """Converts the image format with the original image maintained.
8705 It returns a converted image instance which is new. ::
8707 with img.convert('png') as converted:
8708 converted.save(filename='converted.png')
8710 :param format: image format to convert to
8711 :type format: :class:`basestring`
8712 :returns: a converted image
8713 :rtype: :class:`Image`
8714 :raises ValueError: when the given ``format`` is unsupported
8716 .. versionadded:: 0.1.6
8718 """
8719 cloned = self.clone()
8720 cloned.format = format
8721 return cloned
8723 def make_blob(self, format=None):
8724 """Makes the binary string of the image.
8726 :param format: the image format to write e.g. ``'png'``, ``'jpeg'``.
8727 it is omittable
8728 :type format: :class:`basestring`
8729 :returns: a blob (bytes) string
8730 :rtype: :class:`bytes`
8731 :raises ValueError: when ``format`` is invalid
8733 .. versionchanged:: 0.1.6
8734 Removed a side effect that changes the image :attr:`format`
8735 silently.
8737 .. versionadded:: 0.1.5
8738 The ``format`` parameter became optional.
8740 .. versionadded:: 0.1.1
8742 """
8743 if format is not None:
8744 with self.convert(format) as converted:
8745 return converted.make_blob()
8746 library.MagickResetIterator(self.wand)
8747 length = ctypes.c_size_t()
8748 blob_p = None
8749 if len(self.sequence) > 1:
8750 blob_p = library.MagickGetImagesBlob(self.wand,
8751 ctypes.byref(length))
8752 else:
8753 blob_p = library.MagickGetImageBlob(self.wand,
8754 ctypes.byref(length))
8755 if blob_p and length.value:
8756 blob = ctypes.string_at(blob_p, length.value)
8757 library.MagickRelinquishMemory(blob_p)
8758 return blob
8759 else: # pragma: no cover
8760 self.raise_exception()
8762 def pseudo(self, width, height, pseudo='xc:'):
8763 """Creates a new image from ImageMagick's internal protocol coders.
8765 :param width: Total columns of the new image.
8766 :type width: :class:`numbers.Integral`
8767 :param height: Total rows of the new image.
8768 :type height: :class:`numbers.Integral`
8769 :param pseudo: The protocol & arguments for the pseudo image.
8770 :type pseudo: :class:`basestring`
8772 .. versionadded:: 0.5.0
8773 """
8774 assertions.assert_counting_number(width=width, height=height)
8775 assertions.assert_string(pseudo=pseudo)
8776 r = library.MagickSetSize(self.wand, width, height)
8777 if not r:
8778 self.raise_exception()
8779 r = library.MagickReadImage(self.wand, encode_filename(pseudo))
8780 if not r:
8781 self.raise_exception()
8783 def read(self, file=None, filename=None, blob=None, resolution=None,
8784 units=None):
8785 """Read new image into Image() object.
8787 :param blob: reads an image from the ``blob`` byte array
8788 :type blob: :class:`bytes`
8789 :param file: reads an image from the ``file`` object
8790 :type file: file object
8791 :param filename: reads an image from the ``filename`` string.
8792 Additional :ref:`read_mods` are supported.
8793 :type filename: :class:`basestring`
8794 :param resolution: set a resolution value (DPI),
8795 useful for vectorial formats (like PDF)
8796 :type resolution: :class:`collections.abc.Sequence`,
8797 :class:`numbers.Integral`
8798 :param units: used with ``resolution``, can either be
8799 ``'pixelperinch'``, or ``'pixelpercentimeter'``.
8800 :type units: :class:`basestring`
8802 .. versionadded:: 0.3.0
8804 .. versionchanged:: 0.5.7
8805 Added ``units`` parameter.
8806 """
8807 r = None
8808 # Resolution must be set after image reading.
8809 if resolution is not None:
8810 if (isinstance(resolution, abc.Sequence) and
8811 len(resolution) == 2):
8812 library.MagickSetResolution(self.wand, *resolution)
8813 elif isinstance(resolution, numbers.Integral):
8814 library.MagickSetResolution(self.wand, resolution, resolution)
8815 else:
8816 raise TypeError('resolution must be a (x, y) pair or an '
8817 'integer of the same x/y')
8818 if file is not None:
8819 if (isinstance(file, file_types) and
8820 hasattr(libc, 'fdopen') and hasattr(file, 'mode')):
8821 fd = libc.fdopen(file.fileno(), file.mode)
8822 r = library.MagickReadImageFile(self.wand, fd)
8823 elif not callable(getattr(file, 'read', None)):
8824 raise TypeError('file must be a readable file object'
8825 ', but the given object does not '
8826 'have read() method')
8827 else:
8828 blob = file.read()
8829 file = None
8830 if blob is not None:
8831 if not isinstance(blob, abc.Iterable):
8832 raise TypeError('blob must be iterable, not ' +
8833 repr(blob))
8834 if not isinstance(blob, binary_type):
8835 blob = b''.join(blob)
8836 r = library.MagickReadImageBlob(self.wand, blob, len(blob))
8837 elif filename is not None:
8838 filename = encode_filename(filename)
8839 r = library.MagickReadImage(self.wand, filename)
8840 if not r:
8841 self.raise_exception()
8842 msg = ('MagickReadImage returns false, but did not raise '
8843 'ImageMagick exception. This can occur when a delegate '
8844 'is missing, or returns EXIT_SUCCESS without generating a '
8845 'raster.')
8846 raise WandRuntimeError(msg)
8847 else:
8848 if units is not None:
8849 self.units = units
8851 def reset_sequence(self):
8852 """Remove any previously allocated :class:`~wand.sequence.SingleImage`
8853 instances in :attr:`sequence` attribute.
8855 .. versionadded:: 0.6.0
8856 """
8857 for instance in self.sequence.instances:
8858 if hasattr(instance, 'destroy'):
8859 instance.destroy()
8860 self.sequence.instances = []
8862 def save(self, file=None, filename=None, adjoin=True):
8863 """Saves the image into the ``file`` or ``filename``. It takes
8864 only one argument at a time.
8866 :param file: a file object to write to
8867 :type file: file object
8868 :param filename: a filename string to write to
8869 :type filename: :class:`basestring`
8870 :param adjoin: write all images to a single multi-image file. Only
8871 available if file format supports frames, layers, & etc.
8872 :type adjoin: :class:`bool`
8874 .. versionadded:: 0.1.1
8876 .. versionchanged:: 0.1.5
8877 The ``file`` parameter was added.
8879 .. versionchanged:: 6.0.0
8880 The ``adjoin`` parameter was added.
8882 """
8883 if file is None and filename is None:
8884 raise TypeError('expected an argument')
8885 elif file is not None and filename is not None:
8886 raise TypeError('expected only one argument; but two passed')
8887 elif file is not None:
8888 if isinstance(file, string_type):
8889 raise TypeError('file must be a writable file object, '
8890 'but {0!r} is a string; did you want '
8891 '.save(filename={0!r})?'.format(file))
8892 elif isinstance(file, file_types) and hasattr(libc, 'fdopen'):
8893 fd = libc.fdopen(file.fileno(), file.mode)
8894 if library.MagickGetNumberImages(self.wand) > 1:
8895 r = library.MagickWriteImagesFile(self.wand, fd)
8896 else:
8897 r = library.MagickWriteImageFile(self.wand, fd)
8898 libc.fflush(fd)
8899 if not r:
8900 self.raise_exception()
8901 else:
8902 if not callable(getattr(file, 'write', None)):
8903 raise TypeError('file must be a writable file object, '
8904 'but it does not have write() method: ' +
8905 repr(file))
8906 file.write(self.make_blob())
8907 else:
8908 if not isinstance(filename, string_type):
8909 if not hasattr(filename, '__fspath__'):
8910 raise TypeError('filename must be a string, not ' +
8911 repr(filename))
8912 filename = encode_filename(filename)
8913 if library.MagickGetNumberImages(self.wand) > 1:
8914 r = library.MagickWriteImages(self.wand, filename, adjoin)
8915 else:
8916 r = library.MagickWriteImage(self.wand, filename)
8917 if not r:
8918 self.raise_exception()
8921class Iterator(Resource, abc.Iterator):
8922 """Row iterator for :class:`Image`. It shouldn't be instantiated
8923 directly; instead, it can be acquired through :class:`Image` instance::
8925 assert isinstance(image, wand.image.Image)
8926 iterator = iter(image)
8928 It doesn't iterate every pixel, but rows. For example::
8930 for row in image:
8931 for col in row:
8932 assert isinstance(col, wand.color.Color)
8933 print(col)
8935 Every row is a :class:`collections.abc.Sequence` which consists of
8936 one or more :class:`wand.color.Color` values.
8938 :param image: the image to get an iterator
8939 :type image: :class:`Image`
8941 .. versionadded:: 0.1.3
8943 """
8945 c_is_resource = library.IsPixelIterator
8946 c_destroy_resource = library.DestroyPixelIterator
8947 c_get_exception = library.PixelGetIteratorException
8948 c_clear_exception = library.PixelClearIteratorException
8950 def __init__(self, image=None, iterator=None):
8951 if image is not None and iterator is not None:
8952 raise TypeError('it takes only one argument at a time')
8953 with self.allocate():
8954 if image is not None:
8955 if not isinstance(image, Image):
8956 raise TypeError('expected a wand.image.Image instance, '
8957 'not ' + repr(image))
8958 self.resource = library.NewPixelIterator(image.wand)
8959 self.height = image.height
8960 else:
8961 if not isinstance(iterator, Iterator):
8962 raise TypeError('expected a wand.image.Iterator instance, '
8963 'not ' + repr(iterator))
8964 self.resource = library.ClonePixelIterator(iterator.resource)
8965 self.height = iterator.height
8966 self.raise_exception()
8967 self.cursor = 0
8969 def __iter__(self):
8970 return self
8972 def seek(self, y):
8973 assertions.assert_unsigned_integer(seek=y)
8974 if y > self.height:
8975 raise ValueError('can not be greater than height')
8976 self.cursor = y
8977 if y == 0:
8978 library.PixelSetFirstIteratorRow(self.resource)
8979 else:
8980 if not library.PixelSetIteratorRow(self.resource, y - 1):
8981 self.raise_exception()
8983 def __next__(self, x=None):
8984 if self.cursor >= self.height:
8985 self.destroy()
8986 raise StopIteration()
8987 self.cursor += 1
8988 width = ctypes.c_size_t()
8989 pixels = library.PixelGetNextIteratorRow(self.resource,
8990 ctypes.byref(width))
8991 if x is None:
8992 r_pixels = [None] * width.value
8993 for x in xrange(width.value):
8994 r_pixels[x] = Color.from_pixelwand(pixels[x])
8995 return r_pixels
8996 return Color.from_pixelwand(pixels[x]) if pixels else None
8998 next = __next__ # Python 2 compatibility
9000 def clone(self):
9001 """Clones the same iterator.
9003 """
9004 return type(self)(iterator=self)
9007class ImageProperty(object):
9008 """The mixin class to maintain a weak reference to the parent
9009 :class:`Image` object.
9011 .. versionadded:: 0.3.0
9013 """
9015 def __init__(self, image):
9016 if not isinstance(image, BaseImage):
9017 raise TypeError('expected a wand.image.BaseImage instance, '
9018 'not ' + repr(image))
9019 self._image = weakref.ref(image)
9021 @property
9022 def image(self):
9023 """(:class:`Image`) The parent image.
9025 It ensures that the parent :class:`Image`, which is held in a weak
9026 reference, still exists. Returns the dereferenced :class:`Image`
9027 if it does exist, or raises a :exc:`ClosedImageError` otherwise.
9029 :exc: `ClosedImageError` when the parent Image has been destroyed
9031 """
9032 # Dereference our weakref and check that the parent Image still exists
9033 image = self._image()
9034 if image is not None:
9035 return image
9036 raise ClosedImageError(
9037 'parent Image of {0!r} has been destroyed'.format(self)
9038 )
9041class OptionDict(ImageProperty, abc.MutableMapping):
9042 """Free-form mutable mapping of global internal settings.
9044 .. versionadded:: 0.3.0
9046 .. versionchanged:: 0.5.0
9047 Remove key check to :const:`OPTIONS`. Image properties are specific to
9048 vendor, and this library should not attempt to manage the 100+ options
9049 in a whitelist.
9050 """
9052 def __iter__(self):
9053 return iter(OPTIONS)
9055 def __len__(self):
9056 return len(OPTIONS)
9058 def __getitem__(self, key):
9059 assertions.assert_string(key=key)
9060 image = self.image
9061 return text(library.MagickGetOption(image.wand, binary(key)))
9063 def __setitem__(self, key, value):
9064 assertions.assert_string(key=key, value=value)
9065 image = self.image
9066 library.MagickSetOption(image.wand, binary(key), binary(value))
9068 def __delitem__(self, key):
9069 self[key] = ''
9072class Metadata(ImageProperty, abc.MutableMapping):
9073 """Class that implements dict-like read-only access to image metadata
9074 like EXIF or IPTC headers. Most WRITE encoders will ignore properties
9075 assigned here.
9077 :param image: an image instance
9078 :type image: :class:`Image`
9080 .. note::
9082 You don't have to use this by yourself.
9083 Use :attr:`Image.metadata` property instead.
9085 .. versionadded:: 0.3.0
9087 """
9089 def __init__(self, image):
9090 if not isinstance(image, Image):
9091 raise TypeError('expected a wand.image.Image instance, '
9092 'not ' + repr(image))
9093 super(Metadata, self).__init__(image)
9095 def __getitem__(self, k):
9096 """
9097 :param k: Metadata header name string.
9098 :type k: :class:`basestring`
9099 :returns: a header value string
9100 :rtype: :class:`str`
9101 """
9102 assertions.assert_string(key=k)
9103 image = self.image
9104 v = library.MagickGetImageProperty(image.wand, binary(k))
9105 if bool(v) is False:
9106 raise KeyError(k)
9107 value = v.value
9108 return text(value)
9110 def __setitem__(self, k, v):
9111 """
9112 :param k: Metadata header name string.
9113 :type k: :class:`basestring`
9114 :param v: Value to assign.
9115 :type v: :class:`basestring`
9117 .. versionadded: 0.5.0
9118 """
9119 assertions.assert_string(key=k, value=v)
9120 image = self.image
9121 r = library.MagickSetImageProperty(image.wand, binary(k), binary(v))
9122 if not r:
9123 image.raise_exception()
9124 return v
9126 def __delitem__(self, k):
9127 """
9128 :param k: Metadata header name string.
9129 :type k: :class:`basestring`
9131 .. versionadded: 0.5.0
9132 """
9133 assertions.assert_string(key=k)
9134 image = self.image
9135 r = library.MagickDeleteImageProperty(image.wand, binary(k))
9136 if not r:
9137 image.raise_exception()
9139 def __iter__(self):
9140 image = self.image
9141 num = ctypes.c_size_t()
9142 props_p = library.MagickGetImageProperties(image.wand, b'', num)
9143 props = [text(props_p[i]) for i in xrange(num.value)]
9144 library.MagickRelinquishMemory(props_p)
9145 return iter(props)
9147 def __len__(self):
9148 image = self.image
9149 num = ctypes.c_size_t()
9150 props_p = library.MagickGetImageProperties(image.wand, b'', num)
9151 library.MagickRelinquishMemory(props_p)
9152 return num.value
9155class ArtifactTree(ImageProperty, abc.MutableMapping):
9156 """Splay tree to map image artifacts. Values defined here
9157 are intended to be used elseware, and will not be written
9158 to the encoded image.
9160 For example::
9162 # Omit timestamp from PNG file headers.
9163 with Image(filename='input.png') as img:
9164 img.artifacts['png:exclude-chunks'] = 'tIME'
9165 img.save(filename='output.png')
9167 :param image: an image instance
9168 :type image: :class:`Image`
9170 .. note::
9172 You don't have to use this by yourself.
9173 Use :attr:`Image.artifacts` property instead.
9175 .. versionadded:: 0.5.0
9176 """
9178 def __init__(self, image):
9179 if not isinstance(image, Image):
9180 raise TypeError('expected a wand.image.Image instance, '
9181 'not ' + repr(image))
9182 super(ArtifactTree, self).__init__(image)
9184 def __getitem__(self, k):
9185 """
9186 :param k: Metadata header name string.
9187 :type k: :class:`basestring`
9188 :returns: a header value string
9189 :rtype: :class:`str`
9191 .. versionadded: 0.5.0
9192 """
9193 assertions.assert_string(key=k)
9194 image = self.image
9195 v = library.MagickGetImageArtifact(image.wand, binary(k))
9196 if bool(v) is False:
9197 try:
9198 v = library.MagickGetImageProperty(image.wand, binary(k))
9199 value = v.value
9200 except KeyError: # pragma: no cover
9201 value = ""
9202 else:
9203 value = v.value
9204 return text(value)
9206 def __setitem__(self, k, v):
9207 """
9208 :param k: Metadata header name string.
9209 :type k: :class:`basestring`
9210 :param v: Value to assign.
9211 :type v: :class:`basestring`
9213 .. versionadded: 0.5.0
9214 """
9215 assertions.assert_string(key=k, value=v)
9216 image = self.image
9217 r = library.MagickSetImageArtifact(image.wand, binary(k), binary(v))
9218 if not r: # pragma: no cover
9219 image.raise_exception()
9220 return v
9222 def __delitem__(self, k):
9223 """
9224 :param k: Metadata header name string.
9225 :type k: :class:`basestring`
9227 .. versionadded: 0.5.0
9228 """
9229 assertions.assert_string(key=k)
9230 image = self.image
9231 r = library.MagickDeleteImageArtifact(image.wand, binary(k))
9232 if not r: # pragma: no cover
9233 image.raise_exception()
9235 def __iter__(self):
9236 image = self.image
9237 num = ctypes.c_size_t()
9238 props_p = library.MagickGetImageArtifacts(image.wand, b'', num)
9239 props = [text(props_p[i]) for i in xrange(num.value)]
9240 library.MagickRelinquishMemory(props_p)
9241 return iter(props)
9243 def __len__(self):
9244 image = self.image
9245 num = ctypes.c_size_t()
9246 props_p = library.MagickGetImageArtifacts(image.wand, b'', num)
9247 library.MagickRelinquishMemory(props_p)
9248 return num.value
9251class ProfileDict(ImageProperty, abc.MutableMapping):
9252 """The mapping table of embedded image profiles.
9254 Use this to get, set, and delete whole profile payloads on an image. Each
9255 payload is a raw binary string.
9257 For example::
9259 with Image(filename='photo.jpg') as img:
9260 # Extract EXIF
9261 with open('exif.bin', 'wb') as payload:
9262 payload.write(img.profiles['exif'])
9263 # Import ICC
9264 with open('color_profile.icc', 'rb') as payload:
9265 img.profiles['icc'] = payload.read()
9266 # Remove XMP
9267 del imp.profiles['xmp']
9269 .. seealso::
9271 `Embedded Image Profiles`__ for a list of supported profiles.
9273 __ https://imagemagick.org/script/formats.php#embedded
9275 .. versionadded:: 0.5.1
9276 """
9277 def __init__(self, image):
9278 if not isinstance(image, Image):
9279 raise TypeError('expected a wand.image.Image instance, '
9280 'not ' + repr(image))
9281 super(ProfileDict, self).__init__(image)
9283 def __delitem__(self, k):
9284 assertions.assert_string(key=k)
9285 num = ctypes.c_size_t(0)
9286 profile_p = library.MagickRemoveImageProfile(self.image.wand,
9287 binary(k), num)
9288 library.MagickRelinquishMemory(profile_p)
9290 def __getitem__(self, k):
9291 assertions.assert_string(key=k)
9292 num = ctypes.c_size_t(0)
9293 profile_p = library.MagickGetImageProfile(self.image.wand,
9294 binary(k), num)
9295 if num.value > 0:
9296 if PY3:
9297 return_profile = bytes(profile_p[0:num.value])
9298 else:
9299 return_profile = str(bytearray(profile_p[0:num.value]))
9300 library.MagickRelinquishMemory(profile_p)
9301 else:
9302 return_profile = None
9303 return return_profile
9305 def __iter__(self):
9306 num = ctypes.c_size_t(0)
9307 profiles_p = library.MagickGetImageProfiles(self.image.wand, b'', num)
9308 profiles = [text(profiles_p[i]) for i in xrange(num.value)]
9309 library.MagickRelinquishMemory(profiles_p)
9310 return iter(profiles)
9312 def __len__(self):
9313 num = ctypes.c_size_t(0)
9314 profiles_p = library.MagickGetImageProfiles(self.image.wand, b'', num)
9315 library.MagickRelinquishMemory(profiles_p)
9316 return num.value
9318 def __setitem__(self, k, v):
9319 assertions.assert_string(key=k)
9320 if not isinstance(v, binary_type):
9321 raise TypeError('value must be a binary string, not ' + repr(v))
9322 r = library.MagickSetImageProfile(self.image.wand,
9323 binary(k), v, len(v))
9324 if not r:
9325 self.image.raise_exception()
9328class ChannelImageDict(ImageProperty, abc.Mapping):
9329 """The mapping table of separated images of the particular channel
9330 from the image.
9332 :param image: an image instance
9333 :type image: :class:`Image`
9335 .. note::
9337 You don't have to use this by yourself.
9338 Use :attr:`Image.channel_images` property instead.
9340 .. versionadded:: 0.3.0
9342 """
9344 def __iter__(self):
9345 return iter(CHANNELS)
9347 def __len__(self):
9348 return len(CHANNELS)
9350 def __getitem__(self, channel):
9351 c = CHANNELS[channel]
9352 img = self.image.clone()
9353 if library.MagickSeparateImageChannel:
9354 succeeded = library.MagickSeparateImageChannel(img.wand, c)
9355 else:
9356 succeeded = library.MagickSeparateImage(img.wand, c)
9357 if not succeeded:
9358 try:
9359 img.raise_exception()
9360 except WandException:
9361 img.close()
9362 raise
9363 return img
9366class ChannelDepthDict(ImageProperty, abc.Mapping):
9367 """The mapping table of channels to their depth.
9369 :param image: an image instance
9370 :type image: :class:`Image`
9372 .. note::
9374 You don't have to use this by yourself.
9375 Use :attr:`Image.channel_depths` property instead.
9377 .. versionadded:: 0.3.0
9379 """
9381 def __iter__(self):
9382 return iter(CHANNELS)
9384 def __len__(self):
9385 return len(CHANNELS)
9387 def __getitem__(self, channel):
9388 c = CHANNELS[channel]
9389 if library.MagickGetImageChannelDepth:
9390 depth = library.MagickGetImageChannelDepth(self.image.wand, c)
9391 else:
9392 mask = 0
9393 if c != 0:
9394 mask = library.MagickSetImageChannelMask(self.image.wand, c)
9395 depth = library.MagickGetImageDepth(self.image.wand)
9396 if mask != 0:
9397 library.MagickSetImageChannelMask(self.image.wand, mask)
9398 return int(depth)
9401class HistogramDict(abc.Mapping):
9402 """Specialized mapping object to represent color histogram.
9403 Keys are colors, and values are the number of pixels.
9405 :param image: the image to get its histogram
9406 :type image: :class:`BaseImage`
9408 .. versionadded:: 0.3.0
9410 """
9412 def __init__(self, image):
9413 self.size = ctypes.c_size_t()
9414 self.pixels = library.MagickGetImageHistogram(
9415 image.wand,
9416 ctypes.byref(self.size)
9417 )
9418 self.counts = None
9420 def __del__(self):
9421 if self.pixels:
9422 self.pixels = library.DestroyPixelWands(self.pixels,
9423 self.size.value)
9425 def __len__(self):
9426 if self.counts is None:
9427 return self.size.value
9428 return len(self.counts)
9430 def __iter__(self):
9431 if self.counts is None:
9432 self._build_counts()
9433 return iter(self.counts)
9435 def __getitem__(self, color):
9436 if self.counts is None:
9437 self._build_counts()
9438 if isinstance(color, string_type):
9439 color = Color(color)
9440 assertions.assert_color(color=color)
9441 return self.counts[color]
9443 def _build_counts(self):
9444 self.counts = {}
9445 for i in xrange(self.size.value):
9446 color_count = library.PixelGetColorCount(self.pixels[i])
9447 color = Color.from_pixelwand(self.pixels[i])
9448 self.counts[color] = color_count
9451class ConnectedComponentObject(object):
9452 """Generic Python wrapper to translate
9453 :c:type:`CCObjectInfo` structure into a class describing objects found
9454 within an image. This class is generated by
9455 :meth:`Image.connected_components()
9456 <wand.image.BaseImage.connected_components>` method.
9458 .. versionadded:: 0.5.5
9459 """
9460 #: (:class:`numbers.Integral`) Serialized object identifier
9461 #: starting at `0`.
9462 _id = None
9464 #: (:class:`numbers.Integral`) Width of objects minimum
9465 #: bounding rectangle.
9466 width = None
9468 #: (:class:`numbers.Integral`) Height of objects minimum
9469 #: bounding rectangle.
9470 height = None
9472 #: (:class:`numbers.Integral`) X offset of objects minimum
9473 #: bounding rectangle.
9474 left = None
9476 #: (:class:`numbers.Integral`) Y offset of objects minimum
9477 #: bounding rectangle.
9478 top = None
9480 #: (:class:`numbers.Real`) X offset of objects centroid.
9481 center_x = None
9483 #: (:class:`numbers.Real`) Y offset of objects centroid.
9484 center_y = None
9486 #: (:class:`numbers.Real`) Quantity of pixels that make-up
9487 #: the objects shape.
9488 area = None
9490 #: (:class:`~wand.color.Color`) The average color of the
9491 #: shape.
9492 mean_color = None
9494 def __init__(self, cc_object=None):
9495 if isinstance(cc_object, CCObjectInfo):
9496 self.clone_from_cc_object_info(cc_object)
9498 @property
9499 def size(self):
9500 """(:class:`tuple` (:attr:`width`, :attr:`height`))
9501 Minimum bounding rectangle."""
9502 return self.width, self.height
9504 @property
9505 def offset(self):
9506 """(:class:`tuple` (:attr:`left`, :attr:`top`))
9507 Position of objects minimum bounding rectangle."""
9508 return self.left, self.top
9510 @property
9511 def centroid(self):
9512 """(:class:`tuple` (:attr:`center_x`, :attr:`center_y`))
9513 Center of object."""
9514 return self.center_x, self.center_y
9516 def clone_from_cc_object_info(self, cc_object):
9517 """Copy data from :class:`~wand.cdefs.structures.CCObjectInfo`."""
9518 self._id = cc_object._id
9519 self.width = cc_object.bounding_box.width
9520 self.height = cc_object.bounding_box.height
9521 self.left = cc_object.bounding_box.x
9522 self.top = cc_object.bounding_box.y
9523 self.center_x = cc_object.centroid.x
9524 self.center_y = cc_object.centroid.y
9525 self.area = cc_object.area
9526 pinfo_size = ctypes.sizeof(PixelInfo)
9527 raw_buffer = ctypes.create_string_buffer(pinfo_size)
9528 ctypes.memmove(raw_buffer,
9529 ctypes.byref(cc_object.color),
9530 pinfo_size)
9531 self.mean_color = Color(raw=raw_buffer)
9533 def __repr__(self):
9534 fmt = ("{name}({_id}: {width}x{height}+{left}+{top} {center_x:.2f},"
9535 "{center_y:.2f} {area:.0f} {mean_color})")
9536 return fmt.format(name=self.__class__.__name__, **self.__dict__)
9539class ClosedImageError(DestroyedResourceError):
9540 """An error that rises when some code tries access to an already closed
9541 image.
9543 """