diff --git a/.travis.yml b/.travis.yml index 08ea5d0..572e2cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,12 +38,17 @@ install: - conda install --quiet --file conda-requirements.txt - pip install coveralls + # Work around conda's Pillow package lacking ImageCms + - conda uninstall pillow + - pip install pillow + # Summerise environment # --------------------- - conda list - conda info -a # Install and test imagehash + # -------------------------- - python setup.py install script: diff --git a/imagehash.py b/imagehash.py index 3e0cb57..051de8a 100644 --- a/imagehash.py +++ b/imagehash.py @@ -31,10 +31,11 @@ from __future__ import (absolute_import, division, print_function) -from PIL import Image import numpy -#import scipy.fftpack #import pywt +#import scipy.fftpack +from PIL import Image, ImageCms + __version__ = "4.1.0" """ @@ -270,7 +271,7 @@ def dhash_vertical(image, hash_size=8): return ImageHash(diff) -def whash(image, hash_size = 8, image_scale = None, mode = 'haar', remove_max_haar_ll = True): +def whash(image, hash_size=8, image_scale=None, mode='haar', remove_max_haar_ll=True): """ Wavelet Hash computation. @@ -320,7 +321,7 @@ def whash(image, hash_size = 8, image_scale = None, mode = 'haar', remove_max_ha -def colorhash(image, binbits=3): +def colorhash(image, binbits=3, ignore_icc=False): """ Color Hash computation. @@ -332,11 +333,23 @@ def colorhash(image, binbits=3): * the next 6*binbits encode the fraction in 6 bins of saturation, for mildly saturated parts of the remaining image @binbits number of bits to use to encode each pixel fractions + @ignore_icc use raw color values, ignoring embedded ICC profiles """ + if not ignore_icc: + image_profile = image.info.get("icc_profile") + if image_profile: + from io import BytesIO + # standardize color space to sRGB and preserve relative + # color values by using perceptual rendering intent + srgb_profile = ImageCms.createProfile("sRGB") + image_profile = ImageCms.ImageCmsProfile(BytesIO(image_profile)) + ImageCms.profileToProfile(image, image_profile, srgb_profile, + renderingIntent=ImageCms.INTENT_PERCEPTUAL, inPlace=True) + # bin in hsv space: intensity = numpy.asarray(image.convert("L")).flatten() - h, s, v = [numpy.asarray(v).flatten() for v in image.convert("HSV").split()] + h, s, _ = [numpy.asarray(v).flatten() for v in image.convert("HSV").split()] # black bin mask_black = intensity < 256 // 8 frac_black = mask_black.mean() diff --git a/tests/data/bigbuckbunny-with-diff-icc.png b/tests/data/bigbuckbunny-with-diff-icc.png new file mode 100644 index 0000000..c4db4e3 Binary files /dev/null and b/tests/data/bigbuckbunny-with-diff-icc.png differ diff --git a/tests/data/bigbuckbunny.png b/tests/data/bigbuckbunny.png new file mode 100644 index 0000000..414e11d Binary files /dev/null and b/tests/data/bigbuckbunny.png differ diff --git a/tests/test_colorhash.py b/tests/test_colorhash.py index cb3c264..a5fee46 100644 --- a/tests/test_colorhash.py +++ b/tests/test_colorhash.py @@ -11,6 +11,9 @@ def setUp(self): self.image = self.get_data_image() self.func = imagehash.colorhash + def tearDown(self): + self.image.close() + def test_colorhash(self): self.check_hash_algorithm(self.func, self.image) @@ -63,6 +66,33 @@ def check_hash_size(self, func, image, binbits=range(-1,1)): with self.assertRaises(ValueError): func(image, bit) + def test_colorhash_icc_support(self): + image = self.get_data_image('bigbuckbunny.png') + image_with_icc = self.get_data_image('bigbuckbunny-with-diff-icc.png') + with image, image_with_icc: + self.check_icc_support(self.func, image, image_with_icc) + + def check_icc_support(self, func, orig_image, image_with_diff_icc): + orig_data_hash = func(orig_image) + diff_icc_hash = func(image_with_diff_icc) + emsg = ('same image data but with a different ICC profile should ' + 'result in different hashes: {} == {}'.format(orig_data_hash, + diff_icc_hash)) + self.assertNotEqual(orig_data_hash, diff_icc_hash, emsg) + + def test_colorhash_icc_ignored(self): + image = self.get_data_image('bigbuckbunny.png') + image_with_icc = self.get_data_image('bigbuckbunny-with-diff-icc.png') + with image, image_with_icc: + self.check_icc_ignored(self.func, image, image_with_icc) + + def check_icc_ignored(self, func, orig_image, image_with_diff_icc): + orig_data_hash = func(orig_image) + diff_icc_hash = func(image_with_diff_icc, ignore_icc=True) + emsg = ('ignoring the ICC profile in an image with the same image ' + 'data as one without a profile should result in the same hash: ' + '{} != {}'.format(orig_data_hash, diff_icc_hash)) + self.assertEqual(orig_data_hash, diff_icc_hash, emsg) if __name__ == '__main__': unittest.main()