[Image-SIG] PATCH: Group 4 TIFF compression support

Vladimir Pastukhov vpastukhov at naumen.ru
Tue Jul 29 22:32:07 EDT 2003


Hello,

Enclosed patch for PIL 1.1.4 enables reading files encoded with
group3, group4, tiff_ccitt and tiff_raw_16 TIFF compression schemes.
Internally it uses libtiff (www.libtiff.org) for decompression.

The following additional steps are required to build PIL with
libtiff:

1. Install libtiff library version 3.5.7.

2. For PIL compilation you will need a full set of libtiff
    include files, whereas only a subset of them gets installed.
    Get the libtiff source and run configure to prepare headers
    in libtiff subdirectory.

3. Adjust paths to libtiff header files and library in setup.py
    and libImaging/Makefile.

4. Define HAVE_LIBTIFF in libImaging/ImConfig.h

The code has been tested so far only with a limited set of
group4-encoded images.  Feedback and bug reports are welcome.

Best regards,
-- 
Vladimir Pastukhov <vpastukhov at naumen.ru>

-------------- next part --------------
--- ./PIL/TiffImagePlugin.py.orig	Wed May  7 02:44:17 2003
+++ ./PIL/TiffImagePlugin.py	Tue Jul 29 18:35:36 2003
@@ -65,6 +65,7 @@
 BITSPERSAMPLE = 258
 COMPRESSION = 259
 PHOTOMETRIC_INTERPRETATION = 262
+FILLORDER = 266
 IMAGEDESCRIPTION = 270
 STRIPOFFSETS = 273
 SAMPLESPERPIXEL = 277
@@ -451,7 +452,7 @@
 
         return self.__frame
 
-    def _decoder(self, rawmode, layer):
+    def _decoder(self, rawmode, layer, tile=None):
         "Setup decoder contexts"
 
         args = None
@@ -459,6 +460,13 @@
             rawmode = rawmode[layer]
         if self._compression == "raw":
             args = (rawmode, 0, 1)
+        if self._compression in ["tiff_ccitt", "group3", "group4", "tiff_raw_16"]:
+            args = (rawmode,
+                    self._compression,
+                    (self.tag.has_key(FILLORDER)) \
+                        and self.tag[FILLORDER][0] or -1,
+                    (tile is not None and self.tag.has_key(STRIPBYTECOUNTS)) \
+                        and self.tag[STRIPBYTECOUNTS][tile] or -1)
         if self._compression in ["packbits", "tiff_lzw", "jpeg"]:
             args = rawmode
             if self._compression == "jpeg" and self.tag.has_key(JPEGTABLES):
@@ -525,16 +533,15 @@
         self.tile = []
         if self.tag.has_key(STRIPOFFSETS):
             # 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)
+            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))
                 y = y + h
                 if y >= self.size[1]:
                     x = y = 0
--- ./Setup.in.orig	Tue Apr 22 22:11:25 2003
+++ ./Setup.in	Fri Jul 25 16:18:52 2003
@@ -32,6 +32,9 @@
 # *** IJG JPEG library (libjpeg) location
 	-I/usr/local/include -L/usr/local/lib -ljpeg \
 #
+# *** TIFF library (libtiff) location
+	-I../../kits/tifflib -ltiff \
+#
 # *** ZLIB (libz) location
 	-I/usr/local/include -L/usr/local/lib -lz
 
--- ./_imaging.c.orig	Sat Apr 26 17:50:24 2003
+++ ./_imaging.c	Fri Jul 25 17:48:02 2003
@@ -2658,6 +2658,7 @@
 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);
@@ -2719,6 +2720,12 @@
     {"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},
+#endif
     {"msp_decoder", (PyCFunction)PyImaging_MspDecoderNew, 1},
     {"packbits_decoder", (PyCFunction)PyImaging_PackbitsDecoderNew, 1},
     {"pcd_decoder", (PyCFunction)PyImaging_PcdDecoderNew, 1},
--- ./decode.c.orig	Tue Apr 22 22:11:26 2003
+++ ./decode.c	Tue Jul 29 19:59:17 2003
@@ -52,6 +52,7 @@
     PyObject_HEAD
     int (*decode)(Imaging im, ImagingCodecState state,
 		  UINT8* buffer, int bytes);
+    void (*cleanup)(ImagingCodecState state);
     struct ImagingCodecStateInstance state;
     Imaging im;
     PyObject* lock;
@@ -87,6 +88,7 @@
 
     /* Initialize decoder context */
     decoder->state.context = context;
+    decoder->cleanup = NULL;
 
     /* Target image */
     decoder->lock = NULL;
@@ -98,6 +100,8 @@
 static void
 _dealloc(ImagingDecoderObject* decoder)
 {
+    if (decoder->cleanup)
+    	decoder->cleanup(&decoder->state);
     free(decoder->state.buffer);
     free(decoder->state.context);
     Py_XDECREF(decoder->lock);
@@ -381,6 +385,80 @@
     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 fillorder = -1;
+    int count = -1;
+
+    if (! PyArg_ParseTuple(args, "sss|ii", &mode, &rawmode, &compname, &fillorder, &count))
+	return NULL;
+
+    TRACE(("new decoder %s, fillorder %d, %d bytes\n", compname, fillorder, count));
+
+    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;
+    }
+
+    if (fillorder < 0) {
+	fillorder = FILLORDER_MSB2LSB;
+
+    } else if (fillorder != FILLORDER_MSB2LSB && fillorder != FILLORDER_LSB2MSB) {
+	PyErr_SetString(PyExc_ValueError, "invalid fillorder");
+	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, fillorder, count)) {
+	Py_DECREF(decoder);
+	PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
+	return NULL;
+    }
+
+    decoder->decode  = ImagingLibTiffDecode;
+    decoder->cleanup = ImagingLibTiffCleanup;
+
+    return (PyObject*) decoder;
+}
+
+#endif
 
 /* -------------------------------------------------------------------- */
 /* MSP									*/
--- ./libImaging/ImConfig.h.in.orig	Tue Apr 22 22:11:26 2003
+++ ./libImaging/ImConfig.h.in	Fri Jul 25 15:12:29 2003
@@ -45,6 +45,9 @@
 /* Define if you have the IJG jpeg library (-ljpeg).  */
 #undef HAVE_LIBJPEG
 
+/* Define if you have the tiff library (-ltiff).  */
+#undef HAVE_LIBTIFF
+
 /* Define if you have the Greg Ward's mpeg library (-lmpeg).  */
 #undef HAVE_LIBMPEG
 
--- ./libImaging/ImConfig.h.win.orig	Tue Apr 22 22:11:26 2003
+++ ./libImaging/ImConfig.h.win	Fri Jul 25 15:13:38 2003
@@ -13,6 +13,9 @@
 /* Define if you have the IJG jpeg library (-ljpeg).  */
 #define HAVE_LIBJPEG
 
+/* Define if you have the tiff library (-ltiff).  */
+#define HAVE_LIBTIFF
+
 /* Define if you have the Greg Ward's mpeg library (-lmpeg).  */
 #undef HAVE_LIBMPEG
 
--- ./libImaging/Imaging.h.orig	Tue Apr 22 22:56:24 2003
+++ ./libImaging/Imaging.h	Fri Jul 25 15:29:59 2003
@@ -389,6 +389,10 @@
 #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);
+#endif
 #ifdef	HAVE_LIBMPEG
 extern int ImagingMpegDecode(Imaging im, ImagingCodecState state,
 			     UINT8* buffer, int bytes);
--- ./libImaging/Makefile.in.orig	Tue Apr 22 22:11:26 2003
+++ ./libImaging/Makefile.in	Fri Jul 25 16:18:22 2003
@@ -31,9 +31,10 @@
 
 INCLDIR=	$(srcdir)/.
 JPEGINCLUDE=	/usr/local/include
+TIFFINCLUDE=	../../../kits/tifflib
 OPT=		@OPT@
 #OPT=		-g
-CFLAGS=		$(OPT) -I$(INCLDIR) -I$(JPEGINCLUDE) $(DEFS)
+CFLAGS=		$(OPT) -I$(INCLDIR) -I$(JPEGINCLUDE) -I$(TIFFINCLUDE) $(DEFS)
 
 MKDEP=		mkdep
 SHELL=		/bin/sh
@@ -71,6 +72,7 @@
 		RawDecode.o RawEncode.o \
 		SunRleDecode.o \
 		TgaRleDecode.o \
+		TiffDecode.o \
 		XbmDecode.o XbmEncode.o \
 		ZipDecode.o ZipEncode.o
 
--- ./libImaging/Makefile.win.orig	Tue Apr 22 22:11:26 2003
+++ ./libImaging/Makefile.win	Thu Jul 24 22:19:22 2003
@@ -76,6 +76,7 @@
 		Storage.obj\
 		SunRleDecode.obj\
 		TgaRleDecode.obj\
+		TiffDecode.obj\
 		Unpack.obj\
 		UnpackYCC.obj\
 		XbmDecode.obj\
--- ./libImaging/Tiff.h.orig	Fri Jul 25 15:30:53 2003
+++ ./libImaging/Tiff.h	Tue Jul 29 19:06:40 2003
@@ -0,0 +1,27 @@
+/*
+ * The Python Imaging Library.
+ * $Id: //modules/pil/libImaging/Tiff.h#1 $
+ *
+ * declarations for the LibTiff-based Group3 and Group4 decoder
+ *
+ */
+
+#include <tiffiop.h>
+
+typedef struct {
+
+    /* PRIVATE CONTEXT (set by decoder) */
+    TIFF tiff;
+    int count;
+
+} TIFFSTATE;
+
+extern int  ImagingLibTiffInit(ImagingCodecState state, int compression, int fillorder, int count);
+extern void ImagingLibTiffCleanup(ImagingCodecState state);
+
+/*
+#define VA_ARGS(...)	__VA_ARGS__
+#define TRACE(args)    fprintf(stderr, VA_ARGS args)
+*/
+
+#define TRACE(args)
--- ./libImaging/TiffDecode.c.orig	Fri Jul 25 15:30:58 2003
+++ ./libImaging/TiffDecode.c	Tue Jul 29 19:06:48 2003
@@ -0,0 +1,185 @@
+/*
+ * The Python Imaging Library.
+ * $Id: //modules/pil/libImaging/TiffDecode.c#1 $
+ *
+ * LibTiff-based Group3 and Group4 decoder
+ *
+ */
+
+#include "Imaging.h"
+
+#ifdef HAVE_LIBTIFF
+
+#undef INT32
+#undef UINT32
+
+#include "Tiff.h"
+
+#include <stddef.h>
+
+#include <tiffio.h>
+#include <tiffiop.h>
+
+extern void _TIFFSetDefaultCompressionState(TIFF* tif);
+
+typedef	void (*TIFFFaxFillFunc)(unsigned char*, uint32*, uint32*, uint32);
+
+typedef struct {
+	int     rw_mode;                /* O_RDONLY for decode, else encode */
+	int	mode;			/* operating mode */
+	uint32	rowbytes;		/* bytes in a decoded scanline */
+	uint32	rowpixels;		/* pixels in a scanline */
+
+	uint16	cleanfaxdata;		/* CleanFaxData tag */
+	uint32	badfaxrun;		/* BadFaxRun tag */
+	uint32	badfaxlines;		/* BadFaxLines tag */
+	uint32	groupoptions;		/* Group 3/4 options tag */
+	uint32	recvparams;		/* encoded Class 2 session params */
+	char*	subaddress;		/* subaddress string */
+	uint32	recvtime;		/* time spent receiving (secs) */
+	TIFFVGetMethod vgetparent;	/* super-class method */
+	TIFFVSetMethod vsetparent;	/* super-class method */
+} Fax3BaseState;
+
+typedef struct {
+	Fax3BaseState b;
+	const u_char* bitmap;		/* bit reversal table */
+	uint32	data;			/* current i/o byte/word */
+	int	bit;			/* current i/o bit in byte */
+	int	EOLcnt;			/* count of EOL codes recognized */
+	TIFFFaxFillFunc fill;		/* fill routine */
+	uint32*	runs;			/* b&w runs for current/previous row */
+	uint32*	refruns;		/* runs for reference line */
+	uint32*	curruns;		/* runs for current line */
+} Fax3DecodeState;
+
+
+#define DECODER_STATE(tiff) (UINT8 *)(tiff->tif_data + offsetof(Fax3DecodeState, bitmap))
+#define DECODER_STATE_SIZE  (sizeof(Fax3DecodeState) - offsetof(Fax3DecodeState, bitmap))
+
+
+static void
+_errorHandler(const char* module, const char* fmt, va_list ap)
+{
+    /* be quiet */
+}
+
+static void
+_cleanupLibTiff( TIFF *tiff )
+{
+    if (tiff->tif_cleanup)
+	tiff->tif_cleanup(tiff);
+
+    if (tiff->tif_fieldinfo)
+	free(tiff->tif_fieldinfo);
+}
+
+int
+ImagingLibTiffInit(ImagingCodecState state, int compression, int fillorder, int count)
+{
+    TIFF *tiff = &((TIFFSTATE *) state->context)->tiff;
+    const TIFFCodec *codec;
+
+    TIFFSetErrorHandler(_errorHandler);
+    TIFFSetWarningHandler(_errorHandler);
+
+    codec = TIFFFindCODEC((uint16) compression);
+    if (codec == NULL)
+	return 0;
+
+    _TIFFmemset(tiff, 0, sizeof (*tiff));
+    _TIFFSetDefaultCompressionState(tiff);
+    TIFFDefaultDirectory(tiff);
+
+    tiff->tif_mode = O_RDONLY;
+    tiff->tif_dir.td_fillorder = (uint16) fillorder;
+    tiff->tif_dir.td_compression = (uint16) compression;
+
+    ((TIFFSTATE *) state->context)->count = count;
+
+    if (! codec->init(tiff, 0)) {
+	_cleanupLibTiff(tiff);
+	return 0;
+    }
+
+    return 1;
+}
+
+void
+ImagingLibTiffCleanup(ImagingCodecState state)
+{
+    _cleanupLibTiff( &((TIFFSTATE *) state->context)->tiff );
+}
+
+int
+ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes)
+{
+    TIFF *tiff = &((TIFFSTATE *) state->context)->tiff;
+    int count  = ((TIFFSTATE *) state->context)->count;
+    int savecc;
+    UINT8 saveds[DECODER_STATE_SIZE];
+
+    if (bytes < count)
+	return 0;
+
+    if ((tiff->tif_flags & TIFF_CODERSETUP) == 0) {
+	tiff->tif_dir.td_bitspersample = 1;
+	tiff->tif_dir.td_imagewidth = state->xsize;
+	tiff->tif_scanlinesize = TIFFScanlineSize(tiff);
+
+	if (! tiff->tif_setupdecode(tiff) || ! tiff->tif_predecode(tiff, 0)) {
+	    state->errcode = IMAGING_CODEC_BROKEN;
+	    return -1;
+	}
+
+	tiff->tif_flags |= TIFF_CODERSETUP;
+    }
+
+    tiff->tif_row   = state->y + state->yoff;
+    tiff->tif_rawcp = buffer;
+    tiff->tif_rawcc = bytes;
+
+    TRACE(("decoding %d bytes, row %d\n", bytes, tiff->tif_row));
+    for (;;) {
+	if (count < 0) {
+	    savecc = tiff->tif_rawcc;
+	    memcpy(saveds, DECODER_STATE(tiff), DECODER_STATE_SIZE);
+	}
+
+	if (tiff->tif_decoderow(tiff, (tidata_t) state->buffer,
+				tiff->tif_scanlinesize, 0) < 0) {
+	    TRACE(("decode error, %d bytes left\n", tiff->tif_rawcc));
+	    if (count < 0)
+		break;
+	    state->errcode = IMAGING_CODEC_BROKEN;
+	    return -1;
+	}
+
+	if (count < 0 && tiff->tif_rawcc <= 0) {
+	    TRACE(("not enough data\n"));
+	    break;
+	}
+
+	tiff->tif_postdecode(tiff, (tidata_t) buffer, tiff->tif_scanlinesize);
+
+	state->shuffle((UINT8*) im->image[state->y + state->yoff] +
+		       state->xoff * im->pixelsize, state->buffer,
+		       state->xsize);
+
+	TRACE(("decoded row %d, %d bytes left\n", tiff->tif_row, tiff->tif_rawcc));
+	tiff->tif_row++;
+	state->y++;
+
+	if (state->y >= state->ysize) {
+	    TRACE(("decoded %d rows\n", state->y));
+	    return -1; /* errcode = 0 */
+	}
+    }
+
+    memcpy(DECODER_STATE(tiff), saveds, DECODER_STATE_SIZE);
+
+    TRACE(("decoded %d rows, %d bytes left\n", state->y, savecc));
+    return bytes - savecc;
+}
+
+#endif
--- ./setup.py.orig	Fri May  9 18:00:56 2003
+++ ./setup.py	Fri Jul 25 16:18:34 2003
@@ -24,6 +24,7 @@
 # on windows, the build script expects to find both library files and
 # include files in the directories below.  tweak as necessary.
 JPEGDIR = "../../kits/jpeg-6b"
+TIFFDIR = "../../kits/tifflib"
 ZLIBDIR = "../../kits/zlib-1.1.4"
 FREETYPEDIR = "../../kits/freetype-2.0"
 
@@ -72,6 +73,8 @@
         elif lib == "TIFF":
             HAVE_LIBTIFF = 1
             LIBRARIES.append("tiff")
+            INCLUDE_DIRS.append(TIFFDIR)
+            LIBRARY_DIRS.append(TIFFDIR)
         elif lib == "Z":
             HAVE_LIBZ = 1
             if sys.platform == "win32":


More information about the Image-SIG mailing list