2014-07-07 21:03:50 +04:00
|
|
|
from helper import unittest, PillowTestCase, lena, py3
|
2014-06-28 02:12:37 +04:00
|
|
|
from helper import djpeg_available, cjpeg_available
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-01-27 23:27:03 +04:00
|
|
|
import random
|
2014-06-10 13:10:47 +04:00
|
|
|
from io import BytesIO
|
2014-01-27 23:27:03 +04:00
|
|
|
|
2012-10-16 00:26:38 +04:00
|
|
|
from PIL import Image
|
|
|
|
from PIL import ImageFile
|
2014-06-27 07:37:49 +04:00
|
|
|
from PIL import JpegImagePlugin
|
2012-10-16 00:26:38 +04:00
|
|
|
|
|
|
|
codecs = dir(Image.core)
|
|
|
|
|
2014-06-20 11:34:32 +04:00
|
|
|
test_file = "Tests/images/lena.jpg"
|
2012-10-16 00:26:38 +04:00
|
|
|
|
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
class TestFileJpeg(PillowTestCase):
|
2014-05-21 15:32:24 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def setUp(self):
|
|
|
|
if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs:
|
|
|
|
self.skipTest("jpeg support not available")
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def roundtrip(self, im, **options):
|
|
|
|
out = BytesIO()
|
|
|
|
im.save(out, "JPEG", **options)
|
|
|
|
bytes = out.tell()
|
|
|
|
out.seek(0)
|
|
|
|
im = Image.open(out)
|
|
|
|
im.bytes = bytes # for testing only
|
|
|
|
return im
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_sanity(self):
|
2014-05-21 15:32:24 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
# internal version number
|
|
|
|
self.assertRegexpMatches(Image.core.jpeglib_version, "\d+\.\d+$")
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
im = Image.open(test_file)
|
|
|
|
im.load()
|
|
|
|
self.assertEqual(im.mode, "RGB")
|
|
|
|
self.assertEqual(im.size, (128, 128))
|
|
|
|
self.assertEqual(im.format, "JPEG")
|
2014-05-21 15:32:24 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_app(self):
|
|
|
|
# Test APP/COM reader (@PIL135)
|
2014-03-28 03:44:58 +04:00
|
|
|
im = Image.open(test_file)
|
2014-06-10 13:10:47 +04:00
|
|
|
self.assertEqual(
|
|
|
|
im.applist[0],
|
|
|
|
("APP0", b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"))
|
|
|
|
self.assertEqual(im.applist[1], ("COM", b"Python Imaging Library"))
|
|
|
|
self.assertEqual(len(im.applist), 2)
|
|
|
|
|
|
|
|
def test_cmyk(self):
|
|
|
|
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
|
|
|
# Michael for getting me to look one more time.
|
|
|
|
f = "Tests/images/pil_sample_cmyk.jpg"
|
|
|
|
im = Image.open(f)
|
|
|
|
# the source image has red pixels in the upper left corner.
|
|
|
|
c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))]
|
|
|
|
self.assertEqual(c, 0.0)
|
|
|
|
self.assertGreater(m, 0.8)
|
|
|
|
self.assertGreater(y, 0.8)
|
|
|
|
self.assertEqual(k, 0.0)
|
|
|
|
# the opposite corner is black
|
2014-08-27 11:57:40 +04:00
|
|
|
c, m, y, k = [x / 255.0 for x in im.getpixel((
|
|
|
|
im.size[0]-1, im.size[1]-1))]
|
2014-06-10 13:10:47 +04:00
|
|
|
self.assertGreater(k, 0.9)
|
|
|
|
# roundtrip, and check again
|
|
|
|
im = self.roundtrip(im)
|
|
|
|
c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))]
|
|
|
|
self.assertEqual(c, 0.0)
|
|
|
|
self.assertGreater(m, 0.8)
|
|
|
|
self.assertGreater(y, 0.8)
|
|
|
|
self.assertEqual(k, 0.0)
|
2014-08-27 11:57:40 +04:00
|
|
|
c, m, y, k = [x / 255.0 for x in im.getpixel((
|
|
|
|
im.size[0]-1, im.size[1]-1))]
|
2014-06-10 13:10:47 +04:00
|
|
|
self.assertGreater(k, 0.9)
|
|
|
|
|
|
|
|
def test_dpi(self):
|
|
|
|
def test(xdpi, ydpi=None):
|
|
|
|
im = Image.open(test_file)
|
|
|
|
im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))
|
|
|
|
return im.info.get("dpi")
|
|
|
|
self.assertEqual(test(72), (72, 72))
|
|
|
|
self.assertEqual(test(300), (300, 300))
|
|
|
|
self.assertEqual(test(100, 200), (100, 200))
|
|
|
|
self.assertEqual(test(0), None) # square pixels
|
|
|
|
|
|
|
|
def test_icc(self):
|
|
|
|
# Test ICC support
|
|
|
|
im1 = Image.open("Tests/images/rgb.jpg")
|
|
|
|
icc_profile = im1.info["icc_profile"]
|
|
|
|
self.assertEqual(len(icc_profile), 3144)
|
|
|
|
# Roundtrip via physical file.
|
|
|
|
f = self.tempfile("temp.jpg")
|
|
|
|
im1.save(f, icc_profile=icc_profile)
|
|
|
|
im2 = Image.open(f)
|
|
|
|
self.assertEqual(im2.info.get("icc_profile"), icc_profile)
|
|
|
|
# Roundtrip via memory buffer.
|
|
|
|
im1 = self.roundtrip(lena())
|
|
|
|
im2 = self.roundtrip(lena(), icc_profile=icc_profile)
|
|
|
|
self.assert_image_equal(im1, im2)
|
|
|
|
self.assertFalse(im1.info.get("icc_profile"))
|
|
|
|
self.assertTrue(im2.info.get("icc_profile"))
|
|
|
|
|
|
|
|
def test_icc_big(self):
|
|
|
|
# Make sure that the "extra" support handles large blocks
|
|
|
|
def test(n):
|
|
|
|
# The ICC APP marker can store 65519 bytes per marker, so
|
|
|
|
# using a 4-byte test code should allow us to detect out of
|
|
|
|
# order issues.
|
|
|
|
icc_profile = (b"Test"*int(n/4+1))[:n]
|
|
|
|
assert len(icc_profile) == n # sanity
|
|
|
|
im1 = self.roundtrip(lena(), icc_profile=icc_profile)
|
|
|
|
self.assertEqual(im1.info.get("icc_profile"), icc_profile or None)
|
|
|
|
test(0)
|
|
|
|
test(1)
|
|
|
|
test(3)
|
|
|
|
test(4)
|
|
|
|
test(5)
|
|
|
|
test(65533-14) # full JPEG marker block
|
|
|
|
test(65533-14+1) # full block plus one byte
|
|
|
|
test(ImageFile.MAXBLOCK) # full buffer block
|
|
|
|
test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte
|
|
|
|
test(ImageFile.MAXBLOCK*4+3) # large block
|
|
|
|
|
|
|
|
def test_optimize(self):
|
|
|
|
im1 = self.roundtrip(lena())
|
|
|
|
im2 = self.roundtrip(lena(), optimize=1)
|
|
|
|
self.assert_image_equal(im1, im2)
|
|
|
|
self.assertGreaterEqual(im1.bytes, im2.bytes)
|
|
|
|
|
|
|
|
def test_optimize_large_buffer(self):
|
|
|
|
# https://github.com/python-pillow/Pillow/issues/148
|
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
# this requires ~ 1.5x Image.MAXBLOCK
|
|
|
|
im = Image.new("RGB", (4096, 4096), 0xff3333)
|
|
|
|
im.save(f, format="JPEG", optimize=True)
|
|
|
|
|
|
|
|
def test_progressive(self):
|
|
|
|
im1 = self.roundtrip(lena())
|
|
|
|
im2 = self.roundtrip(lena(), progressive=True)
|
|
|
|
self.assert_image_equal(im1, im2)
|
|
|
|
self.assertGreaterEqual(im1.bytes, im2.bytes)
|
|
|
|
|
|
|
|
def test_progressive_large_buffer(self):
|
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
# this requires ~ 1.5x Image.MAXBLOCK
|
|
|
|
im = Image.new("RGB", (4096, 4096), 0xff3333)
|
|
|
|
im.save(f, format="JPEG", progressive=True)
|
|
|
|
|
|
|
|
def test_progressive_large_buffer_highest_quality(self):
|
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
if py3:
|
|
|
|
a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3))
|
|
|
|
else:
|
2014-08-27 11:57:40 +04:00
|
|
|
a = b''.join(chr(random.randint(0, 255)) for _ in range(
|
|
|
|
256 * 256 * 3))
|
2014-06-10 13:10:47 +04:00
|
|
|
im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1)
|
|
|
|
# this requires more bytes than pixels in the image
|
|
|
|
im.save(f, format="JPEG", progressive=True, quality=100)
|
|
|
|
|
|
|
|
def test_large_exif(self):
|
|
|
|
# https://github.com/python-pillow/Pillow/issues/148
|
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
im = lena()
|
|
|
|
im.save(f, 'JPEG', quality=90, exif=b"1"*65532)
|
|
|
|
|
|
|
|
def test_progressive_compat(self):
|
|
|
|
im1 = self.roundtrip(lena())
|
|
|
|
im2 = self.roundtrip(lena(), progressive=1)
|
|
|
|
im3 = self.roundtrip(lena(), progression=1) # compatibility
|
|
|
|
self.assert_image_equal(im1, im2)
|
|
|
|
self.assert_image_equal(im1, im3)
|
|
|
|
self.assertFalse(im1.info.get("progressive"))
|
|
|
|
self.assertFalse(im1.info.get("progression"))
|
|
|
|
self.assertTrue(im2.info.get("progressive"))
|
|
|
|
self.assertTrue(im2.info.get("progression"))
|
|
|
|
self.assertTrue(im3.info.get("progressive"))
|
|
|
|
self.assertTrue(im3.info.get("progression"))
|
|
|
|
|
|
|
|
def test_quality(self):
|
|
|
|
im1 = self.roundtrip(lena())
|
|
|
|
im2 = self.roundtrip(lena(), quality=50)
|
|
|
|
self.assert_image(im1, im2.mode, im2.size)
|
|
|
|
self.assertGreaterEqual(im1.bytes, im2.bytes)
|
|
|
|
|
|
|
|
def test_smooth(self):
|
|
|
|
im1 = self.roundtrip(lena())
|
|
|
|
im2 = self.roundtrip(lena(), smooth=100)
|
|
|
|
self.assert_image(im1, im2.mode, im2.size)
|
|
|
|
|
|
|
|
def test_subsampling(self):
|
|
|
|
def getsampling(im):
|
|
|
|
layer = im.layer
|
|
|
|
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
|
|
|
|
# experimental API
|
|
|
|
im = self.roundtrip(lena(), subsampling=-1) # default
|
|
|
|
self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
|
|
|
|
im = self.roundtrip(lena(), subsampling=0) # 4:4:4
|
|
|
|
self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1))
|
|
|
|
im = self.roundtrip(lena(), subsampling=1) # 4:2:2
|
|
|
|
self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1))
|
|
|
|
im = self.roundtrip(lena(), subsampling=2) # 4:1:1
|
|
|
|
self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
|
|
|
|
im = self.roundtrip(lena(), subsampling=3) # default (undefined)
|
|
|
|
self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
|
|
|
|
|
|
|
|
im = self.roundtrip(lena(), subsampling="4:4:4")
|
|
|
|
self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1))
|
|
|
|
im = self.roundtrip(lena(), subsampling="4:2:2")
|
|
|
|
self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1))
|
|
|
|
im = self.roundtrip(lena(), subsampling="4:1:1")
|
|
|
|
self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1))
|
|
|
|
|
|
|
|
self.assertRaises(
|
|
|
|
TypeError, lambda: self.roundtrip(lena(), subsampling="1:1:1"))
|
|
|
|
|
|
|
|
def test_exif(self):
|
|
|
|
im = Image.open("Tests/images/pil_sample_rgb.jpg")
|
|
|
|
info = im._getexif()
|
|
|
|
self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh')
|
|
|
|
|
2014-07-16 19:36:56 +04:00
|
|
|
def test_mp(self):
|
|
|
|
im = Image.open("Tests/images/pil_sample_rgb.jpg")
|
|
|
|
self.assertIsNone(im._getmp())
|
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_quality_keep(self):
|
2014-09-02 22:49:24 +04:00
|
|
|
# RGB
|
2014-06-20 11:34:32 +04:00
|
|
|
im = Image.open("Tests/images/lena.jpg")
|
2014-06-10 13:10:47 +04:00
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
im.save(f, quality='keep')
|
2014-09-02 22:49:24 +04:00
|
|
|
# Grayscale
|
|
|
|
im = Image.open("Tests/images/lena_gray.jpg")
|
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
im.save(f, quality='keep')
|
|
|
|
# CMYK
|
|
|
|
im = Image.open("Tests/images/pil_sample_cmyk.jpg")
|
|
|
|
f = self.tempfile('temp.jpg')
|
|
|
|
im.save(f, quality='keep')
|
2014-06-10 13:10:47 +04:00
|
|
|
|
|
|
|
def test_junk_jpeg_header(self):
|
|
|
|
# https://github.com/python-pillow/Pillow/issues/630
|
|
|
|
filename = "Tests/images/junk_jpeg_header.jpg"
|
|
|
|
Image.open(filename)
|
|
|
|
|
2014-06-20 11:40:18 +04:00
|
|
|
def test_qtables(self):
|
2014-06-20 11:34:32 +04:00
|
|
|
im = Image.open("Tests/images/lena.jpg")
|
|
|
|
qtables = im.quantization
|
|
|
|
reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)
|
|
|
|
self.assertEqual(im.quantization, reloaded.quantization)
|
2014-08-28 18:18:54 +04:00
|
|
|
self.assert_image_similar(im, self.roundtrip(im, qtables='web_low'),
|
|
|
|
30)
|
|
|
|
self.assert_image_similar(im, self.roundtrip(im, qtables='web_high'),
|
|
|
|
30)
|
2014-06-20 12:09:59 +04:00
|
|
|
self.assert_image_similar(im, self.roundtrip(im, qtables='keep'), 30)
|
|
|
|
|
2014-08-28 18:18:54 +04:00
|
|
|
# values from wizard.txt in jpeg9-a src package.
|
2014-06-20 12:09:59 +04:00
|
|
|
standard_l_qtable = [int(s) for s in """
|
|
|
|
16 11 10 16 24 40 51 61
|
|
|
|
12 12 14 19 26 58 60 55
|
|
|
|
14 13 16 24 40 57 69 56
|
|
|
|
14 17 22 29 51 87 80 62
|
|
|
|
18 22 37 56 68 109 103 77
|
|
|
|
24 35 55 64 81 104 113 92
|
|
|
|
49 64 78 87 103 121 120 101
|
|
|
|
72 92 95 98 112 100 103 99
|
|
|
|
""".split(None)]
|
|
|
|
|
2014-08-28 18:18:54 +04:00
|
|
|
standard_chrominance_qtable = [int(s) for s in """
|
2014-06-20 12:09:59 +04:00
|
|
|
17 18 24 47 99 99 99 99
|
|
|
|
18 21 26 66 99 99 99 99
|
|
|
|
24 26 56 99 99 99 99 99
|
|
|
|
47 66 99 99 99 99 99 99
|
|
|
|
99 99 99 99 99 99 99 99
|
|
|
|
99 99 99 99 99 99 99 99
|
|
|
|
99 99 99 99 99 99 99 99
|
|
|
|
99 99 99 99 99 99 99 99
|
|
|
|
""".split(None)]
|
|
|
|
# list of qtable lists
|
2014-08-28 18:18:54 +04:00
|
|
|
self.assert_image_similar(
|
|
|
|
im, self.roundtrip(
|
|
|
|
im, qtables=[standard_l_qtable, standard_chrominance_qtable]),
|
|
|
|
30)
|
|
|
|
|
2014-06-20 12:09:59 +04:00
|
|
|
# tuple of qtable lists
|
2014-08-28 18:18:54 +04:00
|
|
|
self.assert_image_similar(
|
|
|
|
im, self.roundtrip(
|
|
|
|
im, qtables=(standard_l_qtable, standard_chrominance_qtable)),
|
|
|
|
30)
|
|
|
|
|
2014-06-20 12:09:59 +04:00
|
|
|
# dict of qtable lists
|
|
|
|
self.assert_image_similar(im,
|
|
|
|
self.roundtrip(im,
|
2014-08-27 11:57:40 +04:00
|
|
|
qtables={0: standard_l_qtable,
|
|
|
|
1: standard_chrominance_qtable}),
|
2014-06-20 12:09:59 +04:00
|
|
|
30)
|
2014-06-27 07:37:49 +04:00
|
|
|
|
2014-06-28 02:12:37 +04:00
|
|
|
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
2014-06-27 07:37:49 +04:00
|
|
|
def test_load_djpeg(self):
|
|
|
|
img = Image.open(test_file)
|
|
|
|
img.load_djpeg()
|
|
|
|
self.assert_image_similar(img, Image.open(test_file), 0)
|
|
|
|
|
2014-06-28 02:12:37 +04:00
|
|
|
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
|
2014-06-27 07:37:49 +04:00
|
|
|
def test_save_cjpeg(self):
|
|
|
|
img = Image.open(test_file)
|
|
|
|
|
|
|
|
tempfile = self.tempfile("temp.jpg")
|
|
|
|
JpegImagePlugin._save_cjpeg(img, 0, tempfile)
|
|
|
|
# Default save quality is 75%, so a tiny bit of difference is alright
|
|
|
|
self.assert_image_similar(img, Image.open(tempfile), 1)
|
|
|
|
|
2014-08-27 11:46:34 +04:00
|
|
|
def test_no_duplicate_0x1001_tag(self):
|
|
|
|
# Arrange
|
|
|
|
from PIL import ExifTags
|
|
|
|
tag_ids = dict(zip(ExifTags.TAGS.values(), ExifTags.TAGS.keys()))
|
|
|
|
|
|
|
|
# Assert
|
2014-08-27 12:06:34 +04:00
|
|
|
self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001)
|
|
|
|
self.assertEqual(tag_ids['RelatedImageLength'], 0x1002)
|
2014-08-27 11:46:34 +04:00
|
|
|
|
2014-06-27 07:37:49 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|
2014-05-21 15:33:28 +04:00
|
|
|
|
|
|
|
# End of file
|