//<copyright>
//
// Copyright (c) 1995-96
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>


//<file>
//
//
// Name :       tiffimage.C
//
// Purpose :    Implementation of class TiffImage
//
// Created :    04 Jan 95    Bernhard Marschall
// Modified :   03 Aug 95    Bernhard Marschall
//
// $Id: tiffimage.C,v 1.6 1997/02/05 14:35:58 bmarsch Exp $
//
// Description:
//
//</file>
//
// $Log: tiffimage.C,v $
// Revision 1.6  1997/02/05 14:35:58  bmarsch
// Correct byte order for BSDI
//
// Revision 1.5  1996/10/02 13:30:08  bmarsch
// verbose.h was moved from hyperg to utils
//
// Revision 1.4  1996/07/25 15:54:59  bmarsch
// Byte order for FreeBSD
//
// Revision 1.3  1996/03/05 09:45:15  bmarsch
// Write warnings to callback instead of stderr
//


#include <stdlib.h>
#if defined(SUN) && (OSMajorVersion < 5)
typedef char *va_list;    // SUN4 doesn't have stdarg.h ???
#else
#include <stdarg.h>
#endif
#include <string.h>

#include "hgrastercb.h"
#include "tiffimage.h"
#include "../../iicmviews/TIFF/tiffio.h"

//#define VERBOSE
#include <hyperg/utils/verbose.h>


// ************************************************************************
// bmarsch: these static variables are needed for the C (TIFF library) to
// C++ (callback) interface

static HgRasterCallback* the_callback = nil;
static char tiffError[256];


// ************************************************************************
// TiffImpl:

typedef	unsigned char u_char;
typedef	unsigned short u_short;
typedef	unsigned int u_int;

/* bmarsch: must be 4 byte integer, but Alpha's long is 8 bytes! */
/*          (former) name u_long conflicts with sys/types.h      */
#if defined(__alpha)
typedef unsigned int uint32;
#else
typedef	unsigned long uint32;
#endif

typedef unsigned char RGBvalue;


typedef void (TiffImpl::*tileContigRoutine)(
    uint32*, const u_char*, const RGBvalue*, uint32, uint32, int, int
);

typedef void (TiffImpl::*tileSeparateRoutine)(
    uint32*, const u_char*, const u_char*, const u_char*,
    const RGBvalue*, uint32, uint32, int, int
);

class TiffImpl {
public:
  TIFF* tif_;
  uint32* raster_;
  u_short bitspersample_;
  u_short samplesperpixel_;
  u_short photometric_;
  u_short orientation_;
  u_short* redcmap_;
  u_short* greencmap_;
  u_short* bluecmap_;

  uint32** BWmap_;
  uint32** PALmap_;

  boolean gt(uint32 w, uint32 h);
  boolean gtTileContig(const RGBvalue* Map, uint32 h, uint32 w);
  boolean gtTileSeparate(const RGBvalue* Map, uint32 h, uint32 w);
  boolean gtStripContig(const RGBvalue* Map, uint32 h, uint32 w);
  boolean gtStripSeparate(const RGBvalue* Map, uint32 h, uint32 w);

  uint32 setorientation(uint32 h);
  boolean makebwmap(RGBvalue* Map);
  boolean makecmap(
    const u_short* rmap, const u_short* gmap, const u_short* bmap
  );
  
  void put8bitcmaptile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void put4bitcmaptile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void put2bitcmaptile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void put1bitcmaptile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void put1bitbwtile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void put2bitbwtile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void put4bitbwtile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void putRGBgreytile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void putRGBcontig8bittile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );
  void putRGBcontig16bittile(
    uint32* dest, const u_char* src, const RGBvalue* Map,
    uint32 w, uint32 h, int fromskew, int toskew
  );

  void putRGBseparate8bittile(
    uint32* dest,
    const u_char* red, const u_char* green, const u_char* blue,
    const RGBvalue* Map, uint32 w, uint32 h, int fromskew, int toskew
  );

  tileContigRoutine pickTileContigCase(const RGBvalue* Map);
  tileSeparateRoutine pickTileSeparateCase(const RGBvalue* Map);

  void putRGBseparate16bittile(
    uint32* dest,
    const u_char* red, const u_char* green, const u_char* blue,
    const RGBvalue* Map, uint32 w, uint32 h, int fromskew, int toskew
  );
};

