libtiff: JPEG/TILEのTIFF読み込み方法

問題

JPEG圧縮ファイルのサポートをした状態でビルドすると、Scanline配列のJPEG圧縮であれば問題なく読めます。
しかし、TILE配列のJPEG圧縮だと、読み込み時にエラーが出まくります。
これは、TILE/JPEGのファイルでは読み込みのときにJPEGDecodeRow()にはいって、ここにイロイロと間違いがあるためのようです。
Scanline/JPEGではJPEGDecode()にはいって、この場合は問題なく動作します。

傾向と対策

TILE配列の場合でもJPEGDecode()関数で正しく読むことができます。
CODEC関係の関数の指定は読み込み時にTIFF構造体に指定してあるため、
これを変えるためにはTIFFOpen()で読み込んだ直後に、読み込みのTIFF構造体に以下のようにタグをセットしておきます。

TIFFSetField( in, TIFFTAG_JPEGcolorMODE, JPEGcolorMODE_RGB );

上のタグは2箇所で作用します。
まず、TILE/JPEGファイルをTIFFOpen()関数で開くと、
デフォルトのデコード関数としてJPEGDecode()が指定されている(tif->tif_decodetile == JPEGDecode;)のですが、
上のタグが指定されていないとJPEGPreDecode()関数内の以下の部分で、JPEGDecodeRow()に書き換えられてしまいます。

    downsampled_output = FALSE;               //デフォルトは"false"
    if (td->td_planarconfig == PLANARCONFIG_CONTIG &&
        sp->photometric == PHOTOMETRIC_YCBCR &&
        //JPEGcolorMODE_RGBがセットされていれば、以下のブロックに入る
        sp->jpegcolormode == JPEGcolorMODE_RGB) {
    /* Convert YCbCr to RGB */
        sp->cinfo.d.jpeg_color_space = JCS_YCbCr;
        //出力時のカラーマップがRGBにセットされる
        sp->cinfo.d.out_color_space = JCS_RGB;
    } else {
            /* Suppress colorspace handling */
        sp->cinfo.d.jpeg_color_space = JCS_UNKNOWN;
        sp->cinfo.d.out_color_space = JCS_UNKNOWN;
        if (td->td_planarconfig == PLANARCONFIG_CONTIG &&
            (sp->h_sampling != 1 || sp->v_sampling != 1))
            
            //JPEGcolorMODE_RGBがセットされていないと、
            //downsampled_outputがtrueになってしまう!!!
            downsampled_output = TRUE;

        /* XXX what about up-sampling? */
    }
    //downsampled_outputがtrueだと、、、
    if (downsampled_output) {
        /* Need to use raw-data interface to libjpeg */
        //やってはいけない設定が次々と行われてしまう。
        sp->cinfo.d.raw_data_out = TRUE;
        tif->tif_decoderow = JPEGDecodeRaw;
        tif->tif_decodestrip = JPEGDecodeRaw;
        tif->tif_decodetile = JPEGDecodeRaw;   //とくにここ!
    } else { 
        //downsampled_outputがfalseのままだと以下のブロックに入って問題なし!
        /* Use normal interface to libjpeg */
        sp->cinfo.d.raw_data_out = FALSE;
        tif->tif_decoderow = JPEGDecode;
        tif->tif_decodestrip = JPEGDecode;
        tif->tif_decodetile = JPEGDecode;
    }

もう1箇所は、TIFFVTileSize()関数内の以下の部分で、タイルバッファを正しく計算できるようになります。

#ifdef YCBCR_SUPPORT
    if (td->td_planarconfig == PLANARCONFIG_CONTIG &&
        td->td_photometric == PHOTOMETRIC_YCBCR &&
        !isUpSampled(tif)) { //上のタグがないと、TIFF_UPSAMPLEDのフラグが起きている
         //その結果、このブロックに入ってややこしい計算をした挙句、
         タイルサイズを間違って返す。
         ///*
         * Packed YCbCr data contain one Cb+Cr for every
         * HorizontalSampling*VerticalSampling Y values.
         * Must also roundup width and height when calculating
         * since images that are not a multiple of the
         * horizontal/vertical subsampling area include
         * YCbCr data for the extended image.
         */
         
        tsize_t w =
            TIFFroundup(td->td_tilewidth, td->td_ycbcrsubsampling[0]);
        tsize_t rowsize = TIFFhowmany(w*td->td_bitspersample, 8);
        tsize_t samplingarea =
            td->td_ycbcrsubsampling[0]*td->td_ycbcrsubsampling[1];
        nrows = TIFFroundup(nrows, td->td_ycbcrsubsampling[1]);
        /* NB: don't need TIFFhowmany here 'cuz everything is rounded */
        tilesize = nrows*rowsize + 2*(nrows*rowsize / samplingarea);
    } else
 #endif
        //正しいタイルサイズを返すのはこっち!
        tilesize = nrows * TIFFTileRowSize(tif);

YCbCrサポートをはずすとほかのところに影響が出る恐れがあります。
おそらくYCbCrというところをみるとJPEGDecodeRaw()はYCbCr変換されたあとのピクセルサイズを元に計算していると思われます。

アーカイブ