libtiff: インデックスカラーのTIFFファイルの読み込み

TIFFTAG_PHOTOMETRICの値が3ならばインデックスカラーファイルです。
この場合、TIFFTAG_COLORMAPが書き込まれていないファイルは無効になります。

インデックスカラーのTIFFファイルでも、画像部分は普通に読み込むことが出来ます。
問題はパレットの扱いです。

TIFFTAG_COLORMAPにはパレットが書き込まれているわけですが、
パレットの数は2^BitsPerSampleと決まっているようです。8bitなら256個のパレットが書き込まれています。
なので、たとえば4色しか使用していなくても必ず256個のパレットが書き込まれています。
順番はRGBの順番で書き込まれていることとなっているようです。

また、パレットは0~65535のうちの任意の値が使用できることになっているようです。
つまり、RGBの値が全て65535のときに白と定義されています。
8bitに変換したいときは255/65535をかけなければいけないわけです。

とはいうものの、この仕様に従っていない場合も多いようで、libtiffに付属のpal2rgbのソースコードでは
パレットが8bitか16bitかを確認する関数が用意されていました。

以下の例ではカラーテーブルを読み込んだ後にインデックスカラーの画像を読み込み、RGBの値に変換する例です。
いくつかの関数でlibtiffに付属のpal2rgb.cを参考にしています。
TIFFGetField()関数でTIFFTAG_COLORMAPを読み込むときに渡す引数に注意してください。

#include <windows.h>
#include <tiffio.h>

// 16bit → 8bit深度への変換
#define  CVT(x)    (((x) * 255) / ((1L<<16)-1))

// パレットが16bit深度のものかどうか調べる
static int checkcmap(int n, WORD *r, WORD *g, WORD *b)
{
  while (n-- > 0)
    if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256)
    return (16);

  return (8);
}

bool ReadTIFF( LPCTSTR lpszFName )
{
  int  nImageLength;
  int  nImageWidth;
  int  nBitsPerSample;
  int  nSamplePerPixel;
  int  nPhotometric;
  int  nRowsPerStrip;
  int i;
  int nRow;
  TIFF *pTif;
  LPBYTE pBuf;
  WORD *pwPalR, *pwPalG, *pwPalB; // カラーテーブルを保持するポインタ

  pTif = TIFFOpen( lpszFName, "r" );

  if (!pTif) return false;
  
  //横
  if ( TIFFGetField( pTif, TIFFTAG_IMAGEWIDTH,      &nImageWidth      ) != 1 )
    return false;
  //縦
  if ( TIFFGetField( pTif, TIFFTAG_IMAGELENGTH,     &nImageLength     ) != 1 )
    return false;
  //色深度のビット数
  if ( TIFFGetField( pTif, TIFFTAG_BITSPERSAMPLE,   &nBitsPerSample   ) != 1 )
    return false;
  //色数  
  if ( TIFFGetField( pTif, TIFFTAG_SAMPLESPERPIXEL,  &nSamplePerPixel ) != 1 )
    return false;
  //ストリップ1つあたりの行数 エラーの場合はデフォルト値
  if ( TIFFGetField( pTif, TIFFTAG_ROWSPERSTRIP,    &nRowsPerStrip    ) != 1 )
    nRowsPerStrip = 1;
  //画像の種別
  if ( TIFFGetField( pTif, TIFFTAG_PHOTOMETRIC,     &nPhotometric     ) != 1 )
    return false;

  if ( nPhotometric == 3 ) // インデックスカラーならば
  {
    // カラーマップは必須
    if ( TIFFGetField( pTif, TIFFTAG_COLORMAP, &pwPalR, &pwPalG, &pwPalB ) != 1 )
      return false;    
    if ( checkcmap( 1<<nBitsPerSample, pwPalR, pwPalG, pwPalB ) == 16 )
    {
      for ( int i = (1<<nBitsPerSample)-1; i >= 0; i--)
      {
        pwPalR[i] = CVT( pwPalR[i] );
        pwPalG[i] = CVT( pwPalG[i] );
        pwPalB[i] = CVT( pwPalB[i] );
      }
    }
  }

  pBuf = new BYTE[nImageWidth*nSamplePerPixel*nRowsPerStrip];

  for ( i = 0; i < nImageLength; i += nRowsPerStrip )
  {
    /* ストリップの行数を計算 */
    nRow = (i + nRowsPerStrip > nImageLength) ? nImageLength - i : nRowsPerStrip;
    
    /* バッファに1ストリップ分読み込み */
    if ( TIFFReadEncodedStrip( pTif, TIFFComputeStrip( pTif, i, 0 ),
         pBuf, nRow*nImageWidth*nSamplePerPixel ) == -1 )
    {
      fprintf( stderr, "errorn" );
      delete[] pBuf;
      return false;
    }
    else
    {
      /* バッファに入っているピクセルに対する処理 */
      /* パレットからRGBに変換するならこんなかんじ
      
        r = pwPalR[pBuf[x]];
        g = pwPalG[pBuf[x]];
        b = pwPalB[pBuf[x]];
      */
    }
  }
  
  delete[] pBuf;
  TIFFClose( pTif );
  
  return true;
}
アーカイブ