diff --git a/CHANGES.rst b/CHANGES.rst index be16960b4..2b1945012 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,18 @@ Changelog (Pillow) - Python 2.7 support will be removed in Pillow 7.0.0 #3682 [hugovk] +- Add EXIF support for PNG #3674 + [radarhere] + +- Add option to set dither param on quantize #3699 + [glasnt] + +- Add reading of DDS uncompressed RGB data #3673 + [radarhere] + +- Correct length of Tiff BYTE tags #3672 + [radarhere] + - Add DIB saving and loading through Image open #3691 [radarhere] diff --git a/Tests/images/exif.png b/Tests/images/exif.png new file mode 100644 index 000000000..0388b6b8a Binary files /dev/null and b/Tests/images/exif.png differ diff --git a/Tests/images/uncompressed_rgb.dds b/Tests/images/uncompressed_rgb.dds new file mode 100755 index 000000000..cd5189532 Binary files /dev/null and b/Tests/images/uncompressed_rgb.dds differ diff --git a/Tests/images/uncompressed_rgb.png b/Tests/images/uncompressed_rgb.png new file mode 100644 index 000000000..50bca09ee Binary files /dev/null and b/Tests/images/uncompressed_rgb.png differ diff --git a/Tests/images/unimplemented_dxgi_format.dds b/Tests/images/unimplemented_dxgi_format.dds new file mode 100644 index 000000000..5ecb42006 Binary files /dev/null and b/Tests/images/unimplemented_dxgi_format.dds differ diff --git a/Tests/images/unimplemented_pixel_format.dds b/Tests/images/unimplemented_pixel_format.dds new file mode 100755 index 000000000..41a343886 Binary files /dev/null and b/Tests/images/unimplemented_pixel_format.dds differ diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index af2524c4a..605c5f69b 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -7,6 +7,7 @@ TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" +TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds" class TestFileDds(PillowTestCase): @@ -67,6 +68,24 @@ class TestFileDds(PillowTestCase): self.assert_image_equal(target, im) + def test_unimplemented_dxgi_format(self): + self.assertRaises(NotImplementedError, Image.open, + "Tests/images/unimplemented_dxgi_format.dds") + + def test_uncompressed_rgb(self): + """Check uncompressed RGB images can be opened""" + + target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace('.dds', '.png')) + + im = Image.open(TEST_FILE_UNCOMPRESSED_RGB) + im.load() + + self.assertEqual(im.format, "DDS") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (800, 600)) + + self.assert_image_equal(target, im) + def test__validate_true(self): """Check valid prefix""" # Arrange @@ -110,3 +129,7 @@ class TestFileDds(PillowTestCase): im.load() self.assertRaises(IOError, short_file) + + def test_unimplemented_pixel_format(self): + self.assertRaises(NotImplementedError, Image.open, + "Tests/images/unimplemented_pixel_format.dds") diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 56564ebde..e395708c3 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -234,11 +234,11 @@ class TestFileLibTiff(LibTiffTestCase): def test_custom_metadata(self): custom = { - 37000: 4, - 37001: 4.2, - 37002: 'custom tag value', - 37003: u'custom tag value', - 37004: b'custom tag value' + 37000: [4, TiffTags.SHORT], + 37001: [4.2, TiffTags.RATIONAL], + 37002: ['custom tag value', TiffTags.ASCII], + 37003: [u'custom tag value', TiffTags.ASCII], + 37004: [b'custom tag value', TiffTags.BYTE] } libtiff_version = TiffImagePlugin._libtiff_version() @@ -251,17 +251,33 @@ class TestFileLibTiff(LibTiffTestCase): for libtiff in libtiffs: TiffImagePlugin.WRITE_LIBTIFF = libtiff - im = hopper() + def check_tags(tiffinfo): + im = hopper() - out = self.tempfile("temp.tif") - im.save(out, tiffinfo=custom) - TiffImagePlugin.WRITE_LIBTIFF = False + out = self.tempfile("temp.tif") + im.save(out, tiffinfo=tiffinfo) - reloaded = Image.open(out) - for tag, value in custom.items(): - if libtiff and isinstance(value, bytes): - value = value.decode() - self.assertEqual(reloaded.tag_v2[tag], value) + reloaded = Image.open(out) + for tag, value in tiffinfo.items(): + reloaded_value = reloaded.tag_v2[tag] + if isinstance(reloaded_value, TiffImagePlugin.IFDRational): + reloaded_value = float(reloaded_value) + + if libtiff and isinstance(value, bytes): + value = value.decode() + + self.assertEqual(reloaded_value, value) + + # Test with types + ifd = TiffImagePlugin.ImageFileDirectory_v2() + for tag, tagdata in custom.items(): + ifd[tag] = tagdata[0] + ifd.tagtype[tag] = tagdata[1] + check_tags(ifd) + + # Test without types + check_tags({tag: tagdata[0] for tag, tagdata in custom.items()}) + TiffImagePlugin.WRITE_LIBTIFF = False def test_int_dpi(self): # issue #1765 diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 2b80bf357..c2864d223 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -590,6 +590,40 @@ class TestFilePng(PillowTestCase): im = Image.open("Tests/images/hopper_idat_after_image_end.png") self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) + def test_exif(self): + im = Image.open("Tests/images/exif.png") + exif = im._getexif() + self.assertEqual(exif[274], 1) + + def test_exif_save(self): + im = Image.open("Tests/images/exif.png") + + test_file = self.tempfile("temp.png") + im.save(test_file) + + reloaded = Image.open(test_file) + exif = reloaded._getexif() + self.assertEqual(exif[274], 1) + + def test_exif_from_jpg(self): + im = Image.open("Tests/images/pil_sample_rgb.jpg") + + test_file = self.tempfile("temp.png") + im.save(test_file) + + reloaded = Image.open(test_file) + exif = reloaded._getexif() + self.assertEqual(exif[305], "Adobe Photoshop CS Macintosh") + + def test_exif_argument(self): + im = Image.open(TEST_PNG_FILE) + + test_file = self.tempfile("temp.png") + im.save(test_file, exif=b"exifstring") + + reloaded = Image.open(test_file) + self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring") + @unittest.skipUnless(HAVE_WEBP and _webp.HAVE_WEBPANIM, "WebP support not installed with animation") def test_apng(self): diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 2f0b65758..762b4bcab 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -46,3 +46,19 @@ class TestImageQuantize(PillowTestCase): converted = image.quantize() self.assert_image(converted, 'P', converted.size) self.assert_image_similar(converted.convert('RGB'), image, 1) + + def test_quantize_no_dither(self): + image = hopper() + palette = Image.open('Tests/images/caption_6_33_22.png').convert('P') + + converted = image.quantize(dither=0, palette=palette) + self.assert_image(converted, 'P', converted.size) + + def test_quantize_dither_diff(self): + image = hopper() + palette = Image.open('Tests/images/caption_6_33_22.png').convert('P') + + dither = image.quantize(dither=1, palette=palette) + nodither = image.quantize(dither=0, palette=palette) + + self.assertNotEqual(dither.tobytes(), nodither.tobytes()) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 0fff91bb5..663b740d5 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -462,6 +462,10 @@ PNG Pillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``P``, ``RGB``, or ``RGBA`` data. Interlaced files are supported as of v1.1.7. +As of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other +image formats, EXIF data is not guaranteed to have been read until +:py:meth:`~PIL.Image.Image.load` has been called. + The :py:meth:`~PIL.Image.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties, when appropriate: @@ -527,6 +531,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **icc_profile** The ICC Profile to include in the saved file. +**exif** + The exif data to include in the saved file. + + .. versionadded:: 6.0.0 + **bits (experimental)** For ``P`` images, this option controls how many bits to store. If omitted, the PNG writer uses 8 bits (256 colors). @@ -849,8 +858,8 @@ DDS DDS is a popular container texture format used in video games and natively supported by DirectX. -Currently, DXT1, DXT3, and DXT5 pixel formats are supported and only in ``RGBA`` -mode. +Currently, uncompressed RGB data and DXT1, DXT3, and DXT5 pixel formats are +supported, and only in ``RGBA`` mode. .. versionadded:: 3.4.0 DXT3 diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index f16c718a1..58586f3bb 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -107,10 +107,22 @@ DIB File Format Pillow now supports reading and writing the DIB "Device Independent Bitmap" file format. +Image.quantize +^^^^^^^^^^^^^^ + +The `dither` option is now a customisable parameter (was previously hardcoded to `1`). This parameter takes the same values used in `Image.convert` + +PNG EXIF Data +^^^^^^^^^^^^^ + +EXIF data can now be read from and saved to PNG images. However, unlike other image +formats, EXIF data is not guaranteed to have been read until +:py:meth:`~PIL.Image.Image.load` has been called. + Other Changes ============= -TODO -^^^^ +Reading new DDS image format +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -TODO +Pillow can now read uncompressed RGB data from DDS images. diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index edc1b7aa3..3954a1d6e 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -123,43 +123,52 @@ class DdsImageFile(ImageFile.ImageFile): # pixel format pfsize, pfflags = struct.unpack("<2I", header.read(8)) fourcc = header.read(4) - bitcount, rmask, gmask, bmask, amask = struct.unpack("<5I", - header.read(20)) + bitcount, = struct.unpack("