initial merge of wiredfool/Python-Imaging-Library-G4-Tiff-Support

This commit is contained in:
wiredfool 2013-03-08 19:51:59 -08:00
parent e782fe721e
commit 5ba6564e26
7 changed files with 404 additions and 21 deletions

View File

@ -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,6 +535,11 @@ class TiffImageFile(ImageFile.ImageFile):
self.__frame = -1
self.__fp = self.fp
if Image.DEBUG:
print "*** TiffImageFile._open ***"
print "- __first:", self.__first
print "- ifh: ", ifh
# and load the first frame
self._seek(0)
@ -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,16 +734,49 @@ 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]
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, 0, w, ysize),
0, a))
a = None
for o in self.tag[STRIPOFFSETS]:
if not a:
a = self._decoder(rawmode, l)
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)),
o, a))
offsets[i], a))
print "tiles: %s" % self.tile
y = y + h
if y >= self.size[1]:
x = y = 0
@ -764,8 +862,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,8 +944,60 @@ 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
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())
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, [

View File

@ -3205,6 +3205,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);
@ -3223,6 +3224,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
@ -3272,6 +3274,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},

View File

@ -388,6 +388,75 @@ PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args)
return (PyObject*) decoder;
}
/* -------------------------------------------------------------------- */
/* LibTiff */
/* -------------------------------------------------------------------- */
#ifdef HAVE_LIBTIFF
#include "Tiff.h"
#include <string.h>
#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 */

130
encode.c
View File

@ -644,3 +644,133 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
}
#endif
/* -------------------------------------------------------------------- */
/* LibTiff */
/* -------------------------------------------------------------------- */
#ifdef HAVE_LIBTIFF
#include "Tiff.h"
#include <string.h>
#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;
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;
}
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_raw_16") == 0) {
compression = COMPRESSION_CCITTRLEW;
} else {
PyErr_SetString(PyExc_ValueError, "unknown compession");
return NULL;
}
encoder = PyImaging_EncoderNew(sizeof(TIFFSTATE));
if (encoder == NULL)
return NULL;
if (get_packer(encoder, mode, rawmode) < 0)
return NULL;
if (! ImagingLibTiffEncodeInit(&encoder->state, filename, fp)) {
Py_DECREF(encoder);
PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
return NULL;
}
while (PyDict_Next(dir, &pos, &key, &value)) {
status = 0;
if (PyInt_Check(value)) {
TRACE(("Setting from Int: %d %ld \n", (int)PyInt_AsLong(key),PyInt_AsLong(value)));
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) PyInt_AsLong(key),
PyInt_AsLong(value));
} else if(PyString_Check(value)) {
TRACE(("Setting from String: %d, %s \n", (int)PyInt_AsLong(key),PyString_AsString(value)));
status = ImagingLibTiffSetField(&encoder->state,
(ttag_t) PyInt_AsLong(key),
PyString_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;i<len;i++) {
floatav[i] = (float)PyFloat_AsDouble(PyList_GetItem(value,i));
}
status = ImagingLibTiffSetField(&encoder->state,
(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),
PyString_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

View File

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

View File

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

View File

@ -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):
@ -156,6 +156,7 @@ class pil_build_ext(build_ext):
#
# locate tkinter libraries
if _tkinter:
TCL_VERSION = _tkinter.TCL_VERSION[:3]
@ -184,6 +185,7 @@ class pil_build_ext(build_ext):
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
@ -284,6 +290,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:
@ -373,7 +382,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"),
]