//  ----------------------------------------------------------------------------
//  MODULE    : PHierarchicalImage
//  LANGUAGE  : C++
//  CREATOR   : Philippe BOSSUT
//  CREAT. DATE : Tuesday, March 12, 1996
//  DESCRIPTION :
//  COMMENTS  :
//    SCCSID          : @(#)ph_image.cpp  1.2 12:48:36 22 Jul 1997
//  ----------------------------------------------------------------------------
//  Copyright (c) 1999 Digital Imaging Group, Inc.
//  For conditions of distribution and use, see copyright notice
//  in Flashpix.h
//  ----------------------------------------------------------------------------

//  ----------------------------------------------------------------------------

//  ----------------------------------------------------------------------------
#include "ph_image.h"
#include "f_fpxio.h"
//  ----------------------------------------------------------------------------

//  Includes
//  --------

#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#ifndef _WINDOWS
#  include  <strings.h>
#endif
#include  <math.h>
#ifdef macintosh
#  include  <Errors.h>
#endif


#ifndef Debug_h
#  include  "debug.h"
#endif
#ifndef Memoire_h
#  include  "b_memory.h"
#endif
#ifndef Fichier_h
#  include  "a_file.h"
#endif

#ifndef PResolutionLevel_h
#  include "pr_level.h"
#endif
#ifndef PTile_h
#  include "ptile.h"
#endif
#ifndef PImageFile_h
#  include "pimgfile.h"
#endif

#ifndef Numbers_h
#  include  "numbers.h"
#endif

#ifndef BufferDesc_h
#  include  "buffdesc.h"
#endif


//  Constants
//  ---------

//  Variables
//  ---------

//  ----------------------------------------------------------------------------
//  #pragma segment PHierarchicalImage
//  ----------------------------------------------------------------------------

//  ----------------------------------------------------------------------------
//  Internal Functions
//  ----------------------------------------------------------------------------

inline float MIN(float a, float b, float c)
{
  return MIN(a, MIN(b,c));
}

//  ----------------------------------------------------------------------------
//  Member Functions
//  ----------------------------------------------------------------------------

// Open a file in Create mode (compressed or uncompressed) :
// CAUTION : the file is opened as long as necessary
PHierarchicalImage::PHierarchicalImage (FicNom& refName, int width, int height, float resolution) : PRIImage (width, height, resolution)
{
  Init ();        // Init the object

  fileName = refName;     // Name of the image file
  mode   = mode_Ecrasement;   // Opening mode: overwrite image

  if (EnoughDiskSpace() == false)
    mode = mode_Lecture;    // If not enough disk space, invalid overwrite mode
}


//  ----------------------------------------------------------------------------
// Open a file in Read mode only (compressed or uncompressed) :
// CAUTION : the file is opened as long as necessary
PHierarchicalImage::PHierarchicalImage (FicNom& refName) : PRIImage ()
{
  Init ();        // Init the object
  fileName = refName;     // Name of the image file
}


//  ----------------------------------------------------------------------------
// Open a file in Read or Write mode (compressed or uncompressed) :
// CAUTION : the file is opened as long as necessary
PHierarchicalImage::PHierarchicalImage (FicNom& refName, mode_Ouverture openMode) : PRIImage ()

{
  Init ();        // Init the object
  fileName = refName;     // Name of the image file
  mode   = openMode;      // Opening mode: overwrite image
}


//  ----------------------------------------------------------------------------
// Open a file in Create mode (compressed or uncompressed) :
// CAUTION : the file is opened as long as necessary
PHierarchicalImage::PHierarchicalImage (int theFd, int width, int height, float resolution)
  : PRIImage (width, height, resolution)
{
  Init ();        // Init the object

  fd  = theFd;      // The caller opened the image
  mode  = mode_Ecrasement;    // Opening mode: overwrite image

  if (EnoughDiskSpace() == false)
    mode = mode_Lecture;    // If not enough disk space, invalid overwrite mode
}


//  ----------------------------------------------------------------------------
// Open a file in Read mode only (compressed or uncompressed) :
// CAUTION : the file is opened as long as necessary
PHierarchicalImage::PHierarchicalImage (int theFd) : PRIImage ()
{
  Init ();    // Init the object
  fd = theFd;   // The caller opened the image
}


//  ----------------------------------------------------------------------------
// Open a file in Read or Write mode (compressed or uncompressed) :
// CAUTION : the file is opened as long as necessary
PHierarchicalImage::PHierarchicalImage (int theFd, mode_Ouverture openMode) : PRIImage ()

{
  Init ();      // Init the object
  fd    = theFd;  // The caller opened the image
  mode  = openMode;   // Opening mode: overwrite image
}


//  ----------------------------------------------------------------------------
// Clean memory
PHierarchicalImage::~PHierarchicalImage ()
{
  if (filePtr) {
    delete filePtr;
    filePtr = NULL;
  }
  if (firstSubImage) {
    delete firstSubImage;
    firstSubImage = NULL;
  }
  if (subImages) {
    delete [] subImages;
    subImages = NULL;
  }
}


//  ----------------------------------------------------------------------------
// Give default values to image information before starting reading or using them
void PHierarchicalImage::Init ()
{
  // PHierarchicalImage values
  baseSpace     = SPACE_32_BITS_RGB;
  baseUncalibrated  = FALSE;
  usedSpace     = SPACE_32_BITS_RGB;
  isFlatFile    =  FALSE;
  alphaOffset     = (unsigned char) GetAlphaOffsetBaseline(usedSpace);
  backgroundBase  = Toolkit_BackgroundColor();
  backgroundUsed  = Toolkit_BackgroundColor();

  convolution   = Toolkit_Convolution();  // Default convolution mode
  filter    = 0;        // No custom convolution function for the moment
  automaticDecimation = true;       // Default is: yes, do decimation automatically

  compression   = Toolkit_Compression();  // Default compression mode
  compressionHandle = NULL;       // Start with no compression handle
  handleSize    = 0;        // which size is then null

  SetTileParameter (Toolkit_TileWidth(), Toolkit_TileWidth()); // Default tile size

                                                                  firstSubImage   = NULL;
  nbSubImages   = 0;
  subImages   = NULL;

  //  fileName  = "";       // Name of the image file

  filePtr   = NULL;       // File not opened yet
  fd      = 0;        // or may be by the caller.
  mode      = mode_Lecture;     // Opening mode: read only by default
  posFic    = 0;        // Position at the begining os the file
  version   = 0;        // Version of the file (we've no idea at that level...)

  posPath   = 0;        // No paths
}


//  ----------------------------------------------------------------------------
FPXStatus PHierarchicalImage::SetTileParameter (int width, int height)
{
  // Test parameters consistency
  assert((width>0) && (height>0));

  tileWidth = MAX(width,height);
  tileSize = width * height * sizeof(Pixel);
  tileLineSize = width * sizeof(Pixel);
  log2TileWidth = (int)(Toolkit_Log2(tileWidth));
  maskTileWidth = tileWidth - 1;
  return FPX_OK;
}


//  ----------------------------------------------------------------------------
// Open the file, create it if necessary (if mode is write), write first information on the first block
FPXStatus PHierarchicalImage::OpenFile ()
{
  return FPX_UNIMPLEMENTED_FUNCTION;
}


//  ----------------------------------------------------------------------------
// Store the header of the file before closing
FPXStatus PHierarchicalImage::CloseFile()
{
  return FPX_UNIMPLEMENTED_FUNCTION;
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::DoNotCloseFileWhenDelete()
{
  filePtr->DoNotCloseFileWhenDelete();
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::CloseFileWhenDelete()
{
  filePtr->CloseFileWhenDelete();
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::UpdateErrorCount()
{
  PRIImage::UpdateErrorCount();
  if (!Status()) {      // If error raised
    nbSubImages = 0;    // Invalid the image
    mode = mode_Lecture;  // Force mode to be read only
  }
}


//  ----------------------------------------------------------------------------
// Open an image file and create the resolution level list "firstSubImage" and the "subImages" array of pointers
FPXStatus PHierarchicalImage::OpenImage()
{
  FPXStatus status = FPX_OK;

  // Open the file if it hasn't been done yet
  if (filePtr == NULL) {
    status = OpenFile();
    // Init the size parameters according to the infos read into the file
    SetImageSize(width,height,resolution);
  }

  if (firstSubImage == NULL && !Status()) {
    if (nbSubImages != 0)                     // The file is open en read or write mode
      CreateInitResolutionLevelList();            // Create and initialize the list of sub image reading the file "filePtr"
    else if ((nbSubImages == 0) && (mode == mode_Ecrasement)) // The file is open in create mode
      CreateEmptyResolutionLevelList();           // Create the list of empty sub images
    else {
      RaiseError( FPX_FILE_READ_ERROR);
      status = FPX_FILE_READ_ERROR;
    }
    // Initialize the array of pointers "subImages"
    // LS 1/11/96: We initialize the field 'subImages' even on error;
    //         else, LP crashes when reading a compressed image without QuickTime installed
    InitResolutionLevelsTable();
  }
  if (status == FPX_OK)
    status = Status();
  return status;
}


//  ----------------------------------------------------------------------------
FPXStatus PHierarchicalImage::GetFileName (FicNom& refName)
{
  refName = fileName;
  return(FPX_OK);
}


//  ----------------------------------------------------------------------------
// Store "pix" in a buffer, write it if it's full and compute sub-image buffer if necessary
FPXStatus PHierarchicalImage::WriteLine (Pixel* pix, short plan)
{
  Pixel   *source;
  FPXStatus status = FPX_OK;

  if (GtheSystemToolkit->interleaving == Interleaving_Pixel)
    status = firstSubImage->WriteLine(pix, plan);
  else {
    FastAllocArray(source,Pixel,(unsigned int)(width));
    if (source == NULL)
      return FPX_MEMORY_ALLOCATION_FAILED;

    if ((GtheSystemToolkit->interleaving != Interleaving_Channel) || (plan == ActiveChannel_All)) {
      if (Toolkit_UnInterleave (pix, source, width, 1, width, 1)) {
        FastDeleteArray(source,Pixel);
        return FPX_COLOR_CONVERSION_ERROR;  // error converting pixel buffer according to user's options
      }

    } else {
      // The buffer contain only the active channel
      // we must create a temporary buffer to send to the image file
      int j;
      unsigned char *src, *dst;

      src = (unsigned char *)(pix);
      dst = (unsigned char *)(source)  + plan;
      for (j = 0; j < width; j++, src++, dst+=sizeof(Pixel))
        *dst = *src;
    }
    status = firstSubImage->WriteLine(source, plan);
    FastDeleteArray(source,Pixel);
  }
  return status;
}


//  ----------------------------------------------------------------------------
// Store "pix" in a buffer, write it if it's full and compute sub-image buffer if necessary
FPXStatus PHierarchicalImage::WriteRectangle (int x0, int y0, int x1, int y1, Pixel* pix, short plan, short resolution)
{
  FPXStatus status = FPX_OK;

  if ( GtheSystemToolkit->interleaving == Interleaving_Pixel) {
    status = subImages[resolution]->WriteRectangle (x0, y0, x1, y1, pix, plan);
  } else {
    int pixWidth = (x1 - x0 + 1);    // width of the buffer 'pix'
    int pixHeight = (y1 - y0 + 1);   // height of the buffer 'pix'
    Pixel*  dest = NULL;        // temporary buffer
    Pixel*  ptrDest = NULL;
    int subX0, subY0, subX1, subY1;  // corners of the temporary buffer
    int bufSize = 0;         // size of temporary buffer
    int bufWidth, bufHeight;     // width ans height of temporary buffer
    int i, j;
    unsigned char *src, *dst, *ptrPix;

    src = dst = ptrPix = 0;
    // cut the rectangle in sub-rectangles to avoid to create a big buffer
    // in memory when converting the interleaving.
    // These sub-rectangles have the size of a tile because writing tiles is optimum.
    int incrRow = tileWidth;
    int incrCol = tileWidth;
    for (subY0 = y0; subY0 <= y1; subY0+=incrRow) {
      subY1 = subY0 + incrRow-1;
      if (subY1 > y1) subY1 = y1;
      bufHeight = subY1 - subY0 +1;
      for (subX0 = x0; subX0 <= x1; subX0+=incrCol) {
        subX1 = subX0 + incrCol-1;
        if (subX1 > x1) subX1 = x1;
        bufWidth = subX1 - subX0 +1;
        if (bufSize != (bufWidth*bufHeight)) {
          bufSize = bufWidth*bufHeight;
          FastDeleteArray(dest, Pixel);
          FastAllocArray(dest, Pixel, bufSize);
          if (!dest)
            return FPX_MEMORY_ALLOCATION_FAILED;
        }
        assert(dest);
        // search the position of the first pixel in the rectangle to copy in the sub_rectangle
        if ((GtheSystemToolkit->interleaving != Interleaving_Channel) || (plan == ActiveChannel_All)) {
          switch (GtheSystemToolkit->interleaving) {
          case Interleaving_Line:
            ptrPix = (unsigned char *)(pix);
            ptrPix += (subY0-y0)*pixWidth*4 + (subX0-x0);
            break;
          case Interleaving_Channel:
            ptrPix = (unsigned char *)(pix);
            ptrPix += (subY0-y0)*pixWidth + (subX0-x0);
            break;
          case Interleaving_Pixel:
            break;
          }
          // compute the Uninterleaving in the sub-rectangle
          if (Toolkit_UnInterleave ((Pixel *)(ptrPix), dest, pixWidth, pixHeight, bufWidth, bufHeight)) {
            FastDeleteArray(dest,Pixel);
            return FPX_COLOR_CONVERSION_ERROR;  // error converting pixel buffer according to user's options
          }
        } else {
          // The buffer contain only the active channel
          // we must copy only this channel to the temporary buffer to send to the image file

          ptrPix = (unsigned char *)(pix);
          ptrPix += (subY0-y0)*pixWidth + (subX0-x0);
          ptrDest = dest;
          for (i=subY0;i<=subY1;i++,ptrDest+=bufWidth,ptrPix+=pixWidth ) {
            src = ptrPix;
            dst = (unsigned char *)(ptrDest)  + plan;
            for (j=subX0; j<=subX1;j++,src++,dst+=sizeof(Pixel))
              *dst = *src;
          }
        }
        // write the temporary buffer in the image file
        status = subImages[resolution]->WriteRectangle (subX0, subY0, subX1, subY1, dest, plan);
        if (status) {
          FastDeleteArray(dest,Pixel);
          return status;
        }
      }
    }

    FastDeleteArray(dest,Pixel);
    dest = NULL;
  }
  return status;

}


//  ----------------------------------------------------------------------------
// To generate the hierarchy, we just have to generate the high res: because of the recursive
// nature of the DecimateTile() function, this will automatically decimate all the sub resolutions.
// Note that this routine does not ensure that modified tiles are written to the file.
// After this call returns, there may still be cached, modified tiles in memory.
FPXStatus PHierarchicalImage::RegenerateHierarchy ()
{
  FPXStatus err = FPX_OK;

  // Required to have the recursion working
  SetAutomaticDecimation(true);

  // Save any changes to the full resolution's tiles and then decimate it's tiles.
  if (firstSubImage) {
    err = firstSubImage->FlushModifiedTiles();
    if (err == FPX_OK)
      err = firstSubImage->DecimateLevel();
  }
  else
    err = FPX_ERROR;

  return err;
}


//  ----------------------------------------------------------------------------
// Read a rectangle in the correct resolution level and fill 'pix' with it
FPXStatus PHierarchicalImage::ReadRectangle (int x0, int y0, int x1, int y1, Pixel* pix, int resolution)
{
  FPXStatus status = FPX_OK;
  FPXStatus   currentStatus;

  if (resolution == -1)
    resolution = 0;
  if (GtheSystemToolkit->interleaving == Interleaving_Pixel)
    status = subImages[resolution]->ReadRectangle (x0, y0, x1, y1, pix);
  else {
    // Cut the rectangle in sub-rectangles to avoid to create a big buffer in memory
    // when converting the interleaving.
    // These sub-rectangles have the size of a tile because it's optimum.
    int pixWidth = (x1 - x0 + 1);    // width of the buffer 'pix'
    int pixHeight = (y1 - y0 + 1);   // height of the buffer 'pix'
    Pixel*  source = NULL;      // temporary buffer
    Pixel*  ptrSource = NULL;
    int subX0, subY0, subX1, subY1;  // corners of the temporary buffer
    int bufSize = 0;         // size of temporary buffer
    int bufWidth, bufHeight;     // width ans height of temporary buffer
    int i, j;
    unsigned char *src, *dst, *ptrPix;

    short plan = Toolkit_ActiveChannel();
    int incrRow = tileWidth;
    int incrCol = tileWidth;
    for (subY0 = y0; subY0 <= y1; subY0+=incrRow) {
      subY1 = subY0 + incrRow-1;
      if (subY1 > y1) subY1 = y1;
      bufHeight = subY1 - subY0 +1;
      for (subX0 = x0; subX0 <= x1; subX0+=incrCol) {
        subX1 = subX0 + incrCol-1;
        if (subX1 > x1) subX1 = x1;
        bufWidth = subX1 - subX0 +1;
        if (bufSize != (bufWidth*bufHeight)) {
          bufSize = bufWidth*bufHeight;
          FastDeleteArray(source, Pixel);
          FastAllocArray(source, Pixel, bufSize);
          if (!source)
            return FPX_MEMORY_ALLOCATION_FAILED;
        }
        assert(source);
        // read a sub rectangle in Pixel
        currentStatus = subImages[resolution]->ReadRectangle (subX0, subY0, subX1, subY1, source);
        if (currentStatus)
          status = currentStatus;
        if (status != FPX_MEMORY_ALLOCATION_FAILED) {
          if ((GtheSystemToolkit->interleaving != Interleaving_Channel) || (plan == ActiveChannel_All)) {
            if (Toolkit_Interleave (source, bufWidth, bufHeight))
              status = FPX_MEMORY_ALLOCATION_FAILED;
            else if (Toolkit_CopyInterleaved (pix, pixWidth, pixHeight,
                                              source, bufWidth, bufHeight, subX0-x0, subY0-y0))
              status = FPX_MEMORY_ALLOCATION_FAILED;
          } else {
            // Pixel by pixel if planes specified, the destination contains only the choosen channel
            ptrPix  = (unsigned char *)(pix);
            ptrPix += (subY0-y0)*pixWidth + (subX0-x0);
            ptrSource = source;
            for (i = subY0; i <= subY1; i++, ptrSource += bufWidth, ptrPix += pixWidth ) {
              src = (unsigned char *)(ptrSource)  + plan;
              dst = ptrPix;
              for (j = subX0; j <= subX1; j++, src += sizeof(Pixel), dst++)
                *dst = *src;
            }
          }
        }
        if (status == FPX_MEMORY_ALLOCATION_FAILED) {
          FastDeleteArray(source,Pixel);
          return status;
        }

      }
    }
    FastDeleteArray(source,Pixel);
    source = NULL;
  }
  return status;
}


//  ----------------------------------------------------------------------------
// Read the path from the Image file: this function has to be derived to really write the things in
// the file. The following code handle only the memory management part.
OSErr PHierarchicalImage::ReadPaths (data_Record** thePath, int* nbRecords, int* theClipPathNum)
{
  return PRIImage::ReadPaths (thePath, nbRecords, theClipPathNum);
}


//  ----------------------------------------------------------------------------
// Write the path in the Image file: this function has to be derived to really write the things in
// the file. The following code handle only the memory management part.
OSErr PHierarchicalImage::WritePaths (data_Record*  thePath, int  nbRecords, int  theClipPathNum)
{
  OSErr  err = noErr;

  // Release the old path if any
  ReleasePaths ();

  // Store the new path info
  nbPaths   = nbRecords;
  clipPathNum = theClipPathNum;

  // Allocate the intermediate path buffer
  if ((path = new data_Record [nbPaths]) == NULL) {
    err = memFullErr;   // error : can't reserve buffer
    goto end;
  }

  // Copy the given paths into the allocated path buffer
  BlockMove(thePath,path,nbPaths*sizeof(data_Record));

 end:
  if (err != noErr)
    // If error, clean everything before exit
    ReleasePaths ();
  return err;
}


//  ----------------------------------------------------------------------------
FPXStatus PHierarchicalImage::Read4Points (int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, Pixel* table, int levelSubImage)
{
  FPXStatus status = FPX_OK;        // Returned value
  int    dx, dy;
  int    n;
  int    px[16], py[16];


  dx = x1 - x0;
  dy = y1 - y0;

  // Compute sub-image level
  if (levelSubImage != -1)
    n = levelSubImage;
  else
    n = Toolkit_Log2((((dx < 0 ? -dx:dx) + (dy < 0 ? -dy:dy)) >> 14) & 0x3ff); // (14 = 12 + 2) PARAM_ECHELLE_VUE;
  // CHG_FIX_FLAT_TNAIL - if a non-hierarchical image is being created, then isFlatFile has
  //    not been initialized, so it is misleading. However, if we're creating, then the
  //    nbCreatedResolutions is 1. nbCreatedResolutions is set properly for reads and
  //    creates, so it was added to the following test to ensure that we render using a
  //    valid resoltion.
  if(isFlatFile || (((PFileFlashPixIO*)this)->GetNumCreatedResolutions() == 1))
    n = 0;
  if (nbSubImages) {          // If image has been successfully opened (else, file is missing)
    if (PRIImage::readInterpolated) {
      if (n >= nbSubImages)
        n = nbSubImages - 1;
      if (n) {
        x3 >>= n; y3 >>= n;
        x2 >>= n; y2 >>= n;
        x1 >>= n; y1 >>= n;
        x0 >>= n; y0 >>= n;
      }

      // Shift by half a pixel toward top left corner (origin): necessary to avoid misregistration when switching
      // between with and without interpolation (classic difference between discrete and continuous coordinates)
      x0 -= 2048; y0 -= 2048;   // Coordinates are with 12 bits precision, so 1 pixel = 4096 units...
      x1 -= 2048; y1 -= 2048;
      x2 -= 2048; y2 -= 2048;
      x3 -= 2048; y3 -= 2048;

      px[P00] = x0 >> 4;          py[P00] = y0 >> 4;
      dx = (x0+x3) >>1;         dy = (y0+y3) >>1;
      px[P22] = dx >>4;         py[P22] = dy >>4;
      px[P11] = (x0+dx)>>5;       py[P11] = (y0+dy)>>5;
      px[P33] = (x3+dx)>>5;       py[P33] = (y3+dy)>>5;
      px[P31] = (x1+dx)>>5;       py[P31] = (y1+dy)>>5;
      px[P13] = (x2+dx)>>5;       py[P13] = (y2+dy)>>5;

      px[P32] = ((x3 += (x0 + x3)) + x1) >> 6;  py[P32] = ((y3 += (y0 + y3)) + y1) >> 6;
      px[P23] = (x3 + x2) >> 6;   py[P23] = (y3 + y2) >> 6;

      x3 = (x0 + x1) >> 1;        y3 = (y0 + y1) >> 1;
      px[P20] = x3 >> 4;          py[P20] = y3 >> 4;
      px[P21] = (x3+dx)>>5;       py[P21] = (y3+dy)>>5;
      px[P10] = (x0+x3)>>5;       py[P10] = (y0+y3)>>5;
      px[P30] = (x1+x3)>>5;       py[P30] = (y1+y3)>>5;

      x3 = (x0 + x2) >> 1;        y3 = (y0 + y2) >> 1;
      px[P02] = x3 >> 4;          py[P02] = y3 >> 4;
      px[P12] = (x3+dx)>>5;       py[P12] = (y3+dy)>>5;
      px[P01] = (x0+x3)>>5;       py[P01] = (y0+y3)>>5;
      px[P03] = (x2+x3)>>5;       py[P03] = (y2+y3)>>5;

      status = subImages[n]->ReadInterpolated (px, py, table);
    }
    else {
      if (n >= nbSubImages)
        n = nbSubImages - 1;
      if (n) {
        x3 >>= n; y3 >>= n;
        x2 >>= n; y2 >>= n;
        x1 >>= n; y1 >>= n;
        x0 >>= n; y0 >>= n;
      }

      px[P00] = x0 >> 12;         py[P00] = y0 >> 12;
      dx = (x0+x3) >>1;         dy = (y0+y3) >>1;
      px[P22] = dx >>12;          py[P22] = dy >>12;
      px[P11] = (x0+dx)>>13;        py[P11] = (y0+dy)>>13;
      px[P33] = (x3+dx)>>13;        py[P33] = (y3+dy)>>13;
      px[P31] = (x1+dx)>>13;        py[P31] = (y1+dy)>>13;
      px[P13] = (x2+dx)>>13;        py[P13] = (y2+dy)>>13;

      px[P32] = ((x3 += (x0 + x3)) + x1) >> 14; py[P32] = ((y3 += (y0 + y3)) + y1) >> 14;
      px[P23] = (x3 + x2) >> 14;    py[P23] = (y3 + y2) >> 14;

      x3 = (x0 + x1) >> 1;        y3 = (y0 + y1) >> 1;
      px[P20] = x3 >> 12;         py[P20] = y3 >> 12;
      px[P21] = (x3+dx)>>13;        py[P21] = (y3+dy)>>13;
      px[P10] = (x0+x3)>>13;        py[P10] = (y0+y3)>>13;
      px[P30] = (x1+x3)>>13;        py[P30] = (y1+y3)>>13;

      x3 = (x0 + x2) >> 1;        y3 = (y0 + y2) >> 1;
      px[P02] = x3 >> 12;         py[P02] = y3 >> 12;
      px[P12] = (x3+dx)>>13;        py[P12] = (y3+dy)>>13;
      px[P01] = (x0+x3)>>13;        py[P01] = (y0+y3)>>13;
      px[P03] = (x2+x3)>>13;        py[P03] = (y2+y3)>>13;
      status = subImages[n]->Read(px,py,table);
    }
  } else
    status = FPX_FILE_READ_ERROR;

  if (status != FPX_OK) {
    // If the tile is missing, fill "table[]" with a chessboard pattern
    Pixel* ptp = table;

    *ptp++ = 0x00000000; *ptp++ = 0x00000000; *ptp++ = 0xFFFFFFFF; *ptp++ = 0xFFFFFFFF;
    *ptp++ = 0x00000000; *ptp++ = 0x00000000; *ptp++ = 0xFFFFFFFF; *ptp++ = 0xFFFFFFFF;
    *ptp++ = 0xFFFFFFFF; *ptp++ = 0xFFFFFFFF; *ptp++ = 0x00000000; *ptp++ = 0x00000000;
    *ptp++ = 0xFFFFFFFF; *ptp++ = 0xFFFFFFFF; *ptp++ = 0x00000000; *ptp++ = 0x00000000;
  }

  return status;
}


//  ----------------------------------------------------------------------------
// Send back the smallest image contained in a rectangle width * height.
// Read it on disk if necessary.
// If decompressor is missing, send back NULL.
// CAUTION : bufferOut must be allocated and freed by calling routine
// CAUTION : we allocate a temporary buffer with pixelsPerLine*height pixels
FPXStatus PHierarchicalImage::ReadInARectangle (Pixel* bufferOut, short pixelsPerLine, short rectWidth, short rectHeight,
                  const CorrectLut* correctLut, Boolean useAlphaChannel, const CombinMat* combinaisonMatrix)
{
  Pixel *bufferTemp, *ptrBufferTemp, *ptrBufferOut;
  int i;

  // allocate an intermediate buffer to read the rectangle
  unsigned int size = (unsigned int)(pixelsPerLine * rectHeight);
  FastAllocArray(bufferTemp,Pixel,size);
  if (bufferTemp == NULL)
    return FPX_MEMORY_ALLOCATION_FAILED;
  ptrBufferTemp = bufferTemp;
  ptrBufferOut = bufferOut;
  for (i = 0; i < rectHeight; i++, ptrBufferTemp += pixelsPerLine, ptrBufferOut += pixelsPerLine )
    memcpy(ptrBufferTemp, ptrBufferOut, pixelsPerLine*sizeof(Pixel));

  // search the corresponding sub-image and call 'ReadInARectangle()' on this sub-image
  FPXStatus status = FPX_OK;
  if (nbSubImages) {
    i = 0;          // Start from the high resolution
    int iWidth  = width; // Width  of the high res in pixels
    int iHeight = height;  // Height of the high res in pixels
    while ((iHeight >= rectHeight) && (iWidth >= rectWidth) && (i <= (nbSubImages-1))) {
      iHeight = (iHeight + 1)/2;
      iWidth  = (iWidth  + 1)/2;
      i++;
    }
    if (i != 0)
      i -= 1;
    subImages[i]->ReadInARectangle(bufferTemp, pixelsPerLine, rectWidth, rectHeight, correctLut, useAlphaChannel, combinaisonMatrix);
  }

  short plan = Toolkit_ActiveChannel();
  if ((plan != ActiveChannel_All) && (GtheSystemToolkit->interleaving == Interleaving_Channel)) {
    // Copy Pixel by pixel if planes specified, the destination contains only the choosen channel
    int j;
    unsigned char *src, *dst, *ptrCharBufferOut;
    ptrCharBufferOut = (unsigned char *)(bufferOut);
    for (i = 0; i < rectHeight; i++, bufferTemp += pixelsPerLine, ptrCharBufferOut += pixelsPerLine ) {
      src = (unsigned char *)(bufferTemp)  + plan;
      dst = ptrCharBufferOut;
      for (j = 0; j < pixelsPerLine; j++, src += sizeof(Pixel), dst++)
        *dst = *src;
    }
  } else {
    // copy the buffer line by line
    ptrBufferTemp = bufferTemp;
    ptrBufferOut = bufferOut;
    for (i = 0; i < rectHeight; i++, ptrBufferTemp += pixelsPerLine, ptrBufferOut += pixelsPerLine )
      memcpy(ptrBufferOut, ptrBufferTemp, pixelsPerLine*sizeof(Pixel));
    // interleave according to user's option
    if (GtheSystemToolkit->interleaving != Interleaving_Pixel)
      if (Toolkit_Interleave (bufferOut, pixelsPerLine, rectHeight))
        status = FPX_MEMORY_ALLOCATION_FAILED;
  }
  FastDeleteArray(bufferTemp,Pixel);
  return status;
}


//  ----------------------------------------------------------------------------
// Read size of full resolution image and size of tiles
FPXStatus PHierarchicalImage::GetInfo (int* width, int* height, int* tileWidth, int* tileHeight, Typ_Compression* compr)
{
  *width  = this->width;
  *height = this->height;
  *tileWidth  = this->tileWidth;
  *tileHeight = this->tileWidth;
  *compr  = this->compression;

  return FPX_OK;
}

//  ----------------------------------------------------------------------------
FPXStatus PHierarchicalImage::GetTileSize (int* tileSize)
{
  *tileSize = this->tileSize;
  return FPX_OK;
}

//  ----------------------------------------------------------------------------
void* PHierarchicalImage::GetCurrentFile()
{
  return filePtr;
}


//  ----------------------------------------------------------------------------
// Test if there is enough space on disk to create the file
// Prompt the user if not
Boolean PHierarchicalImage::EnoughDiskSpace ()
{
  Boolean enoughSpace = true;
  int channelNumber;
  float compressorRatio;
  return true;

  channelNumber = GetNbChannel(baseSpace);

  switch (compression) {
  case Compression_None:
    compressorRatio = (float) 1.0;
    break;
  case Compression_NeoTech5:
  case Compression_NeoTech15:
  case Compression_NeoTech25:
    compressorRatio =(float) 0.33;
    break;
  case Compression_QuickTime:
    compressorRatio = (float) 0.33;
    break;
  case Compression_32to24:
    if (existAlphaChannel)
      compressorRatio =(float) 0.75;
    else
      compressorRatio = (float)1.0;
    break;
  case Compression_Other:
    compressorRatio =(float) 0.33;
    break;
  default:
    compressorRatio = (float) 1.0;
    break;
  }

  int size = (int) (((float)(width * height * channelNumber * compressorRatio * 4 / 3)/1024.0));

  enoughSpace = fileName.EnoughFreeSpace(size);

  return enoughSpace;
}


//  ----------------------------------------------------------------------------
// Set the Boolean existAlphaChannel and test if there is enougth space to use it
Boolean PHierarchicalImage::SetAlphaChannel(Boolean exist)
{
  PRIImage::SetAlphaChannel(exist);
  return (ExistAlphaChannel() ? EnoughDiskSpace() : true);
}

//  ----------------------------------------------------------------------------
// Set the decimation type when computing a subresolution
void PHierarchicalImage::SetConvolution (Typ_Convolution newConvolution)
{
  convolution = newConvolution;
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::SetAutomaticDecimation (Boolean automaticDecimationOn)
{
  automaticDecimation = automaticDecimationOn;
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::SetBackgroundColor (FPXBaselineColorSpace colorSpace, Pixel backgroundColor)
{
  // Set the background color to the color passed in parameter
  backgroundBase = backgroundColor;
  backgroundUsed = backgroundColor;

  // Convert the passed color into the required color space
  ConvertPixelBuffer ((unsigned char*)(&backgroundBase), 1, colorSpace, baseSpace);
  ConvertPixelBuffer ((unsigned char*)(&backgroundUsed), 1, colorSpace, usedSpace);
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::SetUsedColorSpace (FPXBaselineColorSpace colorSpace)
{
  // Convert the background color into the new used space
  ConvertPixelBuffer ((unsigned char*)(&backgroundUsed), 1, usedSpace, colorSpace);
  alphaOffset = (unsigned char) GetAlphaOffsetBaseline(colorSpace);
  usedSpace   = colorSpace;
}

//  ----------------------------------------------------------------------------
void PHierarchicalImage::SetBaseColorSpace (FPXBaselineColorSpace colorSpace)
{
  // Convert the background color into the new base space
  ConvertPixelBuffer ((unsigned char*)(&backgroundBase), 1, baseSpace, colorSpace);
  baseSpace = colorSpace;
}

//  ----------------------------------------------------------------------------
FPXStatus PHierarchicalImage::InverseAlpha ()
{
  for (int i = 0; i < nbSubImages; ++i)
    subImages[i]->InverseAlpha();
  return FPX_OK;
}


//  ----------------------------------------------------------------------------
// Read a pixel of a sub image. Read it on disk if necessary.
// If error, return false and pixel is set to 0.
// CAUTION : coordinates must be discrete with 12 bits precision (1 << 12).
FPXStatus PHierarchicalImage::ReadMean (int xi, int yi, Pixel& pix, int levelSubImage)
{
  FPXStatus status = FPX_OK;

  if ((Status() == 0) && nbSubImages) {

    if (PRIImage::readInterpolated) {
      // Conversion for ReadMeanInterpolated(): requires continuous coordinates with 8 bits precision
      xi = (xi - 2048) >> 4;
      yi = (yi - 2048) >> 4;
      // Go to the next level for precision of interpolation
      levelSubImage = (levelSubImage >= nbSubImages ? nbSubImages - 1 : levelSubImage - 1);
      if (levelSubImage <= 0)
        levelSubImage = 0;
      // Read the pixel
      status = subImages[levelSubImage]->ReadMeanInterpolated (xi >> levelSubImage, yi >> levelSubImage, pix);
    } else {
      // Conversion for ReadMean(): requires discrete coordinates with 0 bits precision
      xi = xi >> 12;
      yi = yi >> 12;
      // Check level
      if (levelSubImage >= nbSubImages)
        levelSubImage = nbSubImages - 1;
      if (levelSubImage <= 0)
        levelSubImage = 0;
      // Read the pixel
      status = subImages[levelSubImage]->ReadMean (xi >> levelSubImage, yi >> levelSubImage, pix);
    }

  } else
    pix = backgroundUsed;

  return status;
}


//  ----------------------------------------------------------------------------
// Determine the dispersion of the alpha channel in a neighborhood: the idea is to compare the local value
// to the value of the same pixel on the next resolution level. Because of the convolution from a level to
// another, one can say that if the pixels are equal, there is no dispersion of the alpha channel, otherwise
// there is some. That way, we avoid effective dispersion computation.
// CAUTION : coordinates must be discrete with 12 bits precision (1 << 12).
Boolean PHierarchicalImage::DispersionAlphaChannel (int xNW,
                                                    int yNW,
                                                    int /*xNE*/,
                                                    int /*yNE*/,
                                                    int /*xSW*/,
                                                    int /*ySW*/,
                                                    int xSE,
                                                    int ySE,
                                                    int levelSubImage)
{
  Boolean dispersion = false;

  if ((Status() == 0) && nbSubImages) {

    /*    The following is the full dispersion algorithm. More computation but less I/O...

    Pixel pix[16];
    // Read the sample
    Boolean status = Read4Points(xNW, yNW, xNE, yNE, xSW, ySW, xSE, ySE, pix, levelSubImage);

    if (status) {
                // Compute the dispersion with the usual algorithm (see CommandeQ2.cp for explanation)
                unsigned char alphaMax = pix[0].alpha;
                unsigned char alphaMin = pix[0].alpha;
                for (long i = 1; i < 16; ++i) {
                alphaMax &= pix[i].alpha;
                alphaMin |= pix[i].alpha;
                }
                dispersion = (alphaMin ^ alphaMax);

                if (!dispersion) {
        // Check if we are on the border to avoid interpolation with the outside of the image
        Boolean onBorder = false;
        if (levelSubImage < 0)
                                levelSubImage = 0;
        if (levelSubImage >= nbSubImages)
                                levelSubImage = nbSubImages - 1;
        long shift = levelSubImage + 12;
        onBorder |= subImages[levelSubImage]->IsOnTheBorder(xNW >> shift, yNW >> shift);
        onBorder |= subImages[levelSubImage]->IsOnTheBorder(xNE >> shift, yNE >> shift);
        onBorder |= subImages[levelSubImage]->IsOnTheBorder(xSW >> shift, ySW >> shift);
        onBorder |= subImages[levelSubImage]->IsOnTheBorder(xSE >> shift, ySE >> shift);
        if (onBorder)
                                dispersion = !(alphaMax == 0);
                                }
                                }
    */
    // Compute central point (>>1) and convert for ReadMean() (>>12)
    // ReadMean() requires discrete coordinates with 0 bits precision
    long xi = (xNW + xSE) >> 13;
    long yi = (yNW + ySE) >> 13;

    // Determine the levels to be compared: the two levels are 2 levels apart to check the
    // stability on a 9x9 neighborhood.
    long nextLevel;

    levelSubImage = levelSubImage - 1;
    if (levelSubImage >= (nbSubImages - 2))
      levelSubImage = nbSubImages - 3;
    if (levelSubImage < 0)
      levelSubImage = 0;
    nextLevel = levelSubImage + 2;
    if (nextLevel >= nbSubImages)
      nextLevel = nbSubImages - 1;

    if (levelSubImage != nextLevel) {
      // Read the pixels in each level
      Pixel pix0, pix1;
      Boolean status  = subImages[levelSubImage]->ReadMean (xi >> levelSubImage, yi >> levelSubImage, pix0);
      status &= subImages[nextLevel]->ReadMean (xi >> nextLevel, yi >> nextLevel, pix1);

      // Compare them and estimate the dispersion value
      dispersion = status && !(pix0.alpha == pix1.alpha);

      // Comparision for the border pixels: the alpha channel outside the image is supposed to be null, so
      // if the pixel is on the border and is not null, there is a dispersion with the outside of the image.
      dispersion |= ((subImages[nextLevel]->IsOnTheBorder(xi >> nextLevel, yi >> nextLevel)) && pix1.alpha);
    }
  }

  return dispersion;
}


//  ----------------------------------------------------------------------------
// Read a rectangle (x0,y0,x1,y1: coordinates at full resolution) in a map
// The correct sub image is read and sampled in order to fill the map with the rectangle
// CAUTION : map must be allocated and freed by calling routine
FPXStatus PHierarchicalImage::ReadSampledRectangle(int x0, int y0, int x1, int y1, Pixel* map, short pixelsPerLine, int mapWidth, int mapHeight, Boolean showAlphaChannel, float ratio)
{

  if (ratio == 0.0){
    if (mapWidth <= MIN((float) mapHeight,(float) (x1-x0),(float) (y1-y0)) ) {
      if ( ABS(float(float(mapWidth) - float(x1-x0)*float(mapHeight)/float(y1-y0))) > 1.0) {
        assert (false);
        return FPX_BAD_COORDINATES;
      }
    } else if (mapHeight <= MIN((float) mapWidth,(float) (x1-x0),(float) (y1-y0)) ) {
      if ( ABS(float(float(mapHeight) - float(y1-y0)*float(mapWidth)/float(x1-x0))) > 1.0) {
        assert (false);
        return FPX_BAD_COORDINATES;
      }
    } else if (x1-x0 <= MIN((float) mapWidth,(float) mapHeight,(float) (y1-y0)) ) {
      if ( ABS(float(float(x1-x0) - float(mapWidth)*float(y1-y0)/float(mapHeight))) > 1.0) {
        assert (false);
        return FPX_BAD_COORDINATES;
      }
    } else if (y1-y0 <= MIN((float) mapWidth,(float) (x1-x0),(float) mapHeight) ) {
      if ( ABS(float(float(y1-y0) - float(mapHeight)*float(x1-x0)/float(mapWidth))) > 1.0 ) {
        assert (false);
        return FPX_BAD_COORDINATES;
      }
    }
    ratio = MIN(float(float(mapWidth)/float(x1 - x0)) , float(float(mapHeight)/float(y1 - y0)));
  }

  if (nbSubImages) {
    long i;

    for (i = nbSubImages-1; i>0; --i)
      if ((subImages[i]->realHeight >= FLOAT_TO_LONG(float(height) * ratio)) && (subImages[i]->realWidth >= FLOAT_TO_LONG(float(width) * ratio)))
        break;

    // compute the coordinates in the choosed sub image
    if (i) {
      x1 >>= i; y1 >>= i;
      x0 >>= i; y0 >>= i;
      ratio *= (float) (pow(2,(double) i));
    }

    // read the rectangle in the correct sub image
    return subImages[i]->ReadSampledRectangle(x0, y0,x1, y1, map, pixelsPerLine, mapWidth, mapHeight, showAlphaChannel, ratio);
  }
  return FPX_OK;
}


//  ----------------------------------------------------------------------------
// Search the top and left corner of the screen pixel which contains the given position
// This function is used when 1 image pixel is represented by more than 1 screen pixel
FPXStatus PHierarchicalImage::SearchPixelTopLeftCorner(int* x1, int* y1, float ratio)
{
  if (nbSubImages) {
    long i;
    for (i = nbSubImages-1; i>0; --i)
      if ((subImages[i]->realHeight >= FLOAT_TO_LONG(float(height) * ratio)) && (subImages[i]->realWidth >= FLOAT_TO_LONG(float(width) * ratio)))
        break;

    // compute the coordinates in the choosed sub image
    if (i)
      ratio *= (float) (pow(2,(double) i));

    // read the rectangle in the correct sub image
    return subImages[i]->SearchPixelTopLeftCorner(x1, y1, ratio);
  }
  return FPX_ERROR;
}


//  ----------------------------------------------------------------------------
// Compute histogram for the 4 channels
FPXStatus PHierarchicalImage::GetHistogram (int* alpha, int* red, int* green, int* blue, int* brightness, const CorrectLut* correctLut)
{
  FPXStatus status = FPX_OK;

  if (Status() == 0 && nbSubImages)
    // Compute only on the last sub-image
    status = subImages[nbSubImages-1]->GetHistogram (alpha, red, green, blue, brightness, correctLut);
  else
    status = FPX_ERROR;

  return status;
}

//  ----------------------------------------------------------------------------
// Make a sub resolution level in create mode
PResolutionLevel* PHierarchicalImage::CreateEmptyResolutionLevel(int , int , int*)
{
  return NULL;
}

//  ----------------------------------------------------------------------------
// Make a sub resolution level in read or write mode
PResolutionLevel* PHierarchicalImage::CreateInitResolutionLevel(int* , long int)
{
  return NULL;
}

//  ----------------------------------------------------------------------------
// Make a resolution list in read or write mode and initialize it
// Set the field 'error' and return it
FPXStatus PHierarchicalImage::CreateInitResolutionLevelList()
{
  return FPX_OK;
}

//  ----------------------------------------------------------------------------
// Make a resolution list in create mode
FPXStatus PHierarchicalImage::CreateEmptyResolutionLevelList()
{
  return FPX_OK;
}

//  ----------------------------------------------------------------------------
// Create the "subImages" array of pointers according to the "firstSubImage" list
FPXStatus PHierarchicalImage::InitResolutionLevelsTable()
{
  PResolutionLevel* img;
  long i;

  // if (nbSubImages == 0) -> the PHierarchicalImage is invalid or impossible to open
  if (nbSubImages) {
    subImages = new PResolutionLevel*[nbSubImages];
    if (subImages)
      for (i = 0, img = firstSubImage; i < nbSubImages; i++, img = img->next)
        subImages[i] = img;
    else {
      nbSubImages = 0;
    }
  } else {
    // Invalid the PHierarchicalImage
    subImages   = NULL;
    nbSubImages = 0;
  }

  return FPX_OK;
}


//  ----------------------------------------------------------------------------
//  External functions used by C code :
//  This is the interface of the Standard Toolkit
//  ----------------------------------------------------------------------------
static Boolean DeleteGlobalArrays ()
{
  return tousLesCodecs->Purge();
}


//  ----------------------------------------------------------------------------
Boolean DemandeMemoireUrgente (long , long size)
{
#ifdef Memoire_Debug
  // FOR DEBUG PURPOSE
  VISU2 "ENTER IN DEMANDEMEMOIREURGENTE()\n" FIN
    VISU2 "Memory requested : %d\n", size FIN
    TraceMemoire();
  // END DEBUG PURPOSE
#endif
  Boolean aPurge        = false;
  Boolean aPurgeImage     = false;
  Boolean aPurgeLockedImage = false;
  Boolean aPurgeCompress    = false;
  long  amountNowFree;
  long  remain = size;

  // Free any tiles not accessed within the last 5 minutes. The hope is that this will
  //  free-up clumps of blocks (i.e., tiles) from a resolution no longer being displayed, etc.
  PTile::FreeAncientBuffers( 2);
  GtheSystemToolkit->AvailableMemory (&amountNowFree);

  // For each iteration of the folowing loop, free the oldest, unlocked buffer used to hold
  //  a tile's 'pixels' or 'rawPixels'
  PTile *foundTile;
  long  isRawPixelsBuffer;

  while ((amountNowFree < size)
   &&   (PTile::FindOldestTileBuffer( &foundTile, &isRawPixelsBuffer) == 0)) {
    if (isRawPixelsBuffer)
      foundTile->FreeRawPixelsBuffer( );
    else
      foundTile->FreePixelsBuffer( );

    GtheSystemToolkit->AvailableMemory (&amountNowFree);
  }

  if (amountNowFree > size)
    return true;

  // Still need more. Purge unlocked image blocks
  PTile::Purge(&remain, 0);
  aPurgeImage = (remain != size ? true : false);
  aPurge = aPurgeImage || aPurge;

  // Purge locked Image if necessary
  if (!aPurge) {
    GtheSystemToolkit->LockImage(NULL);
    PTile::Purge(&remain);
    aPurgeLockedImage = (remain != size ? true : false);
  }
  aPurge = aPurge || aPurgeLockedImage;

  // Delete global arrays if nothing else to purge
  if (!aPurge)
    aPurgeCompress = DeleteGlobalArrays ();
  aPurge = aPurgeCompress || aPurge;

#ifdef Memoire_Debug
  // FOR DEBUG PURPOSE
  if (aPurgeImage) {
    VISU2 "Image purged\n" FIN
      } else {
        VISU2 "Image NOT PURGED\n" FIN
          }
  if (aPurgeLockedImage) {
    VISU2 "Locked Image purged\n" FIN
      } else {
        VISU2 "Locked Image NOT PURGED\n" FIN
          }
  if (aPurgeCompress) {
    VISU2 "Global arrays purged\n" FIN
      } else {
        VISU2 "GLOBAL ARRAYS NOT PURGED\n" FIN
          }
  TraceMemoire();
  PrintAllocatedMemoryStat();
  VISU2 "Exit status = " FIN
    if (!aPurge) {
      VISU2 "Error, DemandeMemoireUrgente FAILED\n" FIN
  } else {
          VISU2 "OK\n" FIN
            }
  VISU2 "Exit of DemandeMemoireUrgente()\n\n\n" FIN
    // END DEBUG PURPOSE
#endif

    return aPurge;
}


//  - EOF ----------------------------------------------------------------------
