Merge pull request #2899 from wiredfool/pr_2882

Run all compressed tiffs through libtiff
This commit is contained in:
wiredfool 2017-12-27 20:39:51 +00:00 committed by GitHub
commit 49bd3232e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 115 additions and 410 deletions

View File

@ -1029,21 +1029,8 @@ class TiffImageFile(ImageFile.ImageFile):
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 @@ class TiffImageFile(ImageFile.ImageFile):
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 @@ class TiffImageFile(ImageFile.ImageFile):
# 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

View File

@ -14,11 +14,22 @@ logger = logging.getLogger(__name__)
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 @@ class PillowTestCase(unittest.TestCase):
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 @@ class PillowTestCase(unittest.TestCase):
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

BIN
Tests/images/copyleft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
Tests/images/pil136.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
Tests/images/pil168.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -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 @@ logger = logging.getLogger(__name__)
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 @@ class TestFileLibTiff(LibTiffTestCase):
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 @@ class TestFileLibTiff(LibTiffTestCase):
# 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 @@ class TestFileLibTiff(LibTiffTestCase):
# 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()

View File

@ -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 @@ TEST_FILE = "Tests/images/hopper.mic"
@unittest.skipUnless(olefile_installed, "olefile package not installed")
@unittest.skipUnless(features.check('libtiff'), "libtiff not installed")
class TestFileMic(PillowTestCase):
def test_sanity(self):

View File

@ -51,13 +51,7 @@ class TestFileTiff(PillowTestCase):
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 @@ class TestFileTiff(PillowTestCase):
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 @@ class TestFileTiff(PillowTestCase):
# 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 @@ class TestFileTiff(PillowTestCase):
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

View File

@ -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},

View File

@ -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 */
/* -------------------------------------------------------------------- */

View File

@ -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:

View File

@ -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);

View File

@ -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<<LZWBITS)
#define LZWBUFFER (1<<LZWBITS)
typedef struct {
/* CONFIGURATION */
/* Filter type */
int filter;
/* PRIVATE CONTEXT (set by decoder) */
/* Input bit buffer */
INT32 bitbuffer;
int bitcount;
/* Code buffer */
int codesize;
int codemask;
/* Constant symbol codes */
int clear, end;
/* Symbol history */
int lastcode;
unsigned char lastdata;
/* History buffer */
int bufferindex;
unsigned char buffer[LZWTABLE];
/* Symbol table */
UINT16 link[LZWTABLE];
unsigned char data[LZWTABLE];
int next;
} LZWSTATE;

View File

@ -1,230 +0,0 @@
/*
* The Python Imaging Library.
* $Id$
*
* a fast, suspendable TIFF LZW decoder
*
* description:
* This code is based on the GIF decoder. There are some
* subtle differences between GIF and TIFF LZW, though:
* - The fill order is different. In the TIFF file, you
* must shift new bits in to the right, not to the left.
* - There is no blocking in the input data stream.
* - The code size is increased one step earlier than
* for GIF
* - Image data are seen as a byte stream, not a pixel
* stream. This means that the code size will always
* start at 9 bits.
*
* history:
* 95-09-13 fl Created (derived from GifDecode.c)
* 96-03-28 fl Revised API, integrated with PIL
* 97-01-05 fl Added filter support, added extra consistency checks
*
* Copyright (c) Fredrik Lundh 1995-97.
* Copyright (c) Secret Labs AB 1997.
*
* See the README file for information on usage and redistribution.
*/
#include "Imaging.h"
#include <stdio.h>
#include <stdlib.h> /* 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;
}

View File

@ -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},

View File

@ -43,7 +43,7 @@ _LIB_IMAGING = (
"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",