diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 57480a361..3c62fd2dd 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -54,7 +54,9 @@ 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) @@ -149,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"), @@ -675,8 +678,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) @@ -691,7 +694,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: @@ -817,11 +820,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" @@ -868,7 +871,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 @@ -975,12 +978,16 @@ 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) + # -- multi-page -- skip TIFF header on subsequent pages if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) @@ -1083,6 +1090,8 @@ def _save(im, fp, filename): blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. atts={} + # bits per sample is a single short in the tiff directory, not a list. + atts[BITSPERSAMPLE] = bits[0] # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. @@ -1100,8 +1109,8 @@ def _save(im, fp, filename): continue if type(v) == tuple and len(v) > 2: # List of ints? - # BitsPerSample is one example, I get (8,8,8) - # UNDONE + if type(v[0]) in (int, float): + atts[k] = list(v) continue if type(v) == tuple and len(v) == 2: # one rational tuple @@ -1121,8 +1130,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'): @@ -1130,7 +1139,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_libtiff.py b/Tests/test_file_libtiff.py index 1c5b22228..7f7ba1d63 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): @@ -141,7 +141,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') @@ -156,7 +156,7 @@ def test_little_endian(): out = tempfile("temp.tif") - out = "temp.le.tif" + #out = "temp.le.tif" im.save(out) reread = Image.open(out) @@ -166,7 +166,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') @@ -201,4 +201,87 @@ 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. + from PIL import ImageFilter + out = tempfile('temp.tif') + im = Image.open('Tests/images/pport_g4.tif') + im = im.convert('L') + + im=im.filter(ImageFilter.GaussianBlur(4)) + im.save(out, compression='tiff_adobe_deflate') + + im2 = Image.open(out) + im2.load() + + assert_image_equal(im, im2) + + +def test_compressions(): + im = lena('RGB') + out = tempfile('temp.tif') + + TiffImagePlugin.READ_LIBTIFF = True + TiffImagePlugin.WRITE_LIBTIFF = True + + for compression in ('packbits', 'tiff_lzw'): + im.save(out, compression=compression) + im2 = Image.open(out) + assert_image_equal(im, im2) + + im.save(out, compression='jpeg') + im2 = Image.open(out) + assert_image_similar(im, im2, 30) + + TiffImagePlugin.READ_LIBTIFF = False + TiffImagePlugin.WRITE_LIBTIFF = False + + + + +def test_cmyk_save(): + im = lena('CMYK') + out = tempfile('temp.tif') + + im.save(out, compression='tiff_adobe_deflate') + im2 = Image.open(out) + assert_image_equal(im, im2) + +def xtest_bw_compression_wRGB(): + """ This test passes, but when running all tests causes a failure due to + output on stderr from the error thrown by libtiff. We need to capture that + but not now""" + + im = lena('RGB') + out = tempfile('temp.tif') + + assert_exception(IOError, lambda: im.save(out, compression='tiff_ccitt')) + assert_exception(IOError, lambda: im.save(out, compression='group3')) + assert_exception(IOError, lambda: im.save(out, compression='group4')) 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 10ed90d12..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; @@ -780,18 +738,35 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } else if(PyList_Check(value)) { int len,i; float *floatav; + int *intav; TRACE(("Setting from List: %d \n", (int)PyInt_AsLong(key))); len = (int)PyList_Size(value); - TRACE((" %d elements, setting as floats \n", len)); - floatav = malloc(sizeof(float)*len); - if (floatav) { - for (i=0;istate, + (ttag_t) PyInt_AsLong(key), + intav); + free(intav); + } + } else { + TRACE((" %d elements, setting as floats \n", len)); + floatav = malloc(sizeof(float)*len); + if (floatav) { + for (i=0;istate, + (ttag_t) PyInt_AsLong(key), + floatav); + free(floatav); + } } - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) PyInt_AsLong(key), - floatav); - free(floatav); } } else if (PyFloat_Check(value)) { TRACE(("Setting from Float: %d, %f \n", (int)PyInt_AsLong(key),PyFloat_AsDouble(value))); 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 */ };