diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 43ffd1b4fe5..db3eaa50ec1 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1029,21 +1029,8 @@ def _decoder(self, rawmode, layer, tile=None): compression = self._compression if compression == "raw": args = (rawmode, 0, 1) - elif compression == "jpeg": - args = rawmode, "" - if JPEGTABLES in self.tag_v2: - # Hack to handle abbreviated JPEG headers - # Definition of JPEGTABLES is that the count - # is the number of bytes in the tables datastream - # so, it should always be 1 in our tag info - self.tile_prefix = self.tag_v2[JPEGTABLES] elif compression == "packbits": args = rawmode - elif compression == "tiff_lzw": - args = rawmode - if PREDICTOR in self.tag_v2: - # Section 14: Differencing Predictor - self.decoderconfig = (self.tag_v2[PREDICTOR],) return args @@ -1244,14 +1231,7 @@ def _setup(self): offsets = self.tag_v2[STRIPOFFSETS] h = self.tag_v2.get(ROWSPERSTRIP, ysize) w = self.size[0] - if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", - "group4", "tiff_jpeg", - "tiff_adobe_deflate", - "tiff_thunderscan", - "tiff_deflate", - "tiff_sgilog", - "tiff_sgilog24", - "tiff_raw_16"]: + if READ_LIBTIFF or self._compression != 'raw': # if DEBUG: # print("Activating g4 compression for whole file") @@ -1285,8 +1265,13 @@ def _setup(self): # we're expecting image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. - if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode: + if rawmode == 'I;16': rawmode = 'I;16N' + if ';16B' in rawmode: + rawmode = rawmode.replace(';16B', ';16N') + if ';16L' in rawmode: + rawmode = rawmode.replace(';16L', ';16N') + # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds diff --git a/Tests/helper.py b/Tests/helper.py index 38eb98a9465..b8b44b6f2ec 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -14,11 +14,22 @@ HAS_UPLOADER = False -try: - import test_image_results - HAS_UPLOADER = True -except ImportError: - pass + +if os.environ.get('SHOW_ERRORS', None): + # local img.show for errors. + HAS_UPLOADER=True + class test_image_results: + @classmethod + def upload(self, a, b): + a.show() + b.show() +else: + try: + import test_image_results + HAS_UPLOADER = True + except ImportError: + pass + def convert_to_comparable(a, b): @@ -99,11 +110,17 @@ def assert_image_equal(self, a, b, msg=None): try: url = test_image_results.upload(a, b) logger.error("Url for test images: %s" % url) - except: + except Exception as msg: pass self.fail(msg or "got different content") + def assert_image_equal_tofile(self, a, filename, msg=None, mode=None): + with Image.open(filename) as img: + if mode: + img = img.convert(mode) + self.assert_image_equal(a, img, msg) + def assert_image_similar(self, a, b, epsilon, msg=None): epsilon = float(epsilon) self.assertEqual( @@ -136,6 +153,12 @@ def assert_image_similar(self, a, b, epsilon, msg=None): pass raise e + def assert_image_similar_tofile(self, a, filename, epsilon, msg=None, mode=None): + with Image.open(filename) as img: + if mode: + img = img.convert(mode) + self.assert_image_similar(a, img, epsilon, msg) + def assert_warning(self, warn_class, func, *args, **kwargs): import warnings diff --git a/Tests/images/copyleft.png b/Tests/images/copyleft.png new file mode 100644 index 00000000000..c0c63b887f3 Binary files /dev/null and b/Tests/images/copyleft.png differ diff --git a/Tests/images/pil136.png b/Tests/images/pil136.png new file mode 100644 index 00000000000..ec752cf261a Binary files /dev/null and b/Tests/images/pil136.png differ diff --git a/Tests/images/pil168.png b/Tests/images/pil168.png new file mode 100644 index 00000000000..1f752ff5606 Binary files /dev/null and b/Tests/images/pil168.png differ diff --git a/Tests/images/tiff_16bit_RGBa_target.png b/Tests/images/tiff_16bit_RGBa_target.png new file mode 100644 index 00000000000..cecc295d4b8 Binary files /dev/null and b/Tests/images/tiff_16bit_RGBa_target.png differ diff --git a/Tests/images/tiff_adobe_deflate.png b/Tests/images/tiff_adobe_deflate.png new file mode 100644 index 00000000000..8a12206a22e Binary files /dev/null and b/Tests/images/tiff_adobe_deflate.png differ diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 302600eb8c9..dc3b1084573 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,5 +1,6 @@ from __future__ import print_function from helper import unittest, PillowTestCase, hopper, py3 +from PIL import features from ctypes import c_float import io @@ -15,9 +16,7 @@ class LibTiffTestCase(PillowTestCase): def setUp(self): - codecs = dir(Image.core) - - if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: + if not features.check('libtiff'): self.skipTest("tiff support not available") def _assert_noerr(self, im): @@ -126,6 +125,8 @@ def test_adobe_deflate_tiff(self): im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) im.load() + self.assert_image_equal_tofile(im, 'Tests/images/tiff_adobe_deflate.png') + def test_write_metadata(self): """ Test metadata writing through libtiff """ for legacy_api in [False, True]: @@ -310,12 +311,7 @@ def test_12bit_rawmode(self): # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, # so we need to unshift so that the integer values are the same. - im2 = Image.open('Tests/images/12in16bit.tif') - - logger.debug("%s", [img.getpixel((0, idx)) - for img in [im, im2] for idx in range(3)]) - - self.assert_image_equal(im, im2) + self.assert_image_equal_tofile(im, 'Tests/images/12in16bit.tif') def test_blur(self): # test case from irc, how to do blur on b/w image @@ -576,6 +572,51 @@ def test_save_tiff_with_jpegtables(self): # Should not raise UnicodeDecodeError or anything else im.save(outfile) + def test_16bit_RGBa_tiff(self): + im = Image.open("Tests/images/tiff_16bit_RGBa.tiff") + + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (100, 40)) + self.assertEqual(im.tile, [('tiff_lzw', (0, 0, 100, 40), 0, ('RGBa;16N', 'tiff_lzw', False))]) + im.load() + + self.assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + + def test_gimp_tiff(self): + # Read TIFF JPEG images from GIMP [@PIL168] + + codecs = dir(Image.core) + if "jpeg_decoder" not in codecs: + self.skipTest("jpeg support not available") + + filename = "Tests/images/pil168.tif" + im = Image.open(filename) + + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (256, 256)) + self.assertEqual( + im.tile, [('jpeg', (0, 0, 256, 256), 0, ('RGB', 'jpeg', False))] + ) + im.load() + + self.assert_image_equal_tofile(im, "Tests/images/pil168.png") + + def test_sampleformat(self): + # https://github.com/python-pillow/Pillow/issues/1466 + im = Image.open("Tests/images/copyleft.tiff") + self.assertEqual(im.mode, 'RGB') + + self.assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode='RGB') + + def test_lzw(self): + im = Image.open("Tests/images/hopper_lzw.tif") + + self.assertEqual(im.mode, 'RGB') + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "TIFF") + im2 = hopper() + self.assert_image_similar(im, im2, 5) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index fb37a5caa5c..f4059f9c903 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase, hopper -from PIL import Image, ImagePalette +from PIL import Image, ImagePalette, features try: from PIL import MicImagePlugin @@ -13,6 +13,7 @@ @unittest.skipUnless(olefile_installed, "olefile package not installed") +@unittest.skipUnless(features.check('libtiff'), "libtiff not installed") class TestFileMic(PillowTestCase): def test_sanity(self): diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index d0aaef6985f..6edc94aed7d 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -51,13 +51,7 @@ def test_mac_tiff(self): self.assertEqual(im.tile, [('raw', (0, 0, 55, 43), 8, ('RGBa', 0, 1))]) im.load() - def test_16bit_RGBa_tiff(self): - im = Image.open("Tests/images/tiff_16bit_RGBa.tiff") - - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (100, 40)) - self.assertEqual(im.tile, [('tiff_lzw', (0, 0, 100, 40), 50, 'RGBa;16B')]) - im.load() + self.assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) def test_wrong_bits_per_sample(self): im = Image.open("Tests/images/tiff_wrong_bits_per_sample.tiff") @@ -67,32 +61,6 @@ def test_wrong_bits_per_sample(self): self.assertEqual(im.tile, [('raw', (0, 0, 52, 53), 160, ('RGBA', 0, 1))]) im.load() - def test_gimp_tiff(self): - # Read TIFF JPEG images from GIMP [@PIL168] - - codecs = dir(Image.core) - if "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - - filename = "Tests/images/pil168.tif" - im = Image.open(filename) - - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (256, 256)) - self.assertEqual( - im.tile, [ - ('jpeg', (0, 0, 256, 64), 8, ('RGB', '')), - ('jpeg', (0, 64, 256, 128), 1215, ('RGB', '')), - ('jpeg', (0, 128, 256, 192), 2550, ('RGB', '')), - ('jpeg', (0, 192, 256, 256), 3890, ('RGB', '')), - ]) - im.load() - - def test_sampleformat(self): - # https://github.com/python-pillow/Pillow/issues/1466 - im = Image.open("Tests/images/copyleft.tiff") - self.assertEqual(im.mode, 'RGB') - def test_set_legacy_api(self): with self.assertRaises(Exception): ImageFileDirectory_v2.legacy_api = None @@ -225,12 +193,7 @@ def test_12bit_rawmode(self): # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, # so we need to unshift so that the integer values are the same. - im2 = Image.open('Tests/images/12in16bit.tif') - - logger.debug("%s", [img.getpixel((0, idx)) - for img in [im, im2] for idx in range(3)]) - - self.assert_image_equal(im, im2) + self.assert_image_equal_tofile(im, 'Tests/images/12in16bit.tif') def test_32bit_float(self): # Issue 614, specific 32-bit float format @@ -436,16 +399,6 @@ def test_with_underscores(self): self.assertEqual(im.tag_v2[X_RESOLUTION], 72) self.assertEqual(im.tag_v2[Y_RESOLUTION], 36) - def test_lzw(self): - # Act - im = Image.open("Tests/images/hopper_lzw.tif") - - # Assert - self.assertEqual(im.mode, 'RGB') - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "TIFF") - im2 = hopper() - self.assert_image_similar(im, im2, 5) def test_roundtrip_tiff_uint16(self): # Test an image of all '0' values diff --git a/_imaging.c b/_imaging.c index 44eb2dbb7b2..d90f23477bd 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3480,7 +3480,6 @@ extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PackbitsDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PcdDecoderNew(PyObject* self, PyObject* args); @@ -3553,7 +3552,6 @@ static PyMethodDef functions[] = { {"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, 1}, {"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, 1}, #endif - {"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1}, #ifdef HAVE_LIBTIFF {"libtiff_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, {"libtiff_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, diff --git a/decode.c b/decode.c index bfd19faa401..be1b503e6a4 100644 --- a/decode.c +++ b/decode.c @@ -35,7 +35,6 @@ #include "py3.h" #include "Gif.h" -#include "Lzw.h" #include "Raw.h" #include "Bit.h" #include "Sgi.h" @@ -484,35 +483,6 @@ PyImaging_HexDecoderNew(PyObject* self, PyObject* args) } -/* -------------------------------------------------------------------- */ -/* LZW */ -/* -------------------------------------------------------------------- */ - -PyObject* -PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; - - char* mode; - char* rawmode; - int filter = 0; - if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &filter)) - return NULL; - - decoder = PyImaging_DecoderNew(sizeof(LZWSTATE)); - if (decoder == NULL) - return NULL; - - if (get_unpacker(decoder, mode, rawmode) < 0) - return NULL; - - decoder->decode = ImagingLzwDecode; - - ((LZWSTATE*)decoder->state.context)->filter = filter; - - return (PyObject*) decoder; -} - /* -------------------------------------------------------------------- */ /* LibTiff */ /* -------------------------------------------------------------------- */ diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index d88c089ba53..e014cbd4d6c 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -549,13 +549,18 @@ For more information about the SPIDER image processing package, see the TIFF ^^^^ -PIL reads and writes TIFF files. It can read both striped and tiled images, -pixel and plane interleaved multi-band images, and either uncompressed, or -Packbits, LZW, or JPEG compressed images. +Pillow reads and writes TIFF files. It can read both striped and tiled +images, pixel and plane interleaved multi-band images. If you have +libtiff and its headers installed, PIL can read and write many kinds +of compressed TIFF files. If not, PIL will only read and write +uncompressed files. -If you have libtiff and its headers installed, PIL can read and write many more -kinds of compressed TIFF files. If not, PIL will always write uncompressed -files. +.. note:: + + Beginning in version 4.4.0, Pillow requires libtiff to read or + write compressed files. Prior to that release, Pillow had buggy + support for reading Packbits, LZW and JPEG compressed TIFFs + without using libtiff. The :py:meth:`~PIL.Image.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index fe8d796a206..aa59fe18c39 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -440,8 +440,6 @@ extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state); #endif -extern int ImagingLzwDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); #ifdef HAVE_LIBTIFF extern int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); diff --git a/libImaging/Lzw.h b/libImaging/Lzw.h deleted file mode 100644 index 8fc30bd7faf..00000000000 --- a/libImaging/Lzw.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * The Python Imaging Library. - * $Id$ - * - * declarations for the TIFF LZW decoder. - * - * Copyright (c) Fredrik Lundh 1995-96. - */ - - -/* Max size for LZW code words */ - -#define LZWBITS 12 - -#define LZWTABLE (1< -#include /* memcpy() */ - -#include "Lzw.h" - - -int -ImagingLzwDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - UINT8* p; - int c, i; - int thiscode; - LZWSTATE* context = (LZWSTATE*) state->context; - - unsigned char *ptr = buf; - - if (!state->state) { - - /* Clear code */ - context->clear = 1 << 8; - - /* End code */ - context->end = context->clear + 1; - - state->state = 1; - } - - for (;;) { - - if (state->state == 1) { - - /* First free entry in table */ - context->next = context->clear + 2; - - /* Initial code size */ - context->codesize = 8 + 1; - context->codemask = (1 << context->codesize) - 1; - - /* Buffer pointer. We fill the buffer from right, which - allows us to return all of it in one operation. */ - context->bufferindex = LZWBUFFER; - - state->state = 2; - } - - if (context->bufferindex < LZWBUFFER) { - - /* Return whole buffer in one chunk */ - i = LZWBUFFER - context->bufferindex; - p = &context->buffer[context->bufferindex]; - - context->bufferindex = LZWBUFFER; - - } else { - - /* Get current symbol */ - while (context->bitcount < context->codesize) { - - if (bytes < 1) - return ptr - buf;; - - /* Read next byte */ - c = *ptr++; bytes--; - - /* New bits are shifted in from from the right. */ - context->bitbuffer = (context->bitbuffer << 8) | c; - context->bitcount += 8; - - } - - /* Extract current symbol from bit buffer. */ - c = (context->bitbuffer >> (context->bitcount - - context->codesize)) - & context->codemask; - - /* Adjust buffer */ - context->bitcount -= context->codesize; - - /* If c is less than clear, it's a data byte. Otherwise, - it's either clear/end or a code symbol which should be - expanded. */ - - if (c == context->clear) { - if (state->state != 2) - state->state = 1; - continue; - } - - if (c == context->end) - break; - - i = 1; - p = &context->lastdata; - - if (state->state == 2) { - - /* First valid symbol after clear; use as is */ - if (c > context->clear) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - context->lastdata = context->lastcode = c; - state->state = 3; - - } else { - - thiscode = c; - - if (c > context->next) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - if (c == context->next) { - - /* c == next is allowed, by some strange reason */ - if (context->bufferindex <= 0) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - context->buffer[--context->bufferindex] = context->lastdata; - c = context->lastcode; - } - - while (c >= context->clear) { - - /* Copy data string to buffer (beginning from right) */ - - if (context->bufferindex <= 0 || c >= LZWTABLE) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - context->buffer[--context->bufferindex] = - context->data[c]; - c = context->link[c]; - } - - context->lastdata = c; - - if (context->next < LZWTABLE) { - - /* While we still have room for it, add this - symbol to the table. */ - context->data[context->next] = c; - context->link[context->next] = context->lastcode; - - context->next++; - - if (context->next == context->codemask && - context->codesize < LZWBITS) { - - /* Expand code size */ - context->codesize++; - context->codemask = (1 << context->codesize) - 1; - - } - } - context->lastcode = thiscode; - } - } - - /* Update the output image */ - for (c = 0; c < i; c++) { - - state->buffer[state->x] = p[c]; - - if (++state->x >= state->bytes) { - - int x, bpp; - - /* Apply filter */ - switch (context->filter) { - case 2: - /* Horizontal differing ("prior") */ - bpp = (state->bits + 7) / 8; - for (x = bpp; x < state->bytes; x++) - state->buffer[x] += state->buffer[x-bpp]; - } - - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); - - state->x = 0; - - if (++state->y >= state->ysize) - /* End of file (errcode = 0) */ - return -1; - } - } - } - - return ptr - buf; -} diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index c7b9a606c76..691eec7c1d8 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -1301,6 +1301,19 @@ static struct { {"RGBA", "B", 8, band2}, {"RGBA", "A", 8, band3}, +#ifdef WORDS_BIGENDIAN + {"RGB", "RGB;16N", 64, unpackRGB16B}, + {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, + {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, + {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, +#else + {"RGB", "RGB;16N", 64, unpackRGB16L}, + {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, + {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, + {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, +#endif + + /* true colour w. alpha premultiplied */ {"RGBa", "RGBa", 32, copy4}, {"RGBa", "BGRa", 32, unpackBGRA}, diff --git a/setup.py b/setup.py index 20226613936..8b443308126 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ "Blend", "Chops", "Convert", "ConvertYCbCr", "Copy", "Crc32", "Crop", "Dib", "Draw", "Effects", "EpsEncode", "File", "Fill", "Filter", "FliDecode", "Geometry", "GetBBox", "GifDecode", "GifEncode", "HexDecode", - "Histo", "JpegDecode", "JpegEncode", "LzwDecode", "Matrix", "ModeFilter", + "Histo", "JpegDecode", "JpegEncode", "Matrix", "ModeFilter", "Negative", "Offset", "Pack", "PackDecode", "Palette", "Paste", "Quant", "QuantOctree", "QuantHash", "QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage",