2011.06.05 12:38 "[Tiff] Questions on TIFF LZW compression", by Thomas Richter

2011.06.08 22:32 "[Tiff] Patch for tif_ojpeg version 3.9.5", by

Hello,

I have received some TIFF_OJPEG images that doesn't open in LibTIFF 3.9.5, and I found that after some code changes in LibTIFF it could be opened. We would like to know if anybody is interested in evaluating our changes, to see if they are useful for inclusion into LibTIFF. Description of the problems found in my TIFF_OJPEG images:

  1. JPEGInterchangeFormat doesn't point to the exact start of a JPEG marker. Instead, it points to the character "J" in the "JFIF" which is not a JPEG marker itself, but a magic number inside the APP_0 segment.
  2. On the other hand, StripOffset points to the correct JPEG marker, SOI (Start of Image, 0xFF 0xD8) as would be expected by LibTIFF.
  3. I found that LibTIFF leans toward JPEGInterchangeFormat when both tags are present. So, one of my change is to try JPEGInterchangeFormat first (to keep the same behavior as LibTIFF), and if it fails try StripOffset or TileOffset for second chance. To make it faster, I do a quick test of the first byte to see if it is 0xFF.
  4. This is implemented by splitting the OJPEGReadHeaderInfoSec function into two parts: the first part (keeping the same function name) is responsible for trying both start addresses. The second part, in a new function named OJPEGReadHeaderInfoSecStream, will take a start address and start parsing the JPEG markers.
  5. The remaining problems with my TIFF_OJPEG images are related to mismatches between what is stored in the TIFF IFD and the actual parameters that are required to decode the JPEG datastream. There is already an "OJPEG parameter correction routine" in LibTIFF, named "OJPEGSubsamplingCorrect". I added more rules to fix the issues in my images. In particular, some images have wrong BitsPerSample (e.g. 4 in a grayscale OJPEG image), wrong SamplesPerPixel (e.g. 1 in a YCbCr OJPEG image or 3 in a grayscale OJPEG image), and Photometric (using a color Photometric in a grayscale OJPEG image or vice versa)
  6. Because TIFF calculates the memory buffer sizes based on the TIFF IFD values, the "parameter correction" needs to be triggered early enough to prevent the wrong buffer size being used during decoding. For this reason, I modified "tif_dirread.c" so that for TIFF_OJPEG images, it will trigger "tif_ojpeg.c" to run the correction routine, so that the first call to "GetField" will fetch the right values.
  7. There is one more code change in OJPEGPostDecode, which allows single-strip OJPEG images (with or without YCbCr subsampling) to be decoded by TIFFReadScanline.
  8. I would like to hear any feedback you have about my code changes. If you want me to try to subdivide the patch into smaller changes, please let me know.

Thanks,
rwong_002@hotmail.com

Index: libtiff/libtiff/tif_dirread.c

=================================================================== RCS file: /cvs/maptools/cvsroot/libtiff/libtiff/tif_dirread.c,v retrieving revision 1.92.2.15

diff -r1.92.2.15 tif_dirread.c
82a83
> uint16 iv2[2];
646a648,657
> /*

>                * If there is any disagreement between the TIFF header and JPEG datastream,
>                * the values from the JPEG datastream should be used. Calling TIFFGetField
>                * will trigger the OJPEG handler to perform a cross-check, which will fix 
>                * the TIFF directory fields.
>                */

>               TIFFGetField(tif,TIFFTAG_BITSPERSAMPLE, &iv);
>               TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL, &iv);
>               TIFFGetField(tif,TIFFTAG_PHOTOMETRIC, &iv);
>               TIFFGetField(tif,TIFFTAG_YCBCRSUBSAMPLING, iv2 + 0, iv2 + 1);

Index: libtiff/libtiff/tif_ojpeg.c

=================================================================== RCS file: /cvs/maptools/cvsroot/libtiff/libtiff/tif_ojpeg.c,v retrieving revision 1.24.2.8

diff -r1.24.2.8 tif_ojpeg.c
337a338
> static int OJPEGReadHeaderInfoSecStream(TIFF* tif);
463a465,479

>               case TIFFTAG_BITSPERSAMPLE:
>                       if (sp->subsamplingcorrect_done==0)
>                               OJPEGSubsamplingCorrect(tif);
>                       return (*sp->vgetparent)(tif,tag,ap);
>                       break;
>               case TIFFTAG_SAMPLESPERPIXEL:
>                       if (sp->subsamplingcorrect_done==0)
>                               OJPEGSubsamplingCorrect(tif);
>                       return (*sp->vgetparent)(tif,tag,ap);
>                       break;
>               case TIFFTAG_PHOTOMETRIC:
>                       if (sp->subsamplingcorrect_done==0)
>                               OJPEGSubsamplingCorrect(tif);
>                       return (*sp->vgetparent)(tif,tag,ap);
>                       break;
567a584,588
>               case TIFFTAG_BITSPERSAMPLE:
>                       // TIFF IFD contains a wrong BitsPerSample value 
>                       // inconsistent with the OJPEG stream.
>                       tif->tif_dir.td_bitspersample = (uint16)8;
>                       break;
845,846c866
<       sp->write_curstrile++;
<       if (sp->write_curstrile%tif->tif_dir.td_stripsperimage==0)
---
>       if (tif->tif_row % sp->lines_per_strile == 0)
848,850c868,874
<               assert(sp->libjpeg_session_active!=0);
<               OJPEGLibjpegSessionAbort(tif);
<               sp->writeheader_done=0;
---
>               sp->write_curstrile++;
>               if (sp->write_curstrile%tif->tif_dir.td_stripsperimage==0)
>               {
>                       assert(sp->libjpeg_session_active!=0);
>                       OJPEGLibjpegSessionAbort(tif);
>                       sp->writeheader_done=0;
>               }

942a967,969
> uint16 bps;
> uint16 spp;
> int r;
944,945c971,1010

<       if ((tif->tif_dir.td_samplesperpixel!=3) || ((tif->tif_dir.td_photometric!=PHOTOMETRIC_YCBCR) &&
<           (tif->tif_dir.td_photometric!=PHOTOMETRIC_ITULAB)))
---

>       /*  
>        *  Originally, this function is intended to only handle inconsistencies in the 
>        *  YCbCrSubsampling tag. We now extend it so that it can fix several other 
>        *  inconsistencies seen in images generated by one of the library vendors.
>        *
>        *  Because of this, OJPEGReadHeaderInfoSec will be called regardless of 
>        *  the values of SamplesPerPixel or Photometric.
>        *
>        *  The OJPEGState flags "subr