diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index e3b706f5b..7ec194043 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -50,6 +50,7 @@ from PIL import _binary import array, sys import collections import itertools +import os II = b"II" # little-endian (intel-style) MM = b"MM" # big-endian (motorola-style) @@ -121,6 +122,8 @@ COMPRESSION_INFO = { 32773: "packbits" } +COMPRESSION_INFO_REV = dict([(v,k) for (k,v) in COMPRESSION_INFO.items()]) + OPEN_INFO = { # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, # ExtraSamples) => mode, rawmode @@ -532,7 +535,12 @@ class TiffImageFile(ImageFile.ImageFile): self.__frame = -1 self.__fp = self.fp - # and load the first frame + if Image.DEBUG: + print ("*** TiffImageFile._open ***") + print ("- __first:", self.__first) + print ("- ifh: ", ifh) + + # and load the first frame self._seek(0) def seek(self, frame): @@ -567,7 +575,7 @@ class TiffImageFile(ImageFile.ImageFile): return self.__frame - def _decoder(self, rawmode, layer): + def _decoder(self, rawmode, layer, tile=None): "Setup decoder contexts" args = None @@ -594,6 +602,63 @@ 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 """ + + pixel = Image.Image.load(self) + + if self.tile is None: + raise IOError("cannot load this image") + if not self.tile: + return pixel + + self.load_prepare() + + if not len(self.tile) == 1: + raise IOError("Not exactly one tile") + + d, e, o, a = self.tile[0] + d = Image._getdecoder(self.mode, d, a, self.decoderconfig) + try: + d.setimage(self.im, e) + except ValueError: + raise IOError("Couldn't set the image") + + if hasattr(self.fp, "fileno"): + # we've got a actual file on disk, pass in the fp. + if Image.DEBUG: + print ("have fileno, calling fileno version of the decoder.") + self.fp.seek(0) + n,e = d.decode("fpfp") # 4 bytes, otherwise the trace might error out + elif hasattr(self.fp, "getvalue"): + # We've got a stringio like thing passed in. Yay for all in memory. + # The decoder needs the entire file in one shot, so there's not + # a lot we can do here other than give it the entire file. + # unless we could do something like get the address of the underlying + # string for stringio. + if Image.DEBUG: + print ("have getvalue. just sending in a string from getvalue") + n,e = d.decode(self.fp.getvalue()) + else: + # we have something else. + if Image.DEBUG: + print ("don't have fileno or getvalue. just reading") + # UNDONE -- so much for that buffer size thing. + n, e = d.decode(self.fp.read()) + + + self.tile = [] + self.readonly = 0 + self.fp = None # might be shared + + if e < 0: + raise IOError(e) + + self.load_end() + + return Image.Image.load(self) + def _setup(self): "Setup this image object based on current tags" @@ -669,20 +734,54 @@ class TiffImageFile(ImageFile.ImageFile): self.tile = [] if STRIPOFFSETS in self.tag: # striped image + offsets = self.tag[STRIPOFFSETS] h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] - a = None - for o in self.tag[STRIPOFFSETS]: - if not a: - a = self._decoder(rawmode, l) + if self._compression in ["tiff_ccitt", "group3", + "group4", "tiff_raw_16"]: + ## if Image.DEBUG: + ## print "Activating g4 compression for whole file" + + # Decoder expects entire file as one tile. + # There's a buffer size limit in load (64k) + # so large g4 images will fail if we use that + # function. + # + # Setup the one tile for the whole image, then + # replace the existing load function with our + # _load_libtiff function. + + self.load = self._load_libtiff + + # To be nice on memory footprint, if there's a + # file descriptor, use that instead of reading + # into a string in python. + + # libtiff closes the file descriptor, so pass in a dup. + fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno()) + + # Offset in the tile tuple is 0, we go from 0,0 to + # w,h, and we only do this once -- eds + a = (rawmode, self._compression, fp ) self.tile.append( (self._compression, - (0, min(y, ysize), w, min(y+h, ysize)), - o, a)) - y = y + h - if y >= self.size[1]: - x = y = 0 - l = l + 1 + (0, 0, w, ysize), + 0, a)) + a = None + + else: + for i in range(len(offsets)): + a = self._decoder(rawmode, l, i) + self.tile.append( + (self._compression, + (0, min(y, ysize), w, min(y+h, ysize)), + offsets[i], a)) + if Image.DEBUG: + print ("tiles: ", self.tile) + y = y + h + if y >= self.size[1]: + x = y = 0 + l = l + 1 a = None elif TILEOFFSETS in self.tag: # tiled image @@ -764,8 +863,12 @@ def _save(im, fp, filename): ifd = ImageFileDirectory(prefix) + compression = im.info.get('compression','raw') + libtiff = compression in ["tiff_ccitt", "group3", + "group4", "tiff_raw_16"] + # -- multi-page -- skip TIFF header on subsequent pages - if fp.tell() == 0: + if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) # PIL always starts the first IFD at offset 8 fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8)) @@ -842,13 +945,65 @@ def _save(im, fp, filename): ifd[ROWSPERSTRIP] = im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer - ifd[COMPRESSION] = 1 # no compression + ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default - offset = ifd.save(fp) + if libtiff: + if Image.DEBUG: + print ("Saving using libtiff encoder") + print (ifd.items()) + _fp = 0 + if hasattr(fp, "fileno"): + fp.seek(0) + _fp = os.dup(fp.fileno()) - ImageFile._save(im, fp, [ - ("raw", (0,0)+im.size, offset, (rawmode, stride, 1)) - ]) + blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. + atts = dict([(k,v) for (k,(v,)) in ifd.items() if k not in blocklist]) + try: + # pull in more bits from the original file, e.g x,y resolution + # so that we can save(load('')) == original file. + for k,v in im.ifd.items(): + if k not in atts and k not in blocklist: + if type(v[0]) == tuple and len(v) > 1: + # A tuple of more than one rational tuples + # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL + atts[k] = [float(elt[0])/float(elt[1]) for elt in v] + continue + if type(v[0]) == tuple and len(v) == 1: + # A tuple of one rational tuples + # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL + atts[k] = float(v[0][0])/float(v[0][1]) + continue + if type(v) == tuple and len(v) == 1: + # int or similar + atts[k] = v[0] + continue + if type(v) == str: + atts[k] = v + continue + + except: + # if we don't have an ifd here, just punt. + pass + if Image.DEBUG: + print (atts) + a = (rawmode, compression, _fp, filename, atts) + e = Image._getencoder(im.mode, compression, 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 + if not _fp: + fp.write(d) + if s: + break + if s < 0: + raise IOError("encoder error %d when writing image file" % s) + + else: + offset = ifd.save(fp) + + ImageFile._save(im, fp, [ + ("raw", (0,0)+im.size, offset, (rawmode, stride, 1)) + ]) # -- helper for multi-page save -- diff --git a/Tests/images/lena_bw.png b/Tests/images/lena_bw.png new file mode 100644 index 000000000..f9b64c185 Binary files /dev/null and b/Tests/images/lena_bw.png differ diff --git a/Tests/images/lena_bw_500.png b/Tests/images/lena_bw_500.png new file mode 100644 index 000000000..1e1d0bd7d Binary files /dev/null and b/Tests/images/lena_bw_500.png differ diff --git a/Tests/images/lena_g4.tif b/Tests/images/lena_g4.tif new file mode 100644 index 000000000..7ebe72fab Binary files /dev/null and b/Tests/images/lena_g4.tif differ diff --git a/Tests/images/lena_g4_500.tif b/Tests/images/lena_g4_500.tif new file mode 100644 index 000000000..80f5e70f1 Binary files /dev/null and b/Tests/images/lena_g4_500.tif differ diff --git a/Tests/images/pport_g4.tif b/Tests/images/pport_g4.tif new file mode 100644 index 000000000..890e21318 Binary files /dev/null and b/Tests/images/pport_g4.tif differ diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 843e13d28..584d6b804 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -2,6 +2,9 @@ from tester import * from PIL import Image +import StringIO +import random + def test_sanity(): file = tempfile("temp.tif") @@ -55,3 +58,98 @@ def test_gimp_tiff(): ('jpeg', (0, 192, 256, 256), 3890, ('RGB', '')), ]) assert_no_exception(lambda: im.load()) + +def _assert_noerr(im): + """Helper tests that assert basic sanity about the g4 tiff reading""" + #1 bit + assert_equal(im.mode, "1") + + # Does the data actually load + assert_no_exception(lambda: im.load()) + assert_no_exception(lambda: im.getdata()) + + try: + assert_equal(im._compression, 'group4') + except: + print "No _compression" + print (dir(im)) + + # can we write it back out, in a different form. + out = tempfile("temp.png") + assert_no_exception(lambda: im.save(out)) + +def test_g4_tiff(): + """Test the ordinary file path load path""" + + file = "Tests/images/lena_g4_500.tif" + im = Image.open(file) + + assert_equal(im.size, (500,500)) + _assert_noerr(im) + +def test_g4_large(): + file = "Tests/images/pport_g4.tif" + im = Image.open(file) + _assert_noerr(im) + +def test_g4_tiff_file(): + """Testing the string load path""" + + file = "Tests/images/lena_g4_500.tif" + with open(file,'rb') as f: + im = Image.open(f) + + assert_equal(im.size, (500,500)) + _assert_noerr(im) + +def test_g4_tiff_stringio(): + """Testing the stringio loading code path""" + + file = "Tests/images/lena_g4_500.tif" + s = StringIO.StringIO() + with open(file,'rb') as f: + s.write(f.read()) + s.seek(0) + im = Image.open(s) + + assert_equal(im.size, (500,500)) + _assert_noerr(im) + +def test_g4_tiff_fail(): # UNDONE fails badly, unknown reason + """The 128x128 lena image fails for some reason. Investigating""" + + Image.DEBUG = True + file = "Tests/images/lena_g4.tif" + im = Image.open(file) + + assert_equal(im.size, (128,128)) + _assert_noerr(im) + Image.DEBUG = False + +def test_g4_eq_png(): + """ Checking that we're actually getting the data that we expect""" + png = Image.open('Tests/images/lena_bw_500.png') + g4 = Image.open('Tests/images/lena_g4_500.tif') + + assert_image_equal(g4, png) + +def test_g4_write(): + """Checking to see that the saved image is the same as what we wrote""" + Image.DEBUG = True + + file = "Tests/images/lena_g4_500.tif" + orig = Image.open(file) + + out = "temp.tif" + rot = orig.transpose(Image.ROTATE_90) + assert_equal(rot.size,(500,500)) + rot.save(out) + + reread = Image.open(out) + assert_equal(reread.size,(500,500)) + _assert_noerr(reread) + assert_image_equal(reread, rot) + + assert_false(orig.tobytes() == reread.tobytes()) + + Image.DEBUG = False diff --git a/_imaging.c b/_imaging.c index 29f1f2352..8b5d2a0f0 100644 --- a/_imaging.c +++ b/_imaging.c @@ -3236,6 +3236,7 @@ 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_TiffLzwDecoderNew(PyObject* self, PyObject* args); +extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_MspDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PackbitsDecoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_PcdDecoderNew(PyObject* self, PyObject* args); @@ -3254,6 +3255,7 @@ extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args); +extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args); /* Display support etc (in display.c) */ #ifdef WIN32 @@ -3303,6 +3305,17 @@ static PyMethodDef functions[] = { {"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1}, #endif {"tiff_lzw_decoder", (PyCFunction)PyImaging_TiffLzwDecoderNew, 1}, +#ifdef HAVE_LIBTIFF + {"tiff_ccitt_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, + {"group3_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, + {"group4_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, + {"tiff_raw_16_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, + + {"tiff_ccitt_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, + {"group3_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, + {"group4_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, + {"tiff_raw_16_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, +#endif {"msp_decoder", (PyCFunction)PyImaging_MspDecoderNew, 1}, {"packbits_decoder", (PyCFunction)PyImaging_PackbitsDecoderNew, 1}, {"pcd_decoder", (PyCFunction)PyImaging_PcdDecoderNew, 1}, diff --git a/decode.c b/decode.c index 9ecfee346..cb037072b 100644 --- a/decode.c +++ b/decode.c @@ -388,6 +388,75 @@ PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args) return (PyObject*) decoder; } +/* -------------------------------------------------------------------- */ +/* LibTiff */ +/* -------------------------------------------------------------------- */ + +#ifdef HAVE_LIBTIFF + +#include "Tiff.h" + +#include +#ifdef __WIN32__ +#define strcasecmp(s1, s2) stricmp(s1, s2) +#endif + +PyObject* +PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) +{ + ImagingDecoderObject* decoder; + char* mode; + char* rawmode; + char* compname; + int compression; + int fp; + + if (! PyArg_ParseTuple(args, "sssi", &mode, &rawmode, &compname, &fp)) + return NULL; + + TRACE(("new tiff decoder %s\n", compname)); + + /* UNDONE -- we can probably do almost any arbitrary compression here, + * since we're effective passing in the whole file in one shot and + * getting back the data row by row. V2 maybe + */ + + 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_raw_16") == 0) { + compression = COMPRESSION_CCITTRLEW; + + } else { + PyErr_SetString(PyExc_ValueError, "unknown compession"); + return NULL; + } + + decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE)); + if (decoder == NULL) + return NULL; + + if (get_unpacker(decoder, mode, rawmode) < 0) + return NULL; + + if (! ImagingLibTiffInit(&decoder->state, compression, fp)) { + Py_DECREF(decoder); + PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); + return NULL; + } + + decoder->decode = ImagingLibTiffDecode; + + return (PyObject*) decoder; +} + +#endif /* -------------------------------------------------------------------- */ /* MSP */ diff --git a/encode.c b/encode.c index f6191613f..b3d6faeae 100644 --- a/encode.c +++ b/encode.c @@ -650,3 +650,154 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) } #endif + +/* -------------------------------------------------------------------- */ +/* LibTiff */ +/* -------------------------------------------------------------------- */ + +#ifdef HAVE_LIBTIFF + +#include "Tiff.h" + +#include +#ifdef __WIN32__ +#define strcasecmp(s1, s2) stricmp(s1, s2) +#endif + +PyObject* +PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) +{ + ImagingEncoderObject* encoder; + + char* mode; + char* rawmode; + char* compname; + char* filename; + int compression; + int fp; + + PyObject *dir; + PyObject *key, *value; + Py_ssize_t pos = 0; + int status; + + Py_ssize_t d_size; + PyObject *keys, *values; + + + if (! PyArg_ParseTuple(args, "sssisO", &mode, &rawmode, &compname, &fp, &filename, &dir)) { + return NULL; + } + + if (!PyDict_Check(dir)) { + PyErr_SetString(PyExc_ValueError, "Invalid Dictionary"); + return NULL; + } else { + d_size = PyDict_Size(dir); + TRACE(("dict size: %d\n", (int)d_size)); + keys = PyDict_Keys(dir); + values = PyDict_Values(dir); + for (pos=0;posstate, filename, fp)) { + Py_DECREF(encoder); + PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); + return NULL; + } + + // While failes on 64 bit machines, complains that pos is an int instead of a Py_ssize_t + // while (PyDict_Next(dir, &pos, &key, &value)) { + for (pos=0;posstate, + (ttag_t) PyInt_AsLong(key), + PyInt_AsLong(value)); + } else if(PyBytes_Check(value)) { + TRACE(("Setting from String: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(value))); + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + PyBytes_AsString(value)); + + } else if(PyList_Check(value)) { + int len,i; + float *floatav; + 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), + floatav); + free(floatav); + } + } else if (PyFloat_Check(value)) { + TRACE(("Setting from String: %d, %f \n", (int)PyInt_AsLong(key),PyFloat_AsDouble(value))); + status = ImagingLibTiffSetField(&encoder->state, + (ttag_t) PyInt_AsLong(key), + (float)PyFloat_AsDouble(value)); + } else { + TRACE(("Unhandled type for key %d : %s ", + (int)PyInt_AsLong(key), + PyBytes_AsString(PyObject_Str(value)))); + } + if (!status) { + TRACE(("Error setting Field\n")); + Py_DECREF(encoder); + PyErr_SetString(PyExc_RuntimeError, "Error setting from dictionary"); + return NULL; + } + } + + encoder->encode = ImagingLibTiffEncode; + + return (PyObject*) encoder; +} + +#endif + diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 76861a0df..64a4fb66d 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -420,6 +420,12 @@ extern int ImagingJpegEncode(Imaging im, 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); +extern int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, + UINT8* buffer, int bytes); +#endif #ifdef HAVE_LIBMPEG extern int ImagingMpegDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes); diff --git a/libImaging/Tiff.h b/libImaging/Tiff.h new file mode 100644 index 000000000..d05c309f2 --- /dev/null +++ b/libImaging/Tiff.h @@ -0,0 +1,57 @@ +/* + * The Python Imaging Library. + * $Id: //modules/pil/libImaging/Tiff.h#1 $ + * + * declarations for the LibTiff-based Group3 and Group4 decoder + * + */ + +#ifndef _TIFFIO_ +#include +#endif +#ifndef _TIFF_ +#include +#endif + +#ifndef min +#define min(x,y) (( x > y ) ? y : x ) +#define max(x,y) (( x < y ) ? y : x ) +#endif + +#ifndef _PIL_LIBTIFF_ +#define _PIL_LIBTIFF_ + +typedef struct { + tdata_t data; /* tdata_t == void* */ + toff_t loc; /* toff_t == uint32 */ + tsize_t size; /* tsize_t == int32 */ + int fp; + TIFF *tiff; /* Used in write */ + toff_t eof; + int flrealloc; /* may we realloc */ +} TIFFSTATE; + + + +extern int ImagingLibTiffInit(ImagingCodecState state, int compression, int fp); +extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); +extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); + + +#if defined(_MSC_VER) && (_MSC_VER == 1310) +/* VS2003/py2.4 can't use varargs. Skipping trace for now.*/ +#define TRACE(args) +#else + + +#define VA_ARGS(...) __VA_ARGS__ +#define TRACE(args) fprintf(stderr, VA_ARGS args) + +/* +#define TRACE(args) +*/ +#endif + + + +#endif diff --git a/libImaging/TiffDecode.c b/libImaging/TiffDecode.c new file mode 100644 index 000000000..f380b6c39 --- /dev/null +++ b/libImaging/TiffDecode.c @@ -0,0 +1,417 @@ +/* + * The Python Imaging Library. + * $Id: //modules/pil/libImaging/TiffDecode.c#1 $ + * + * LibTiff-based Group3 and Group4 decoder + * + * + * started modding to use non-private tiff functions to port to libtiff 4.x + * eds 3/12/12 + * + */ + +#include "Imaging.h" + +#ifdef HAVE_LIBTIFF + +#ifndef uint +#define uint uint32 +#endif + +#include "Tiff.h" + +void dump_state(const TIFFSTATE *state){ + TRACE(("State: Location %u size %d eof %d data: %p \n", (uint)state->loc, + (int)state->size, (uint)state->eof, state->data)); +} + +/* + procs for TIFFOpenClient +*/ + +tsize_t _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { + TIFFSTATE *state = (TIFFSTATE *)hdata; + tsize_t to_read; + + TRACE(("_tiffReadProc: %d \n", (int)size)); + dump_state(state); + + to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); + TRACE(("to_read: %d\n", (int)to_read)); + + _TIFFmemcpy(buf, (UINT8 *)state->data + state->loc, to_read); + state->loc += (toff_t)to_read; + + TRACE( ("location: %u\n", (uint)state->loc)); + return to_read; +} + +tsize_t _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { + TIFFSTATE *state = (TIFFSTATE *)hdata; + tsize_t to_write; + + TRACE(("_tiffWriteProc: %d \n", (int)size)); + dump_state(state); + + to_write = min(size, state->size - (tsize_t)state->loc); + if (state->flrealloc && size>to_write) { + tdata_t new; + tsize_t newsize=state->size; + while (newsize < (size + state->size)) { + newsize += 64*1024; + // newsize*=2; // UNDONE, by 64k chunks? + } + TRACE(("Reallocing in write to %d bytes\n", (int)newsize)); + new = realloc(state->data, newsize); + if (!new) { + // fail out + return 0; + } + state->data = new; + state->size = newsize; + to_write = size; + } + + TRACE(("to_write: %d\n", (int)to_write)); + + _TIFFmemcpy((UINT8 *)state->data + state->loc, buf, to_write); + state->loc += (toff_t)to_write; + state->eof = max(state->loc, state->eof); + + dump_state(state); + return to_write; +} + +toff_t _tiffSeekProc(thandle_t hdata, toff_t off, int whence) { + TIFFSTATE *state = (TIFFSTATE *)hdata; + + TRACE(("_tiffSeekProc: off: %u whence: %d \n", (uint)off, whence)); + dump_state(state); + switch (whence) { + case 0: + state->loc = off; + break; + case 1: + state->loc += off; + break; + case 2: + state->loc = state->eof + off; + break; + } + dump_state(state); + return state->loc; +} + +int _tiffCloseProc(thandle_t hdata) { + TIFFSTATE *state = (TIFFSTATE *)hdata; + + TRACE(("_tiffCloseProc \n")); + dump_state(state); + + return 0; +} + + +toff_t _tiffSizeProc(thandle_t hdata) { + TIFFSTATE *state = (TIFFSTATE *)hdata; + + TRACE(("_tiffSizeProc \n")); + dump_state(state); + + return (toff_t)state->size; +} +int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) { + TIFFSTATE *state = (TIFFSTATE *)hdata; + + TRACE(("_tiffMapProc input size: %u, data: %p\n", (uint)*psize, *pbase)); + dump_state(state); + + *pbase = state->data; + *psize = state->size; + TRACE(("_tiffMapProc returning size: %u, data: %p\n", (uint)*psize, *pbase)); + return (1); +} + +int _tiffNullMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) { + (void) hdata; (void) pbase; (void) psize; + return (0); +} + +void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { + TRACE(("_tiffUnMapProc\n")); + (void) hdata; (void) base; (void) size; +} + +int ImagingLibTiffInit(ImagingCodecState state, int compression, int fp) { + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + + TRACE(("initing libtiff\n")); + TRACE(("Compression: %d, filepointer: %d \n", compression, 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, + state->xoff, state->yoff)); + TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); + TRACE(("State: context %p \n", state->context)); + + clientstate->loc = 0; + clientstate->size = 0; + clientstate->data = 0; + clientstate->fp = fp; + clientstate->eof = 0; + + return 1; +} + +int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) { + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + char *filename = "tempfile.tif"; + char *mode = "r"; + TIFF *tiff; + int size; + + + /* buffer is the encoded file, bytes is the length of the encoded file */ + /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ + + TRACE(("in decoder: bytes %d\n", bytes)); + 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, + state->xoff, state->yoff)); + TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); + TRACE(("Buffer: %p: %c%c%c%c\n", buffer, (char)buffer[0], (char)buffer[1],(char)buffer[2], (char)buffer[3])); + TRACE(("State->Buffer: %c%c%c%c\n", (char)state->buffer[0], (char)state->buffer[1],(char)state->buffer[2], (char)state->buffer[3])); + TRACE(("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", + im->mode, im->type, im->bands, im->xsize, im->ysize)); + TRACE(("Image: image8 %p, image32 %p, image %p, block %p \n", + im->image8, im->image32, im->image, im->block)); + TRACE(("Image: pixelsize: %d, linesize %d \n", + im->pixelsize, im->linesize)); + + dump_state(clientstate); + clientstate->size = bytes; + clientstate->eof = clientstate->size; + clientstate->loc = 0; + clientstate->data = (tdata_t)buffer; + clientstate->flrealloc = 0; + + dump_state(clientstate); + if (clientstate->fp) { + TRACE(("Opening using fd: %d\n",clientstate->fp)); + tiff = TIFFFdOpen(clientstate->fp, filename, mode); + } else { + TRACE(("Opening from string\n")); + tiff = TIFFClientOpen(filename, mode, + (thandle_t) clientstate, + _tiffReadProc, _tiffWriteProc, + _tiffSeekProc, _tiffCloseProc, _tiffSizeProc, + _tiffMapProc, _tiffUnmapProc); + } + + if (!tiff){ + TRACE(("Error, didn't get the tiff\n")); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + size = TIFFScanlineSize(tiff); + TRACE(("ScanlineSize: %d \n", size)); + if (size > state->bytes) { + TRACE(("Error, scanline size > buffer size\n")); + state->errcode = IMAGING_CODEC_BROKEN; + TIFFClose(tiff); + return -1; + } + + // Have to do this row by row and shove stuff into the buffer that way, + // with shuffle. (or, just alloc a buffer myself, then figure out how to get it + // back in. Can't use read encoded stripe. + + // This thing pretty much requires that I have the whole image in one shot. + // Prehaps a stub version would work better??? + while(state->y < state->ysize){ + if (TIFFReadScanline(tiff, (tdata_t)state->buffer, (uint32)state->y, 0) == -1) { + TRACE(("Decode Error, row %d\n", state->y)); + state->errcode = IMAGING_CODEC_BROKEN; + TIFFClose(tiff); + return -1; + } + /* TRACE(("Decoded row %d \n", state->y)); */ + state->shuffle((UINT8*) im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer, + state->xsize); + + state->y++; + } + + TIFFClose(tiff); + TRACE(("Done Decoding, Returning \n")); + // Returning -1 here to force ImageFile.load to break, rather than + // even think about looping back around. + return -1; +} + +int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { + // Open the FD or the pointer as a tiff file, for writing. + // We may have to do some monkeying around to make this really work. + // If we have a fp, then we're good. + // If we have a memory string, we're probably going to have to malloc, then + // shuffle bytes into the writescanline process. + // Going to have to deal with the directory as well. + + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + int bufsize = 64*1024; + char *mode = "w"; + + TRACE(("initing libtiff\n")); + TRACE(("Filename %s, filepointer: %d \n", filename, 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, + state->xoff, state->yoff)); + TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); + TRACE(("State: context %p \n", state->context)); + + clientstate->loc = 0; + clientstate->size = 0; + clientstate->eof =0; + clientstate->data = 0; + clientstate->flrealloc = 0; + clientstate->fp = fp; + + state->state = 0; + + if (fp) { + TRACE(("Opening using fd: %d for writing \n",clientstate->fp)); + clientstate->tiff = TIFFFdOpen(clientstate->fp, filename, mode); + } else { + // malloc a buffer to write the tif, we're going to need to realloc or something if we need bigger. + TRACE(("Opening a buffer for writing \n")); + clientstate->data = malloc(bufsize); + clientstate->size = bufsize; + clientstate->flrealloc=1; + + if (!clientstate->data) { + TRACE(("Error, couldn't allocate a buffer of size %d\n", bufsize)); + return 0; + } + + clientstate->tiff = TIFFClientOpen(filename, mode, + (thandle_t) clientstate, + _tiffReadProc, _tiffWriteProc, + _tiffSeekProc, _tiffCloseProc, _tiffSizeProc, + _tiffNullMapProc, _tiffUnmapProc); /*force no mmap*/ + + } + + if (!clientstate->tiff) { + TRACE(("Error, couldn't open tiff file\n")); + return 0; + } + + return 1; + +} + +int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...){ + // after tif_dir.c->TIFFSetField. + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + va_list ap; + int status; + + va_start(ap, tag); + status = TIFFVSetField(clientstate->tiff, tag, ap); + va_end(ap); + return status; +} + + +int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) { + /* One shot encoder. Encode everything to the tiff in the clientstate. + If we're running off of a FD, then run once, we're good, everything + ends up in the file, we close and we're done. + + If we're going to memory, then we need to write the whole file into memory, then + parcel it back out to the pystring buffer bytes at a time. + + */ + + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + TIFF *tiff = clientstate->tiff; + + TRACE(("in encoder: bytes %d\n", bytes)); + 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, + state->xoff, state->yoff)); + TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); + TRACE(("Buffer: %p: %c%c%c%c\n", buffer, (char)buffer[0], (char)buffer[1],(char)buffer[2], (char)buffer[3])); + TRACE(("State->Buffer: %c%c%c%c\n", (char)state->buffer[0], (char)state->buffer[1],(char)state->buffer[2], (char)state->buffer[3])); + TRACE(("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", + im->mode, im->type, im->bands, im->xsize, im->ysize)); + TRACE(("Image: image8 %p, image32 %p, image %p, block %p \n", + im->image8, im->image32, im->image, im->block)); + TRACE(("Image: pixelsize: %d, linesize %d \n", + im->pixelsize, im->linesize)); + + dump_state(clientstate); + + if (state->state == 0) { + TRACE(("Encoding line bt line")); + while(state->y < state->ysize){ + state->shuffle(state->buffer, + (UINT8*) im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); + + if (TIFFWriteScanline(tiff, (tdata_t)(state->buffer), (uint32)state->y, 0) == -1) { + TRACE(("Encode Error, row %d\n", state->y)); + state->errcode = IMAGING_CODEC_BROKEN; + TIFFClose(tiff); + if (!clientstate->fp){ + free(clientstate->data); + } + return -1; + } + state->y++; + } + + if (state->y == state->ysize) { + state->state=1; + + TRACE(("Flushing \n")); + if (!TIFFFlush(tiff)) { + TRACE(("Error flushing the tiff")); + // likely reason is memory. + state->errcode = IMAGING_CODEC_MEMORY; + TIFFClose(tiff); + if (!clientstate->fp){ + free(clientstate->data); + } + return -1; + } + TRACE(("Closing \n")); + TIFFClose(tiff); + // reset the clientstate metadata to use it to read out the buffer. + clientstate->loc = 0; + clientstate->size = clientstate->eof; // redundant? + } + } + + if (state->state == 1 && !clientstate->fp) { + int read = (int)_tiffReadProc(clientstate, (tdata_t)buffer, (tsize_t)bytes); + TRACE(("Buffer: %p: %c%c%c%c\n", buffer, (char)buffer[0], (char)buffer[1],(char)buffer[2], (char)buffer[3])); + if (clientstate->loc == clientstate->eof) { + TRACE(("Hit EOF, calling an end, freeing data")); + state->errcode = IMAGING_CODEC_END; + free(clientstate->data); + } + return read; + } + + state->errcode = IMAGING_CODEC_END; + return 0; +} +#endif diff --git a/selftest.py b/selftest.py index 01c78a3ff..b0c7d7d30 100644 --- a/selftest.py +++ b/selftest.py @@ -186,6 +186,7 @@ if __name__ == "__main__": check_module("TKINTER", "_imagingtk") check_codec("JPEG", "jpeg") check_codec("ZLIB (PNG/ZIP)", "zip") + check_codec("G4 TIFF", "group4") check_module("FREETYPE2", "_imagingft") check_module("LITTLECMS", "_imagingcms") print("-"*68) @@ -201,3 +202,4 @@ if __name__ == "__main__": print("--- %s tests passed." % status[1]) sys.exit(exit_status) + diff --git a/setup.py b/setup.py index e28fdb4f8..3bfef2061 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ _LIB_IMAGING = ( "QuantHeap", "PcdDecode", "PcxDecode", "PcxEncode", "Point", "RankFilter", "RawDecode", "RawEncode", "Storage", "SunRleDecode", "TgaRleDecode", "Unpack", "UnpackYCC", "UnsharpMask", "XbmDecode", - "XbmEncode", "ZipDecode", "ZipEncode") + "XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode") def _add_directory(path, dir, where=None): @@ -76,7 +76,7 @@ PIL_VERSION = '1.1.7' TCL_ROOT = None JPEG_ROOT = None ZLIB_ROOT = None -TIFF_ROOT = None +TIFF_ROOT = None FREETYPE_ROOT = None LCMS_ROOT = None @@ -156,6 +156,7 @@ class pil_build_ext(build_ext): # # locate tkinter libraries + if _tkinter: TCL_VERSION = _tkinter.TCL_VERSION[:3] @@ -183,6 +184,7 @@ class pil_build_ext(build_ext): break else: TCL_ROOT = None + # # add standard directories @@ -229,6 +231,10 @@ class pil_build_ext(build_ext): if _find_library_file(self, "tiff"): feature.tiff = "tiff" + if sys.platform == "win32" and _find_library_file(self, "libtiff"): + feature.tiff = "libtiff" + if sys.platform == "darwin" and _find_library_file(self, "libtiff"): + feature.tiff = "libtiff" if _find_library_file(self, "freetype"): # look for freetype2 include files @@ -288,6 +294,9 @@ class pil_build_ext(build_ext): if feature.zlib: libs.append(feature.zlib) defs.append(("HAVE_LIBZ", None)) + if feature.tiff: + libs.append(feature.tiff) + defs.append(("HAVE_LIBTIFF", None)) if sys.platform == "win32": libs.extend(["kernel32", "user32", "gdi32"]) if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1: @@ -382,7 +391,7 @@ class pil_build_ext(build_ext): (feature.tcl and feature.tk, "TKINTER"), (feature.jpeg, "JPEG"), (feature.zlib, "ZLIB (PNG/ZIP)"), - # (feature.tiff, "experimental TIFF G3/G4 read"), + (feature.tiff, "experimental TIFF G3/G4 read"), (feature.freetype, "FREETYPE2"), (feature.lcms, "LITTLECMS"), (feature.webp, "WEBP"),