Skip to content

Commit

Permalink
Merge 5f34dd1 into e232c2f
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowone committed Oct 28, 2015
2 parents e232c2f + 5f34dd1 commit 50d6879
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/changes.rst
Expand Up @@ -10,6 +10,8 @@ Version 0.4.2
To be released

- Fixed :exc:`ImportError` on MSYS2. [:issue:`257` by Eon Jeong]
- Added :meth:`Image.quantize() <wand.image.Image.quantize>` quantize (MagickQuantizeImage)
- Added :meth:`Image.transform_colorspace() <wand.image.Image.transform_colorspace>` quantize (MagickTransformImageColorspace)


.. _changelog-0.4.1:
Expand Down
Binary file added tests/assets/cmyk.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions tests/image_test.py
Expand Up @@ -1717,3 +1717,41 @@ def test_issue_150(fx_asset, tmpdir):
img.format = 'pjpeg'
with open(str(tmpdir.join('out.jpg')), 'wb') as f:
img.save(file=f)


@mark.slow
def test_quantize(fx_asset):
number_colors = 64
with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img:
colors = set([color for row in img for color in row])
assert len(colors) > number_colors

with Image(filename=str(fx_asset.join('mona-lisa.jpg'))) as img:
with raises(TypeError):
img.quantize(str(number_colors), 'undefined', 0, True, True)

with raises(TypeError):
img.quantize(number_colors, 0, 0, True, True)

with raises(TypeError):
img.quantize(number_colors, 'undefined', 'depth', True, True)

with raises(TypeError):
img.quantize(number_colors, 'undefined', 0, 1, True)

with raises(TypeError):
img.quantize(number_colors, 'undefined', 0, True, 1)

img.quantize(number_colors, 'undefined', 0, True, True)
colors = set([color for row in img for color in row])
assert colors
assert len(colors) <= number_colors


def test_transform_colorspace(fx_asset):
with Image(filename=str(fx_asset.join('cmyk.jpg'))) as img:
with raises(TypeError):
img.transform_colorspace('unknown')

img.transform_colorspace('srgb')
assert img.colorspace == 'srgb'
8 changes: 8 additions & 0 deletions wand/api.py
Expand Up @@ -324,6 +324,8 @@ class AffineMatrix(ctypes.Structure):
library.MagickGetImageColorspace.restype = ctypes.c_int

library.MagickSetImageColorspace.argtypes = [ctypes.c_void_p, ctypes.c_int]
library.MagickTransformImageColorspace.argtypes = [ctypes.c_void_p, ctypes.c_int]


library.MagickGetImageCompression.argtypes = [ctypes.c_void_p]
library.MagickGetImageCompression.restype = ctypes.c_int
Expand Down Expand Up @@ -1274,6 +1276,12 @@ class AffineMatrix(ctypes.Structure):
library.MagickTransposeImage.argtypes = [ctypes.c_void_p]
library.MagickTransverseImage.argtypes = [ctypes.c_void_p]

library.MagickQuantizeImage.argtypes = [ctypes.c_void_p,
ctypes.c_int,
ctypes.c_int,
ctypes.c_int,
ctypes.c_bool,
ctypes.c_bool]

except AttributeError:
raise ImportError('MagickWand shared library not found or incompatible\n'
Expand Down
91 changes: 89 additions & 2 deletions wand/image.py
Expand Up @@ -481,6 +481,7 @@
'vertical_tile', 'horizontal_tile_edge',
'vertical_tile_edge', 'checker_tile')


def manipulative(function):
"""Mark the operation manipulating itself instead of returning new one."""
@functools.wraps(function)
Expand Down Expand Up @@ -1215,7 +1216,6 @@ def distort(self, method, arguments, best_fit=False):
argc, argv, bool(best_fit))
self.raise_exception()


@manipulative
def crop(self, left=0, top=0, right=None, bottom=None,
width=None, height=None, reset_coords=True,
Expand Down Expand Up @@ -2246,6 +2246,93 @@ def watermark(self, image, transparency=0.0, left=0, top=0):
self.composite(watermark_image, left=left, top=top)
self.raise_exception()

@manipulative
def quantize(self, number_colors, colorspace_type,
treedepth, dither, measure_error):
"""`quantize` analyzes the colors within a sequence of images and
chooses a fixed number of colors to represent the image. The goal of
the algorithm is to minimize the color difference between the input and
output image while minimizing the processing time.
:param number_colors: the number of colors.
:type number_colors: :class:`numbers.Integral`
:param colorspace_type: colorspace_type. available value can be found
in the :const:`COLORSPACE_TYPES`
:type colorspace_type: :class:`basestring`
:param treedepth: Normally, this integer value is zero or one. A zero or
one tells Quantize to choose a optimal tree depth of
Log4(number_colors). A tree of this depth generally
allows the best representation of the reference image
with the least amount of memory and
the fastest computational speed. In some cases,
such as an image with low color dispersion
(a few number of colors), a value other than
Log4(number_colors) is required. To expand
the color tree completely, use a value of 8.
:type treedepth: :class:`numbers.Integral`
:param dither: A value other than zero distributes the difference
between an original image and the corresponding
color reduced algorithm to neighboring pixels along
a Hilbert curve.
:type dither: :class:`bool`
:param measure_error: A value other than zero measures the difference
between the original and quantized images.
This difference is the total quantization error.
The error is computed by summing over all pixels
in an image the distance squared in RGB space
between each reference pixel value and
its quantized value.
:type measure_error: :class:`bool`
.. versionadded:: 0.4.2
"""
if not isinstance(number_colors, numbers.Integral):
raise TypeError('number_colors must be integral, '
'not ' + repr(number_colors))

if not isinstance(colorspace_type, string_type) \
or colorspace_type not in COLORSPACE_TYPES:
raise TypeError('Colorspace value must be a string from '
'COLORSPACE_TYPES, not ' + repr(colorspace_type))

if not isinstance(treedepth, numbers.Integral):
raise TypeError('treedepth must be integral, '
'not ' + repr(treedepth))

if not isinstance(dither, bool):
raise TypeError('dither must be a bool, not ' +
repr(dither))

if not isinstance(measure_error, bool):
raise TypeError('measure_error must be a bool, not ' +
repr(measure_error))

r = library.MagickQuantizeImage(self.wand, number_colors,
COLORSPACE_TYPES.index(colorspace_type),
treedepth, dither, measure_error)
if not r:
self.raise_exception()

@manipulative
def transform_colorspace(self, colorspace_type):
"""Transform image's colorspace.
:param colorspace_type: colorspace_type. available value can be found
in the :const:`COLORSPACE_TYPES`
:type colorspace_type: :class:`basestring`
.. versionadded:: 0.4.2
"""
if not isinstance(colorspace_type, string_type) \
or colorspace_type not in COLORSPACE_TYPES:
raise TypeError('Colorspace value must be a string from '
'COLORSPACE_TYPES, not ' + repr(colorspace_type))
r = library.MagickTransformImageColorspace(self.wand, COLORSPACE_TYPES.index(colorspace_type))
if not r:
self.raise_exception()

def __repr__(self):
cls = type(self)
if getattr(self, 'c_resource', None) is None:
Expand Down Expand Up @@ -2857,7 +2944,7 @@ def auto_orient(self):
result = library.MagickAutoOrientImage(self.wand)
if not result:
self.raise_exception()
except AttributeError as e:
except AttributeError:
self._auto_orient()

def border(self, color, width, height):
Expand Down

0 comments on commit 50d6879

Please sign in to comment.