diff --git a/PIL/Image.py b/PIL/Image.py index e1d88fe59..ec5ff548d 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -475,7 +475,7 @@ class Image: new.mode = im.mode new.size = im.size new.palette = self.palette - if im.mode == "P": + if im.mode == "P" and not new.palette: from PIL import ImagePalette new.palette = ImagePalette.ImagePalette() try: diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index f1976c182..0f6deedd3 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -54,6 +54,10 @@ import collections import itertools import os +# Set these to true to force use of libtiff for reading or writing. +READ_LIBTIFF = False +WRITE_LIBTIFF= False + II = b"II" # little-endian (intel-style) MM = b"MM" # big-endian (motorola-style) @@ -147,6 +151,7 @@ OPEN_INFO = { (II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8,8), (2,)): ("LA", "LA"), (II, 1, 1, 2, (8,), ()): ("L", "L;R"), + (II, 1, 1, 1, (12,), ()): ("I;16", "I;12"), (II, 1, 1, 1, (16,), ()): ("I;16", "I;16"), (II, 1, 2, 1, (16,), ()): ("I;16S", "I;16S"), (II, 1, 1, 1, (32,), ()): ("I", "I;32N"), @@ -617,8 +622,8 @@ class TiffImageFile(ImageFile.ImageFile): return args def _load_libtiff(self): - """ Overload method triggered when we detect a g3/g4 tiff - Calls out to lib tiff """ + """ Overload method triggered when we detect a compressed tiff + Calls out to libtiff """ pixel = Image.Image.load(self) @@ -633,7 +638,7 @@ class TiffImageFile(ImageFile.ImageFile): raise IOError("Not exactly one tile") d, e, o, a = self.tile[0] - d = Image._getdecoder(self.mode, d, a, self.decoderconfig) + d = Image._getdecoder(self.mode, 'libtiff', a, self.decoderconfig) try: d.setimage(self.im, e) except ValueError: @@ -759,11 +764,11 @@ class TiffImageFile(ImageFile.ImageFile): offsets = self.tag[STRIPOFFSETS] h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] - if 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 in ["tiff_ccitt", "group3", "group4", + "tiff_jpeg", "tiff_adobe_deflate", + "tiff_thunderscan", "tiff_deflate", + "tiff_sgilog", "tiff_sgilog24", + "tiff_raw_16"]: ## if Image.DEBUG: ## print "Activating g4 compression for whole file" @@ -810,7 +815,7 @@ 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'): + if self.mode in ('I;16B', 'I;16') and 'I;16' in rawmode: rawmode = 'I;16N' # Offset in the tile tuple is 0, we go from 0,0 to @@ -917,11 +922,12 @@ def _save(im, fp, filename): ifd = ImageFileDirectory(prefix) compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) - libtiff = compression in ["tiff_ccitt", "group3", "group4", - "tiff_jpeg", "tiff_adobe_deflate", - "tiff_thunderscan", "tiff_deflate", - "tiff_sgilog", "tiff_sgilog24", - "tiff_raw_16"] + + libtiff = WRITE_LIBTIFF or compression in ["tiff_ccitt", "group3", "group4", + "tiff_jpeg", "tiff_adobe_deflate", + "tiff_thunderscan", "tiff_deflate", + "tiff_sgilog", "tiff_sgilog24", + "tiff_raw_16"] # required for color libtiff images ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) @@ -1057,8 +1063,8 @@ def _save(im, fp, filename): if Image.DEBUG: print (atts) - # libtiff always returns the bytes in native order. - # we're expecting image byte order. So, if the rawmode + # libtiff always expects the bytes in native order. + # we're storing image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. if im.mode in ('I;16B', 'I;16'): @@ -1066,7 +1072,7 @@ def _save(im, fp, filename): a = (rawmode, compression, _fp, filename, atts) # print (im.mode, compression, a, im.encoderconfig) - e = Image._getencoder(im.mode, compression, a, im.encoderconfig) + e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e.setimage(im.im, (0,0)+im.size) while 1: l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock diff --git a/Tests/images/12bit.cropped.tif b/Tests/images/12bit.cropped.tif index eb8b1dc21..85af58328 100644 Binary files a/Tests/images/12bit.cropped.tif and b/Tests/images/12bit.cropped.tif differ diff --git a/Tests/images/12in16bit.tif b/Tests/images/12in16bit.tif new file mode 100644 index 000000000..02e1bfe6a Binary files /dev/null and b/Tests/images/12in16bit.tif differ diff --git a/Tests/images/12bit.MM.cropped.tif b/Tests/images/16bit.MM.cropped.tif similarity index 100% rename from Tests/images/12bit.MM.cropped.tif rename to Tests/images/16bit.MM.cropped.tif diff --git a/Tests/images/12bit.MM.deflate.tif b/Tests/images/16bit.MM.deflate.tif similarity index 100% rename from Tests/images/12bit.MM.deflate.tif rename to Tests/images/16bit.MM.deflate.tif diff --git a/Tests/images/16bit.cropped.tif b/Tests/images/16bit.cropped.tif new file mode 100644 index 000000000..eb8b1dc21 Binary files /dev/null and b/Tests/images/16bit.cropped.tif differ diff --git a/Tests/images/12bit.deflate.tif b/Tests/images/16bit.deflate.tif similarity index 100% rename from Tests/images/12bit.deflate.tif rename to Tests/images/16bit.deflate.tif diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 0c27865cd..3a6478e2a 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -27,3 +27,22 @@ def test_optimize(): return len(file.getvalue()) assert_equal(test(0), 800) assert_equal(test(1), 38) + +def test_roundtrip(): + out = tempfile('temp.gif') + im = lena() + im.save(out) + reread = Image.open(out) + + assert_image_similar(reread.convert('RGB'), im, 50) + +def test_roundtrip2(): + #see https://github.com/python-imaging/Pillow/issues/403 + out = 'temp.gif'#tempfile('temp.gif') + im = Image.open('Images/lena.gif') + im2 = im.copy() + im2.save(out) + reread = Image.open(out) + + assert_image_similar(reread.convert('RGB'), lena(), 50) + diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 843d98896..6553518fc 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,10 +1,10 @@ from tester import * -from PIL import Image +from PIL import Image, TiffImagePlugin codecs = dir(Image.core) -if "group4_encoder" not in codecs or "group4_decoder" not in codecs: +if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: skip("tiff support not available") def _assert_noerr(im): @@ -118,7 +118,7 @@ def test_g3_compression(): assert_image_equal(reread, i) def test_little_endian(): - im = Image.open('Tests/images/12bit.deflate.tif') + im = Image.open('Tests/images/16bit.deflate.tif') assert_equal(im.getpixel((0,0)), 480) assert_equal(im.mode, 'I;16') @@ -143,7 +143,7 @@ def test_little_endian(): # on big endian, we'll get back mode = 'I;16B' here. def test_big_endian(): - im = Image.open('Tests/images/12bit.MM.deflate.tif') + im = Image.open('Tests/images/16bit.MM.deflate.tif') assert_equal(im.getpixel((0,0)), 480) assert_equal(im.mode, 'I;16B') @@ -178,6 +178,31 @@ def test_g4_string_info(): reread = Image.open(out) assert_equal('temp.tif', reread.tag[269]) +def test_12bit_rawmode(): + """ Are we generating the same interpretation of the image as Imagemagick is? """ + TiffImagePlugin.READ_LIBTIFF = True + #Image.DEBUG = True + im = Image.open('Tests/images/12bit.cropped.tif') + im.load() + TiffImagePlugin.READ_LIBTIFF = False + # to make the target -- + # convert 12bit.cropped.tif -depth 16 tmp.tif + # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif + # 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') + + if Image.DEBUG: + print (im.getpixel((0,0))) + print (im.getpixel((0,1))) + print (im.getpixel((0,2))) + + print (im2.getpixel((0,0))) + print (im2.getpixel((0,1))) + print (im2.getpixel((0,2))) + + assert_image_equal(im, im2) def test_blur(): # test case from irc, how to do blur on b/w image and save to compressed tif. diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index e0f014980..2ad71d6e6 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -6,7 +6,7 @@ from test_file_libtiff import _assert_noerr codecs = dir(Image.core) -if "group4_encoder" not in codecs or "group4_decoder" not in codecs: +if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: skip("tiff support not available") """ The small lena image was failing on open in the libtiff diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 88c1aa8fd..9041b2046 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -74,7 +74,7 @@ def test_xyres_tiff(): def test_little_endian(): - im = Image.open('Tests/images/12bit.cropped.tif') + im = Image.open('Tests/images/16bit.cropped.tif') assert_equal(im.getpixel((0,0)), 480) assert_equal(im.mode, 'I;16') @@ -89,7 +89,7 @@ def test_little_endian(): def test_big_endian(): - im = Image.open('Tests/images/12bit.MM.cropped.tif') + im = Image.open('Tests/images/16bit.MM.cropped.tif') assert_equal(im.getpixel((0,0)), 480) assert_equal(im.mode, 'I;16B') @@ -102,3 +102,29 @@ def test_big_endian(): else: assert_equal(b[0], b'\x01') assert_equal(b[1], b'\xe0') + + +def test_12bit_rawmode(): + """ Are we generating the same interpretation of the image as Imagemagick is? """ + + #Image.DEBUG = True + im = Image.open('Tests/images/12bit.cropped.tif') + + # to make the target -- + # convert 12bit.cropped.tif -depth 16 tmp.tif + # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif + # 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') + + if Image.DEBUG: + print (im.getpixel((0,0))) + print (im.getpixel((0,1))) + print (im.getpixel((0,2))) + + print (im2.getpixel((0,0))) + print (im2.getpixel((0,1))) + print (im2.getpixel((0,2))) + + assert_image_equal(im, im2) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 547246c8b..fd3d39bc5 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -38,12 +38,12 @@ def test_8bit(): im = Image.open('Images/lena.jpg') _test_float_conversion(im.convert('L')) -def test_12bit(): - im = Image.open('Tests/images/12bit.cropped.tif') +def test_16bit(): + im = Image.open('Tests/images/16bit.cropped.tif') _test_float_conversion(im) -def test_12bit_workaround(): - im = Image.open('Tests/images/12bit.cropped.tif') +def test_16bit_workaround(): + im = Image.open('Tests/images/16bit.cropped.tif') _test_float_conversion(im.convert('I')) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 61b3cf480..7c7003179 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -75,7 +75,7 @@ def _test_img_equals_nparray(img, np): def test_16bit(): - img = Image.open('Tests/images/12bit.cropped.tif') + img = Image.open('Tests/images/16bit.cropped.tif') np_img = numpy.array(img) _test_img_equals_nparray(img, np_img) assert_equal(np_img.dtype, numpy.dtype('state, compression, fp)) { + if (! ImagingLibTiffInit(&decoder->state, fp)) { Py_DECREF(decoder); PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); return NULL; diff --git a/encode.c b/encode.c index 2c9145c71..8be44d8ec 100644 --- a/encode.c +++ b/encode.c @@ -673,7 +673,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) char* rawmode; char* compname; char* filename; - int compression; int fp; PyObject *dir; @@ -706,47 +705,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) TRACE(("new tiff encoder %s fp: %d, filename: %s \n", compname, fp, filename)); - /* UNDONE -- we can probably do almost any arbitrary compression here, - * so long as we're doing row/stripe based actions and not tiles. - */ - - if (strcasecmp(compname, "tiff_ccitt") == 0) { - compression = COMPRESSION_CCITTRLE; - - } else if (strcasecmp(compname, "group3") == 0) { - compression = COMPRESSION_CCITTFAX3; - - } else if (strcasecmp(compname, "group4") == 0) { - compression = COMPRESSION_CCITTFAX4; - - } else if (strcasecmp(compname, "tiff_jpeg") == 0) { - compression = COMPRESSION_OJPEG; - - } else if (strcasecmp(compname, "tiff_adobe_deflate") == 0) { - compression = COMPRESSION_ADOBE_DEFLATE; - - } else if (strcasecmp(compname, "tiff_thunderscan") == 0) { - compression = COMPRESSION_THUNDERSCAN; - - } else if (strcasecmp(compname, "tiff_deflate") == 0) { - compression = COMPRESSION_DEFLATE; - - } else if (strcasecmp(compname, "tiff_sgilog") == 0) { - compression = COMPRESSION_SGILOG; - - } else if (strcasecmp(compname, "tiff_sgilog24") == 0) { - compression = COMPRESSION_SGILOG24; - - } else if (strcasecmp(compname, "tiff_raw_16") == 0) { - compression = COMPRESSION_CCITTRLEW; - - } else { - PyErr_SetString(PyExc_ValueError, "unknown compession"); - return NULL; - } - - TRACE(("Found compression: %d\n", compression)); - encoder = PyImaging_EncoderNew(sizeof(TIFFSTATE)); if (encoder == NULL) return NULL; diff --git a/libImaging/TiffDecode.c b/libImaging/TiffDecode.c index d535a5033..787cd4506 100644 --- a/libImaging/TiffDecode.c +++ b/libImaging/TiffDecode.c @@ -142,11 +142,11 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { (void) hdata; (void) base; (void) size; } -int ImagingLibTiffInit(ImagingCodecState state, int compression, int fp) { +int ImagingLibTiffInit(ImagingCodecState state, int fp) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TRACE(("initing libtiff\n")); - TRACE(("Compression: %d, filepointer: %d \n", compression, fp)); + TRACE(("filepointer: %d \n", fp)); TRACE(("State: count %d, state %d, x %d, y %d, ystep %d\n", state->count, state->state, state->x, state->y, state->ystep)); TRACE(("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, diff --git a/libImaging/TiffDecode.h b/libImaging/TiffDecode.h index 306b3fab4..90fe3c9d4 100644 --- a/libImaging/TiffDecode.h +++ b/libImaging/TiffDecode.h @@ -38,7 +38,7 @@ typedef struct { -extern int ImagingLibTiffInit(ImagingCodecState state, int compression, int fp); +extern int ImagingLibTiffInit(ImagingCodecState state, int fp); extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index f6d6718d1..70b11b1b0 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -795,6 +795,60 @@ unpackI16N_I16(UINT8* out, const UINT8* in, int pixels){ } } +static void +unpackI12_I16(UINT8* out, const UINT8* in, int pixels){ + /* Fillorder 1/MSB -> LittleEndian, for 12bit integer greyscale tiffs. + + According to the TIFF spec: + + FillOrder = 2 should be used only when BitsPerSample = 1 and + the data is either uncompressed or compressed using CCITT 1D + or 2D compression, to avoid potentially ambigous situations. + + Yeah. I thought so. We'll see how well people read the spec. + We've got several fillorder=2 modes in TiffImagePlugin.py + + There's no spec I can find. It appears that the in storage + layout is: 00 80 00 ... -> (128 , 0 ...). The samples are + stored in a single big bitian 12bit block, but need to be + pulled out to little endian format to be stored in a 2 byte + int. + */ + + int i; + UINT16 pixel; +#ifdef WORDS_BIGENDIAN + UINT8* tmp = (UINT8 *)&pixel; +#endif + UINT16* out16 = (UINT16 *)out; + for (i = 0; i < pixels-1; i+=2) { + pixel = (((UINT16) in[0]) << 4 ) + (in[1] >>4); +#ifdef WORDS_BIGENDIAN + out[0] = tmp[1]; out[1] = tmp[0]; +#else + out16[0] = pixel; +#endif + + pixel = (((UINT16) (in[1] & 0x0F)) << 8) + in[2]; +#ifdef WORDS_BIGENDIAN + out[2] = tmp[1]; out[3] = tmp[0]; +#else + out16[1] = pixel; +#endif + + in += 3; out16 += 2; out+=4; + } + if (i == pixels-1) { + pixel = (((UINT16) in[0]) << 4 ) + (in[1] >>4); +#ifdef WORDS_BIGENDIAN + out[0] = tmp[1]; out[1] = tmp[0]; +#else + out16[0] = pixel; +#endif + } +} + + static void copy1(UINT8* out, const UINT8* in, int pixels) { @@ -1163,6 +1217,8 @@ static struct { {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16B", "I;16N", 16, unpackI16N_I16B}, + {"I;16", "I;12", 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. + {NULL} /* sentinel */ };