// ************************************************************************
// my error handler

static void tiff_errorhandler(const char*, const char* fmt, va_list ap)
{
  vsprintf(tiffError, fmt, ap);
  if (the_callback)
    the_callback->error(tiffError);
}

// ************************************************************************

TiffImage::TiffImage(FILE* fp, const char* fname, Raster*& raster,
                     HgRasterCallback* cb, boolean dither)
: RasterImage(fp, fname, raster, cb, dither)
{
  DEBUGNL("TI::TI");
  the_callback = cb;

  impl_ = new TiffImpl();
}

TiffImage::~TiffImage()
{
  delete impl_;
}

void TiffImage::decode(int)
{
  // cannot be displayed during loading
}

// ************************************************************************

/* kandrews 22 Mar 94 */
/* to accomodate little endian order on DECstation and PC's */
#if defined(MIPSEL) || defined(PMAX) || defined(__alpha) || defined(LINUX) || defined(FreeBSD) || defined(BSDI)
#define BYTE_R 0
#define BYTE_G 1
#define BYTE_B 2
#else
#define BYTE_R 3
#define BYTE_G 2
#define BYTE_B 1
#endif /* MIPSEL */

void TiffImage::finishDecode()
{
  DEBUGNL("TI::finishDecode()");

  TIFFSetErrorHandler(tiff_errorhandler);
  TIFFSetWarningHandler(tiff_errorhandler);

  TIFF*& tif = impl_->tif_;

  tif = TIFFOpen(filename_, "r");
  if (!tif) {
//     strcpy(tiffError, "TIFF: could not open input file");
//     callback_->error(tiffError);
    return;
  }

  if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &impl_->bitspersample_)) {
    impl_->bitspersample_ = 1;
  }

  switch (impl_->bitspersample_) {
    case 1: case 2: case 4:
    case 8: case 16:
      break;
    default:
      TIFFClose(tif);
      sprintf(tiffError, "TIFF: bad value for bitspersample: %d", impl_->bitspersample_);
      if (callback_)
        callback_->error(tiffError);
      return;
  }
  if (!TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &impl_->samplesperpixel_)) {
    impl_->samplesperpixel_ = 1;
  }

  switch (impl_->samplesperpixel_) {
    case 1: case 3: case 4:
      break;
    default:
      TIFFClose(tif);
      sprintf(tiffError, "TIFF: bad value for samplesperpixel: %d", impl_->samplesperpixel_);
      if (callback_)
        callback_->error(tiffError);
      return;
  }
  uint32 width;
  TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
  uint32 height;
  TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);

  if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &impl_->photometric_)) {
    switch (impl_->samplesperpixel_) {
      case 1:
        impl_->photometric_ = PHOTOMETRIC_MINISBLACK;
        break;
      case 3: case 4:
        impl_->photometric_ = PHOTOMETRIC_RGB;
        break;
      default:
        TIFFClose(tif);
        sprintf(tiffError, "TIFF: bad value for photometric: %d", impl_->photometric_);
        if (callback_)
          callback_->error(tiffError);
        return;
    }
  }

  setSize((int) width, (int) height);
  if (callback_)
    callback_->readHeader();

  uint32*& raster = impl_->raster_;
  raster = new uint32[width * height];

  impl_->BWmap_ = nil;
  impl_->PALmap_ = nil;

  if (raster && impl_->gt(width, height)) {
    /* create raster from packed image data */
    /*int scansize =*/ TIFFScanlineSize(tif);
    Byte* pixptr;

    for (long i = 0; i < height; i++) {
      u_char* c = (u_char*) (raster + (height-i-1) * width);
      pixptr = (Byte*) data() + i * width * 3;
      for (long j = 0; j < width; j++) {
        unsigned char cr = c[BYTE_R];
        unsigned char cg = c[BYTE_G];
        unsigned char cb = c[BYTE_B];
        *pixptr++ = cr;
        *pixptr++ = cg;
        *pixptr++ = cb;
        c += sizeof (uint32);
      }

      if (callback_)
        callback_->increaseProgress(1.0 / height);
    }
    if (callback_)
      callback_->decodedSequential((int) height);
  }

  TIFFClose(tif);
  delete raster;
  delete impl_->BWmap_;
  delete impl_->PALmap_;
}

#undef BYTE_R
#undef BYTE_G
#undef BYTE_B

// ************************************************************************
// what follows is copied from InterViews' class TIFFRasterImpl in tiff.c
// (thank God, it is private, otherwise I could have used it directly :-)

static int checkcmap(
  int n, const u_short* r, const u_short* g, const u_short* b
) {
  while (n-- > 0) {
    if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256) {
      return 16;
    }
  }
  return 8;
}

boolean TiffImpl::gt(uint32 w, uint32 h) {
  u_short minsamplevalue;
  u_short maxsamplevalue;
  u_short planarconfig;
  RGBvalue* Map = nil;

  if (!TIFFGetField(tif_, TIFFTAG_MINSAMPLEVALUE, &minsamplevalue)) {
    minsamplevalue = 0;
  }
  if (!TIFFGetField(tif_, TIFFTAG_MAXSAMPLEVALUE, &maxsamplevalue)) {
    maxsamplevalue = (1<<bitspersample_)-1;
  }
  switch (photometric_) {
    case PHOTOMETRIC_RGB:
      if (minsamplevalue == 0 && maxsamplevalue == 255) {
        break;
      }
      /* fall thru... */
    case PHOTOMETRIC_MINISBLACK:
    case PHOTOMETRIC_MINISWHITE: {
      register int x, range;

      range = maxsamplevalue - minsamplevalue;
      Map = new RGBvalue[range + 1];
      if (Map == nil) {
        TIFFError(
          TIFFFileName(tif_),
        "No space for photometric conversion table"
        );
        return false;
      }
      if (photometric_ == PHOTOMETRIC_MINISWHITE) {
        for (x = 0; x <= range; x++) {
          Map[x] = ((range - x) * 255) / range;
        }
      } else {
        for (x = 0; x <= range; x++) {
          Map[x] = (x * 255) / range;
        }
      }
      if (photometric_ != PHOTOMETRIC_RGB && bitspersample_ != 8) {
        /*
         * Use photometric mapping table to construct
         * unpacking tables for samples < 8 bits.
         */
        if (!makebwmap(Map)) {
          return false;
        }
        delete Map;			/* no longer need Map, free it */
        Map = nil;
      }
      break;
    }
    case PHOTOMETRIC_PALETTE:
      if (!TIFFGetField(
        tif_, TIFFTAG_COLORMAP, &redcmap_, &greencmap_, &bluecmap_)
      ) {
        TIFFError(TIFFFileName(tif_), "Missing required \"Colormap\" tag");
        return (false);
      }
      /*
       * Convert 16-bit colormap to 8-bit (unless it looks
       * like an old-style 8-bit colormap).
       */
      if (
        checkcmap(
          1 << bitspersample_, redcmap_, greencmap_, bluecmap_
        ) == 16
      ) {
        int i;
        for (i = (1 << bitspersample_) - 1; i > 0; i--) {
#define        CVT(x)		(((x) * 255) / ((1L<<16)-1))
          redcmap_[i] = (u_short) CVT(redcmap_[i]);
          greencmap_[i] = (u_short) CVT(greencmap_[i]);
          bluecmap_[i] = (u_short) CVT(bluecmap_[i]);
        }
      }
      if (bitspersample_ <= 8) {
        /*
         * Use mapping table and colormap to construct
         * unpacking tables for samples < 8 bits.
         */
        if (!makecmap(redcmap_, greencmap_, bluecmap_)) {
          return false;
        }
      }
      break;
  }
  TIFFGetField(tif_, TIFFTAG_PLANARCONFIG, &planarconfig);
  boolean e;
  if (planarconfig == PLANARCONFIG_SEPARATE && samplesperpixel_ > 1) {
    e = TIFFIsTiled(tif_) ?
    gtTileSeparate(Map, h, w) : gtStripSeparate(Map, h, w);
  } else {
    e = TIFFIsTiled(tif_) ? 
    gtTileContig(Map, h, w) : gtStripContig(Map, h, w);
  }
  delete Map;
  return e;
}

uint32 TiffImpl::setorientation(uint32 h) {
  uint32 y;

  if (!TIFFGetField(tif_, TIFFTAG_ORIENTATION, &orientation_)) {
    orientation_ = ORIENTATION_TOPLEFT;
  }
  switch (orientation_) {
    case ORIENTATION_BOTRIGHT:
    case ORIENTATION_RIGHTBOT:        /* XXX */
    case ORIENTATION_LEFTBOT:        /* XXX */
      TIFFWarning(TIFFFileName(tif_), "using bottom-left orientation");
      orientation_ = ORIENTATION_BOTLEFT;
      /* fall thru... */
    case ORIENTATION_BOTLEFT:
      y = 0;
      break;
    case ORIENTATION_TOPRIGHT:
    case ORIENTATION_RIGHTTOP:        /* XXX */
    case ORIENTATION_LEFTTOP:        /* XXX */
    default:
      TIFFWarning(TIFFFileName(tif_), "using top-left orientation");
      orientation_ = ORIENTATION_TOPLEFT;
      /* fall thru... */
    case ORIENTATION_TOPLEFT:
      y = h-1;
      break;
  }
  return y;
}

/*
 * Get an tile-organized image that has
 *    PlanarConfiguration contiguous if SamplesPerPixel > 1
 * or
 *    SamplesPerPixel == 1
 */    
boolean TiffImpl::gtTileContig(const RGBvalue* Map, uint32 h, uint32 w) {
  u_char* buf = new u_char[TIFFTileSize(tif_)];
  if (buf == nil) {
    TIFFError(TIFFFileName(tif_), "No space for tile buffer");
    return false;
  }
  tileContigRoutine put = pickTileContigCase(Map);
  uint32 tw;
  TIFFGetField(tif_, TIFFTAG_TILEWIDTH, &tw);
  uint32 th;
  TIFFGetField(tif_, TIFFTAG_TILELENGTH, &th);
  uint32 y = setorientation(h);
  int toskew = (int)(orientation_ == ORIENTATION_TOPLEFT ? -tw+-w : -tw+w);

  if (orientation_ == ORIENTATION_TOPLEFT);  // ???

  for (uint32 row = 0; row < h; row += th) {

    uint32 nrow = (row + th > h ? h - row : th);
    for (uint32 col = 0; col < w; col += tw) {

      if (TIFFReadTile(tif_, buf, col, row, 0, 0) < 0) {
        break;
      }
      if (col + tw > w) {
        /*
         * Tile is clipped horizontally.  Calculate
         * visible portion and skewing factors.
         */
        uint32 npix = w - col;
        int fromskew = (int)(tw - npix);
        (this->*put)(
          raster_ + y*w + col, buf, Map,
        npix, nrow, fromskew, toskew + fromskew
        );
      } else
        (this->*put)(
          raster_ + y*w + col, buf, Map, tw, nrow, 0, toskew
        );
    }

    y += (orientation_ == ORIENTATION_TOPLEFT ? -nrow : nrow);
  }
  delete buf;
  return true;
}

/*
 * Get an tile-organized image that has
 *     SamplesPerPixel > 1
 *     PlanarConfiguration separated
 * We assume that all such images are RGB.
 */    
boolean TiffImpl::gtTileSeparate(
  const RGBvalue* Map, uint32 h, uint32 w
) {
  uint32 tilesize = TIFFTileSize(tif_);
  u_char* buf = new u_char[3*tilesize];
  if (buf == nil) {
    TIFFError(TIFFFileName(tif_), "No space for tile buffer");
    return false;
  }
  u_char* r = buf;
  u_char* g = r + tilesize;
  u_char* b = g + tilesize;

  tileSeparateRoutine put = pickTileSeparateCase(Map);
  uint32 tw;
  TIFFGetField(tif_, TIFFTAG_TILEWIDTH, &tw);
  uint32 th;
  TIFFGetField(tif_, TIFFTAG_TILELENGTH, &th);
  uint32 y = setorientation(h);
  int toskew = (int)(orientation_ == ORIENTATION_TOPLEFT ? -tw+-w : -tw+w);

  for (uint32 row = 0; row < h; row += th) {
    uint32 nrow = (row + th > h ? h - row : th);

    for (uint32 col = 0; col < w; col += tw) {

      if (TIFFReadTile(tif_, r, col, row, 0, 0) < 0) {
        break;
      }
      if (TIFFReadTile(tif_, g, col, row, 0, 1) < 0) {
        break;
      }
      if (TIFFReadTile(tif_, b, col, row, 0, 2) < 0) {
        break;
      }
      if (col + tw > w) {
        /*
         * Tile is clipped horizontally.  Calculate
         * visible portion and skewing factors.
         */
        uint32 npix = w - col;
        int fromskew = (int)(tw - npix);
        (this->*put)(
          raster_ + y*w + col, r, g, b, Map,
        npix, nrow, fromskew, toskew + fromskew
        );
      } else
        (this->*put)(
          raster_ + y*w + col, r, g, b, Map,
        tw, nrow, 0, toskew
        );
    }

    y += (orientation_ == ORIENTATION_TOPLEFT ? -nrow : nrow);
  }
  delete buf;
  return true;
}

/*
 * Get a strip-organized image that has
 *    PlanarConfiguration contiguous if SamplesPerPixel > 1
 * or
 *    SamplesPerPixel == 1
 */    
boolean TiffImpl::gtStripContig(
  const RGBvalue* Map, uint32 h, uint32 w
) {
  u_char* buf = new u_char[TIFFStripSize(tif_)];
  if (buf == nil) {
    TIFFError(TIFFFileName(tif_), "No space for strip buffer");
    return (false);
  }
  tileContigRoutine put = pickTileContigCase(Map);
  uint32 y = setorientation(h);
  int toskew = (int)(orientation_ == ORIENTATION_TOPLEFT ? -w + -w : -w + w);

  if (orientation_ == ORIENTATION_TOPLEFT);

  uint32 rowsperstrip = (uint32) -1L;
  TIFFGetField(tif_, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
  uint32 imagewidth;
  TIFFGetField(tif_, TIFFTAG_IMAGEWIDTH, &imagewidth);
  int scanline = (int)TIFFScanlineSize(tif_);
  int fromskew = (int)(w < imagewidth ? imagewidth - w : 0);
  for (uint32 row = 0; row < h; row += rowsperstrip) {

    u_int nrow = u_int(row + rowsperstrip > h ? h - row : rowsperstrip);
    if (TIFFReadEncodedStrip(
      tif_, TIFFComputeStrip(tif_, row, 0), buf, nrow*scanline) < 0
    ) {
      break;
    }
    (this->*put)(raster_ + y*w, buf, Map, w, nrow, fromskew, toskew);
    y += (orientation_ == ORIENTATION_TOPLEFT ? -nrow : nrow);
  }

  delete buf;
  return true;
}

/*
 * Get a strip-organized image with
 *     SamplesPerPixel > 1
 *     PlanarConfiguration separated
 * We assume that all such images are RGB.
 */
boolean TiffImpl::gtStripSeparate(
  const RGBvalue* Map, uint32 h, uint32 w
) {
  uint32 stripsize = TIFFStripSize(tif_);
  u_char* buf = new u_char[3*stripsize];
  u_char* r = buf;
  u_char* g = r + stripsize;
  u_char* b = g + stripsize;
  tileSeparateRoutine put = pickTileSeparateCase(Map);
  uint32 y = setorientation(h);
  int toskew = (int)(orientation_ == ORIENTATION_TOPLEFT ? -w + -w : -w + w);

  if (orientation_ == ORIENTATION_TOPLEFT);

  uint32 rowsperstrip = (uint32) -1L;
  TIFFGetField(tif_, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
  uint32 imagewidth;
  TIFFGetField(tif_, TIFFTAG_IMAGEWIDTH, &imagewidth);
  int scanline = TIFFScanlineSize(tif_);
  int fromskew = (int)(w < imagewidth ? imagewidth - w : 0);
  for (uint32 row = 0; row < h; row += rowsperstrip) {

    u_int nrow = u_int(row + rowsperstrip > h ? h - row : rowsperstrip);
    if (TIFFReadEncodedStrip(
      tif_, TIFFComputeStrip(tif_, row, 0), r, nrow*scanline) < 0
    ) {
      break;
    }
    if (TIFFReadEncodedStrip(
      tif_, TIFFComputeStrip(tif_, row, 1), g, nrow*scanline) < 0
    ) {
      break;
    }
    if (TIFFReadEncodedStrip(
      tif_, TIFFComputeStrip(tif_, row, 2), b, nrow*scanline) < 0
    ) {
      break;
    }
    (this->*put)(raster_ + y*w, r, g, b, Map, w, nrow, fromskew, toskew);
    y += (orientation_ == ORIENTATION_TOPLEFT ? -nrow : nrow);
  }
  delete buf;
  return true;
}

#define        PACK(r,g,b)	((uint32)(r))|(((uint32)(g))<<8)|(((uint32)(b))<<16)

/*
 * Greyscale images with less than 8 bits/sample are handled
 * with a table to avoid lots of shifts and masks.  The table
 * is setup so that put*bwtile (below) can retrieve 8/bitspersample_
 * pixel values simply by indexing into the table with one
 * number.
 */
boolean TiffImpl::makebwmap(RGBvalue* Map) {
  register int i;
  int nsamples = 8 / bitspersample_;

  BWmap_ = (uint32 **)malloc(
    256*sizeof (uint32 *)+(256*nsamples*sizeof(uint32))
  );
  if (BWmap_ == nil) {
    TIFFError(TIFFFileName(tif_), "No space for B&W mapping table");
    return false;
  }
  register uint32* p = (uint32*)(BWmap_ + 256);
  for (i = 0; i < 256; i++) {
    BWmap_[i] = p;
    switch (bitspersample_) {
      register RGBvalue c;
#define        GREY(x)	c = Map[x]; *p++ = PACK(c,c,c);
      case 1:
      GREY(i>>7);
      GREY((i>>6)&1);
      GREY((i>>5)&1);
      GREY((i>>4)&1);
      GREY((i>>3)&1);
      GREY((i>>2)&1);
      GREY((i>>1)&1);
      GREY(i&1);
      break;
      case 2:
      GREY(i>>6);
      GREY((i>>4)&3);
      GREY((i>>2)&3);
      GREY(i&3);
      break;
      case 4:
      GREY(i>>4);
      GREY(i&0xf);
      break;
    }
#undef        GREY
  }
  return true;
}

/*
 * Palette images with <= 8 bits/sample are handled
 * with a table to avoid lots of shifts and masks.  The table
 * is setup so that put*cmaptile (below) can retrieve 8/bitspersample_
 * pixel values simply by indexing into the table with one
 * number.
 */
boolean TiffImpl::makecmap(
  const u_short* rmap, const u_short* gmap, const u_short* bmap
) {
  register int i;
  int nsamples = 8 / bitspersample_;
  register uint32 *p;

  PALmap_ = (uint32 **)malloc(
    256*sizeof (uint32 *)+(256*nsamples*sizeof(uint32))
  );
  if (PALmap_ == nil) {
    TIFFError(TIFFFileName(tif_), "No space for Palette mapping table");
    return (false);
  }
  p = (uint32 *)(PALmap_ + 256);
  for (i = 0; i < 256; i++) {
    PALmap_[i] = p;
#define        CMAP(x)	\
    c = x; *p++ = PACK(rmap[c]&0xff, gmap[c]&0xff, bmap[c]&0xff);
    switch (bitspersample_) {
      register RGBvalue c;
      case 1:
      CMAP(i>>7);
      CMAP((i>>6)&1);
      CMAP((i>>5)&1);
      CMAP((i>>4)&1);
      CMAP((i>>3)&1);
      CMAP((i>>2)&1);
      CMAP((i>>1)&1);
      CMAP(i&1);
      break;
      case 2:
      CMAP(i>>6);
      CMAP((i>>4)&3);
      CMAP((i>>2)&3);
      CMAP(i&3);
      break;
      case 4:
      CMAP(i>>4);
      CMAP(i&0xf);
      break;
      case 8:
      CMAP(i);
      break;
    }
#undef CMAP
  }
  return (true);
}

/*
 * The following routines move decoded data returned
 * from the TIFF library into rasters that are suitable
 * for passing to lrecwrite.  They do the necessary
 * conversions based on whether the drawing mode is RGB
 * colormap and whether or not there is a mapping table.
 *
 * The routines have been created according to the most
 * important cases and optimized.  pickTileContigCase and
 * pickTileSeparateCase analyze the parameters and select
 * the appropriate "put" routine to use.
 */
#define        REPEAT8(op)	REPEAT4(op); REPEAT4(op)
#define        REPEAT4(op)	REPEAT2(op); REPEAT2(op)
#define        REPEAT2(op)	op; op
#define        CASE8(x,op)				\
switch (x) {				\
                                        case 7: op; case 6: op; case 5: op;	\
                                                                                case 4: op; case 3: op; case 2: op;	\
                                                                                                                        case 1: op;				\
                                                                                                                                                                }
#define        CASE4(x,op)	switch (x) { case 3: op; case 2: op; case 1: op; }

#define        UNROLL8(w, op1, op2) {		\
        uint32 x;			\
        for (x = w; x >= 8; x -= 8) {	\
            op1;			\
            REPEAT8(op2);		\
        }				\
        if (x > 0) {			\
            op1;			\
            CASE8(x,op2);		\
        }				\
}
#define        UNROLL4(w, op1, op2) {		\
        uint32 x;			\
        for (x = w; x >= 4; x -= 4) {	\
            op1;			\
            REPEAT4(op2);		\
        }				\
        if (x > 0) {			\
            op1;			\
            CASE4(x,op2);		\
        }				\
}
#define        UNROLL2(w, op1, op2) {		\
        uint32 x;			\
        for (x = w; x >= 2; x -= 2) {	\
            op1;			\
            REPEAT2(op2);		\
        }				\
        if (x) {			\
            op1;			\
            op2;			\
        }				\
}
        		
#define        SKEW(r,g,b,skew)	{ r += skew; g += skew; b += skew; }

/*
 * 8-bit palette => RGB
 */
void TiffImpl::put8bitcmaptile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    while (h-- > 0) {
        UNROLL8(w, "", *cp++ = PALmap_[*pp++][0]);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 4-bit palette => RGB
 */
void TiffImpl::put4bitcmaptile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32 *bw;

    fromskew /= 2;
    while (h-- > 0) {
        UNROLL2(w, bw = PALmap_[*pp++], *cp++ = *bw++);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 2-bit palette => RGB
 */
void TiffImpl::put2bitcmaptile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32 *bw;

    fromskew /= 4;
    while (h-- > 0) {
        UNROLL4(w, bw = PALmap_[*pp++], *cp++ = *bw++);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 1-bit palette => RGB
 */
void TiffImpl::put1bitcmaptile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32 *bw;

    fromskew /= 8;
    while (h-- > 0) {
        UNROLL8(w, bw = PALmap_[*pp++], *cp++ = *bw++);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 1-bit bilevel => RGB
 */
void TiffImpl::put1bitbwtile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32* bw;

    fromskew /= 8;
    while (h-- > 0) {
        UNROLL8(w, bw = BWmap_[*pp++], *cp++ = *bw++);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 2-bit greyscale => RGB
 */
void TiffImpl::put2bitbwtile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32* bw;

    fromskew /= 4;
    while (h-- > 0) {
        UNROLL4(w, bw = BWmap_[*pp++], *cp++ = *bw++);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 4-bit greyscale => RGB
 */
void TiffImpl::put4bitbwtile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue*,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32* bw;

    fromskew /= 2;
    while (h-- > 0) {
        UNROLL2(w, bw = BWmap_[*pp++], *cp++ = *bw++);
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * 8-bit packed samples => RGB
 */
void TiffImpl::putRGBcontig8bittile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue* Map,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    fromskew *= samplesperpixel_;
    if (Map) {
        while (h-- > 0) {
            uint32 x;
            for (x = w; x-- > 0;) {
        	*cp++ = PACK(Map[pp[0]], Map[pp[1]], Map[pp[2]]);
        	pp += samplesperpixel_;
            }
            pp += fromskew;
            cp += toskew;
        }
    } else {
        while (h-- > 0) {
            UNROLL8(w, "", *cp++ = PACK(pp[0], pp[1], pp[2]); pp += samplesperpixel_);
            cp += toskew;
            pp += fromskew;
        }
    }
}

/*
 * 16-bit packed samples => RGB
 */
void TiffImpl::putRGBcontig16bittile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue* Map,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32 x;

    fromskew *= samplesperpixel_;
    if (Map) {
        while (h-- > 0) {
            for (x = w; x-- > 0;) {
        	*cp++ = PACK(Map[pp[0]], Map[pp[1]], Map[pp[2]]);
        	pp += samplesperpixel_;
            }
            cp += toskew;
            pp += fromskew;
        }
    } else {
        while (h-- > 0) {
            for (x = w; x-- > 0;) {
        	*cp++ = PACK(pp[0], pp[1], pp[2]);
        	pp += samplesperpixel_;
            }
            cp += toskew;
            pp += fromskew;
        }
    }
}

/*
 * 8-bit unpacked samples => RGB
 */
void TiffImpl::putRGBseparate8bittile(
    uint32* cp,
    const u_char* r, const u_char* g, const u_char* b,
    const RGBvalue* Map,
    uint32 w, uint32 h,
    int fromskew, int toskew
)
{
    if (Map) {
        while (h-- > 0) {
            uint32 x;
            for (x = w; x > 0; x--)
        	*cp++ = PACK(Map[*r++], Map[*g++], Map[*b++]);
            SKEW(r, g, b, fromskew);
            cp += toskew;
        }
    } else {
        while (h-- > 0) {
            UNROLL8(w, "", *cp++ = PACK(*r++, *g++, *b++));
            SKEW(r, g, b, fromskew);
            cp += toskew;
        }
    }
}

/*
 * 16-bit unpacked samples => RGB
 */
void TiffImpl::putRGBseparate16bittile(
    uint32* cp,
    const u_char* r, const u_char* g, const u_char* b,
    const RGBvalue* Map,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    uint32 x;

    if (Map) {
        while (h-- > 0) {
            for (x = w; x > 0; x--)
        	*cp++ = PACK(Map[*r++], Map[*g++], Map[*b++]);
            SKEW(r, g, b, fromskew);
            cp += toskew;
        }
    } else {
        while (h-- > 0) {
            for (x = 0; x < w; x++)
        	*cp++ = PACK(*r++, *g++, *b++);
            SKEW(r, g, b, fromskew);
            cp += toskew;
        }
    }
}

/*
 * 8-bit greyscale => RGB
 */
void TiffImpl::putRGBgreytile(
    uint32* cp,
    const u_char* pp,
    const RGBvalue* Map,
    uint32 w, uint32 h,
    int fromskew, int toskew
) {
    while (h-- > 0) {
        uint32 x;
        for (x = w; x-- > 0;) {
            RGBvalue c = Map[*pp++];
            *cp++ = PACK(c,c,c);
        }
        cp += toskew;
        pp += fromskew;
    }
}

/*
 * Select the appropriate conversion routine for packed data.
 */
tileContigRoutine TiffImpl::pickTileContigCase(const RGBvalue*) {
    tileContigRoutine put = 0;
    switch (photometric_) {
    case PHOTOMETRIC_RGB:
        if (bitspersample_ == 8) {
            put = &TiffImpl::putRGBcontig8bittile;
        } else {
            put = &TiffImpl::putRGBcontig16bittile;
        }
        break;
    case PHOTOMETRIC_PALETTE:
        switch (bitspersample_) {
        case 8:	put = &TiffImpl::put8bitcmaptile; break;
        case 4: put = &TiffImpl::put4bitcmaptile; break;
        case 2: put = &TiffImpl::put2bitcmaptile; break;
        case 1: put = &TiffImpl::put1bitcmaptile; break;
        }
        break;
    case PHOTOMETRIC_MINISWHITE:
    case PHOTOMETRIC_MINISBLACK:
        switch (bitspersample_) {
        case 8:	put = &TiffImpl::putRGBgreytile; break;
        case 4: put = &TiffImpl::put4bitbwtile; break;
        case 2: put = &TiffImpl::put2bitbwtile; break;
        case 1: put = &TiffImpl::put1bitbwtile; break;
        }
        break;
    }
    return (put);
}

/*
 * Select the appropriate conversion routine for unpacked data.
 *
 * NB: we assume that unpacked single channel data is directed
 *     to the "packed routines.
 */
tileSeparateRoutine TiffImpl::pickTileSeparateCase(const RGBvalue*) {
    if (bitspersample_ == 8) {
        return &TiffImpl::putRGBseparate8bittile;
    }
    return &TiffImpl::putRGBseparate16bittile;
}
