Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run all compressed tiffs through libtiff #2899

Merged
merged 13 commits into from
Dec 27, 2017
Merged
29 changes: 7 additions & 22 deletions PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down
35 changes: 29 additions & 6 deletions Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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

Expand Down
Binary file added Tests/images/copyleft.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/pil136.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/pil168.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tiff_16bit_RGBa_target.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/tiff_adobe_deflate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 50 additions & 9 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
3 changes: 2 additions & 1 deletion Tests/test_file_mic.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand Down
51 changes: 2 additions & 49 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions _imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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},
Expand Down
30 changes: 0 additions & 30 deletions decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
#include "py3.h"

#include "Gif.h"
#include "Lzw.h"
#include "Raw.h"
#include "Bit.h"
#include "Sgi.h"
Expand Down Expand Up @@ -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 */
/* -------------------------------------------------------------------- */
Expand Down
17 changes: 11 additions & 6 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version 5.0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going to have to do a blanket 4.4/5.0 replace.

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:
Expand Down
2 changes: 0 additions & 2 deletions libImaging/Imaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading