/******************************************************************************
 * $Id: geotiff.cpp 34555 2016-07-05 18:39:17Z rouault $
 *
 * Project:  GeoTIFF Driver
 * Purpose:  GDAL GeoTIFF support.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1998, 2002, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2007-2015, Even Rouault <even dot rouault at spatialys dot com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

/* If we use sunpro compiler on linux. Weird idea indeed ! */
#if defined(__SUNPRO_CC) && defined(__linux__)
#define _GNU_SOURCE
#elif defined(__GNUC__) && !defined(_GNU_SOURCE)
/* Required to use RTLD_DEFAULT of dlfcn.h */
#define _GNU_SOURCE
#endif

#include "cpl_port.h"  // Must be first.

#include <set>

#include "cpl_csv.h"
#include "cplkeywordparser.h"
#include "cpl_minixml.h"
#include "cpl_multiproc.h"
#include "cpl_string.h"
#include "cpl_vsi_virtual.h"
#include "cpl_worker_thread_pool.h"
#include "gdal_csv.h"
#include "gdal_frmts.h"
#include "gdal_mdreader.h"
#include "gdal_pam.h"
#include "geovalues.h"
#include "gt_jpeg_copy.h"
#include "gt_overview.h"
#include "gt_wkt_srs.h"
#include "gt_wkt_srs_priv.h"
#include "gtiff.h"
#include "ogr_spatialref.h"
#include "tif_float.h"
#ifdef INTERNAL_LIBTIFF
#  include "tiffiop.h"
#endif
#include "tifvsi.h"
#include "xtiffio.h"

CPL_CVSID("$Id: geotiff.cpp 34555 2016-07-05 18:39:17Z rouault $");

#if SIZEOF_VOIDP == 4
static int bGlobalStripIntegerOverflow = FALSE;
#endif
static bool bGlobalInExternalOvr = false;

typedef enum
{
    GTIFFTAGTYPE_STRING,
    GTIFFTAGTYPE_SHORT,
    GTIFFTAGTYPE_FLOAT
} GTIFFTagTypes;

typedef struct
{
    const char    *pszTagName;
    int            nTagVal;
    GTIFFTagTypes  eType;
} GTIFFTags;

static const GTIFFTags asTIFFTags[] =
{
    { "TIFFTAG_DOCUMENTNAME", TIFFTAG_DOCUMENTNAME, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_IMAGEDESCRIPTION", TIFFTAG_IMAGEDESCRIPTION, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_SOFTWARE", TIFFTAG_SOFTWARE, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_DATETIME", TIFFTAG_DATETIME, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_ARTIST", TIFFTAG_ARTIST, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_HOSTCOMPUTER", TIFFTAG_HOSTCOMPUTER, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_COPYRIGHT", TIFFTAG_COPYRIGHT, GTIFFTAGTYPE_STRING },
    { "TIFFTAG_XRESOLUTION", TIFFTAG_XRESOLUTION, GTIFFTAGTYPE_FLOAT },
    { "TIFFTAG_YRESOLUTION", TIFFTAG_YRESOLUTION, GTIFFTAGTYPE_FLOAT },
    { "TIFFTAG_RESOLUTIONUNIT", TIFFTAG_RESOLUTIONUNIT, GTIFFTAGTYPE_SHORT }, /* dealt as special case */
    { "TIFFTAG_MINSAMPLEVALUE", TIFFTAG_MINSAMPLEVALUE, GTIFFTAGTYPE_SHORT },
    { "TIFFTAG_MAXSAMPLEVALUE", TIFFTAG_MAXSAMPLEVALUE, GTIFFTAGTYPE_SHORT },
};

/************************************************************************/
/*                            IsPowerOfTwo()                            */
/************************************************************************/

static bool IsPowerOfTwo(unsigned int i)
{
    int nBitSet = 0;
    while(i != 0)
    {
        if ((i & 1))
            nBitSet ++;
        i >>= 1;
    }
    return nBitSet == 1;
}

/************************************************************************/
/*                          GTIFFSetInExternalOvr()                     */
/************************************************************************/

void GTIFFSetInExternalOvr(bool b)
{
    bGlobalInExternalOvr = b;
}

/************************************************************************/
/*                     GTIFFGetOverviewBlockSize()                      */
/************************************************************************/

void GTIFFGetOverviewBlockSize(int* pnBlockXSize, int* pnBlockYSize)
{
    static bool bHasWarned = false;
    const char* pszVal = CPLGetConfigOption("GDAL_TIFF_OVR_BLOCKSIZE", "128");
    int nOvrBlockSize = atoi(pszVal);
    if (nOvrBlockSize < 64 || nOvrBlockSize > 4096 ||
        !IsPowerOfTwo(nOvrBlockSize))
    {
        if (!bHasWarned)
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                    "Wrong value for GDAL_TIFF_OVR_BLOCKSIZE : %s. "
                    "Should be a power of 2 between 64 and 4096. Defaulting to 128",
                    pszVal);
            bHasWarned = true;
        }
        nOvrBlockSize = 128;
    }

    *pnBlockXSize = nOvrBlockSize;
    *pnBlockYSize = nOvrBlockSize;
}

enum
{
    ENDIANNESS_NATIVE,
    ENDIANNESS_LITTLE,
    ENDIANNESS_BIG
};

/************************************************************************/
/* ==================================================================== */
/*                          GTiffDataset                                */
/* ==================================================================== */
/************************************************************************/

class GTiffRasterBand;
class GTiffRGBABand;
class GTiffBitmapBand;
class GTiffJPEGOverviewDS;
class GTiffJPEGOverviewBand;

typedef enum
{
    VIRTUAL_MEM_IO_NO,
    VIRTUAL_MEM_IO_YES,
    VIRTUAL_MEM_IO_IF_ENOUGH_RAM
} VirtualMemIOEnum;

class GTiffDataset;
typedef struct
{
    GTiffDataset *poDS;
    int           bTIFFIsBigEndian;
    char         *pszTmpFilename;
    int           nHeight;
    uint16        nPredictor;
    GByte        *pabyBuffer;
    int           nBufferSize;
    int           nStripOrTile;

    GByte        *pabyCompressedBuffer; /* owned by pszTmpFilename */
    int           nCompressedBufferSize;
    int           bReady;
} GTiffCompressionJob;

class GTiffDataset CPL_FINAL : public GDALPamDataset
{
    friend class GTiffRasterBand;
    friend class GTiffSplitBand;
    friend class GTiffRGBABand;
    friend class GTiffBitmapBand;
    friend class GTiffSplitBitmapBand;
    friend class GTiffOddBitsBand;
    friend class GTiffJPEGOverviewDS;
    friend class GTiffJPEGOverviewBand;

    friend void    GTIFFSetJpegQuality(GDALDatasetH hGTIFFDS, int nJpegQuality);

    TIFF       *hTIFF;
    VSILFILE   *fpL;
    int         bStreamingIn;

    int         bStreamingOut;
    CPLString   osTmpFilename;
    VSILFILE*   fpToWrite;
    int         nLastWrittenBlockId;

    GTiffDataset **ppoActiveDSRef;
    GTiffDataset *poActiveDS; /* only used in actual base */

    int         bScanDeferred;
    void        ScanDirectories();

    toff_t      nDirOffset;
    int         bBase;
    int         bCloseTIFFHandle; /* useful for closing TIFF handle opened by GTIFF_DIR: */

    uint16      nPlanarConfig;
    uint16      nSamplesPerPixel;
    uint16      nBitsPerSample;
    uint32      nRowsPerStrip;
    uint16      nPhotometric;
    uint16      nSampleFormat;
    uint16      nCompression;

    int         nBlocksPerBand;

    uint32      nBlockXSize;
    uint32      nBlockYSize;

    int         nLoadedBlock;  /* or tile */
    int         bLoadedBlockDirty;
    GByte       *pabyBlockBuf;

    CPLErr      LoadBlockBuf( int nBlockId, int bReadFromDisk = TRUE );
    CPLErr      FlushBlockBuf();
    int         bWriteErrorInFlushBlockBuf;

    char        *pszProjection;
    int         bLookedForProjection;
    int         bLookedForMDAreaOrPoint;

    void        LoadMDAreaOrPoint();
    void        LookForProjection();
#ifdef ESRI_BUILD
    void        AdjustLinearUnit( short UOMLength );
#endif

    double      adfGeoTransform[6];
    int         bGeoTransformValid;

    int         bTreatAsRGBA;
    int         bCrystalized;
    bool        bCheckIfColorInterpMustGoToPamAtCrystalization;

    void        Crystalize();  // TODO: Spelling.

    GDALColorTable *poColorTable;

    void        WriteGeoTIFFInfo();
    int         SetDirectory( toff_t nDirOffset = 0 );

    int         nOverviewCount;
    GTiffDataset **papoOverviewDS;

    int         nJPEGOverviewVisibilityFlag; /* if > 0, the implicit JPEG overviews are visible through GetOverviewCount() */
    int         nJPEGOverviewCount; /* currently visible overviews. Generally == nJPEGOverviewCountOri */
    int         nJPEGOverviewCountOri; /* size of papoJPEGOverviewDS */
    GTiffJPEGOverviewDS **papoJPEGOverviewDS;
    int         GetJPEGOverviewCount();

    int         nGCPCount;
    GDAL_GCP    *pasGCPList;

    int         IsBlockAvailable( int nBlockId );

    int         bGeoTIFFInfoChanged;
    int         bForceUnsetGTOrGCPs;
    int         bForceUnsetProjection;

    int         bNoDataChanged;
    int         bNoDataSet;
    double      dfNoDataValue;

    int         bMetadataChanged;
    int         bColorProfileMetadataChanged;

    int         bNeedsRewrite;

    void        ApplyPamInfo();
    void        PushMetadataToPam();

    GDALMultiDomainMetadata oGTiffMDMD;

    CPLString   osProfile;
    char      **papszCreationOptions;

    int         bLoadingOtherBands;

    void*        pabyTempWriteBuffer;
    int          nTempWriteBufferSize;
    bool         WriteEncodedTile(uint32 tile, GByte* pabyData, int bPreserveDataBuffer);
    bool         WriteEncodedStrip(uint32 strip, GByte* pabyData, int bPreserveDataBuffer);

    GTiffDataset* poMaskDS;
    GTiffDataset* poBaseDS;

    CPLString    osFilename;

    int          bFillEmptyTiles;
    void         FillEmptyTiles(void);

    void         FlushDirectory();
    CPLErr       CleanOverviews();

    /* Used for the all-in-on-strip case */
    int           nLastLineRead;
    int           nLastBandRead;
    int           bTreatAsSplit;
    int           bTreatAsSplitBitmap;

    int           bClipWarn;

    int           bIMDRPCMetadataLoaded;
    char**        papszMetadataFiles;
    void          LoadMetadata();

    int           bEXIFMetadataLoaded;
    void          LoadEXIFMetadata();

    int           bICCMetadataLoaded;
    void          LoadICCProfile();

    int           bHasWarnedDisableAggressiveBandCaching;

    int           bDontReloadFirstBlock; /* Hack for libtiff 3.X and #3633 */

    int           nZLevel;
    int           nLZMAPreset;
    int           nJpegQuality;
    int           nJpegTablesMode;

    int           bPromoteTo8Bits;

    int           bDebugDontWriteBlocks;

    CPLErr        RegisterNewOverviewDataset(toff_t nOverviewOffset);
    CPLErr        CreateOverviewsFromSrcOverviews(GDALDataset* poSrcDS);
    CPLErr        CreateInternalMaskOverviews(int nOvrBlockXSize,
                                              int nOvrBlockYSize);

    int           bIsFinalized;
    int           Finalize();

    int           bIgnoreReadErrors;

    CPLString     osGeorefFilename;

    int           bDirectIO;

    VirtualMemIOEnum eVirtualMemIOUsage;
    CPLVirtualMem* psVirtualMemIOMapping;

    GTIFFKeysFlavorEnum eGeoTIFFKeysFlavor;

    CPLVirtualMem *pBaseMapping;
    int            nRefBaseMapping;

    int            bHasDiscardedLsb;
    std::vector<int> anMaskLsb, anOffsetLsb;
    void           DiscardLsb(GByte* pabyBuffer, int nBytes, int iBand);
    void           GetDiscardLsbOption(char** papszOptions);

    CPLWorkerThreadPool *poCompressThreadPool;
    std::vector<GTiffCompressionJob> asCompressionJobs;
    CPLMutex      *hCompressThreadPoolMutex;
    void           InitCompressionThreads(char** papszOptions);
    void           InitCreationOrOpenOptions(char** papszOptions);
    static void    ThreadCompressionFunc(void* pData);
    void           WaitCompletionForBlock(int nBlockId);
    void           WriteRawStripOrTile(int nStripOrTile,
                                 GByte* pabyCompressedBuffer,
                                 int nCompressedBufferSize);
    int            SubmitCompressionJob(int nStripOrTile, GByte* pabyData,
                                        int cc, int nHeight);

    int            GuessJPEGQuality(int& bOutHasQuantizationTable,
                                    int& bOutHasHuffmanTable);

    int            DirectIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg );

    int            VirtualMemIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg );

    GByte          *m_pTempBufferForCommonDirectIO;
    size_t          m_nTempBufferForCommonDirectIOSize;
    template<class FetchBuffer> CPLErr CommonDirectIO(
                               FetchBuffer& oFetcher,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace );

    bool        m_bReadGeoTransform;
    bool        m_bLoadPam;
    void        LoadGeoreferencingAndPamIfNeeded();

    bool        m_bHasGotSiblingFiles;
    char      **GetSiblingFiles();

    void        FlushCacheInternal( bool bFlushDirectory );

  protected:
    virtual int         CloseDependentDatasets();

  public:
                 GTiffDataset();
                 ~GTiffDataset();

    virtual const char *GetProjectionRef(void);
    virtual CPLErr SetProjection( const char * );
    virtual CPLErr GetGeoTransform( double * );
    virtual CPLErr SetGeoTransform( double * );

    virtual int    GetGCPCount();
    virtual const char *GetGCPProjection();
    virtual const GDAL_GCP *GetGCPs();
    CPLErr         SetGCPs( int, const GDAL_GCP *, const char * );

    virtual CPLErr IRasterIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg);
    virtual char **GetFileList(void);

    virtual CPLErr IBuildOverviews( const char *, int, int *, int, int *,
                                    GDALProgressFunc, void * );

    CPLErr         OpenOffset( TIFF *, GTiffDataset **ppoActiveDSRef,
                               toff_t nDirOffset, int bBaseIn, GDALAccess,
                               int bAllowRGBAInterface = TRUE, bool bReadGeoTransform = false);

    static GDALDataset *OpenDir( GDALOpenInfo * );
    static GDALDataset *Open( GDALOpenInfo * );
    static int          Identify( GDALOpenInfo * );
    static GDALDataset *Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType, char ** papszParmList );
    static GDALDataset *CreateCopy( const char * pszFilename,
                                    GDALDataset *poSrcDS,
                                    int bStrict, char ** papszOptions,
                                    GDALProgressFunc pfnProgress,
                                    void * pProgressData );
    virtual void    FlushCache( void );

    virtual char      **GetMetadataDomainList();
    virtual CPLErr  SetMetadata( char **, const char * = "" );
    virtual char  **GetMetadata( const char * pszDomain = "" );
    virtual CPLErr  SetMetadataItem( const char*, const char*,
                                     const char* = "" );
    virtual const char *GetMetadataItem( const char * pszName,
                                         const char * pszDomain = "" );
    virtual void   *GetInternalHandle( const char * );

    virtual CPLErr          CreateMaskBand( int nFlags );

    // only needed by createcopy and close code.
    static void     WriteRPC( GDALDataset *, TIFF *, int, const char *,
                                   const char *, char **, int bWriteOnlyInPAMIfNeeded = FALSE );
    static int      WriteMetadata( GDALDataset *, TIFF *, int, const char *,
                                   const char *, char **, int bExcludeRPBandIMGFileWriting = FALSE );
    static void     WriteNoDataValue( TIFF *, double );
    static void     UnsetNoDataValue( TIFF * );

    static TIFF *   CreateLL( const char * pszFilename,
                              int nXSize, int nYSize, int nBands,
                              GDALDataType eType,
                              double dfExtraSpaceForOverviews,
                              char **papszParmList,
                              VSILFILE** pfpL,
                              CPLString& osTmpFilename);

    CPLErr   WriteEncodedTileOrStrip(uint32 tile_or_strip, void* data, int bPreserveDataBuffer);

    static void SaveICCProfile(GTiffDataset *pDS, TIFF *hTIFF, char **papszParmList, uint32 nBitsPerSample);
};


/************************************************************************/
/* ==================================================================== */
/*                        GTiffJPEGOverviewDS                           */
/* ==================================================================== */
/************************************************************************/

class GTiffJPEGOverviewDS CPL_FINAL : public GDALDataset
{
        friend class GTiffJPEGOverviewBand;
        GTiffDataset* poParentDS;
        int nOverviewLevel;

        int        nJPEGTableSize;
        GByte     *pabyJPEGTable;
        CPLString  osTmpFilenameJPEGTable;

        CPLString    osTmpFilename;
        GDALDataset* poJPEGDS;
        int          nBlockId; /* valid block id of the parent DS that match poJPEGDS */

    public:
        GTiffJPEGOverviewDS(GTiffDataset* poParentDS, int nOverviewLevel,
                            const void* pJPEGTable, int nJPEGTableSize);
       ~GTiffJPEGOverviewDS();

       virtual CPLErr IRasterIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg);
};

class GTiffJPEGOverviewBand CPL_FINAL : public GDALRasterBand
{
    public:
        GTiffJPEGOverviewBand(GTiffJPEGOverviewDS* poDS, int nBand);

        virtual CPLErr IReadBlock( int, int, void * );
};

/************************************************************************/
/*                        GTiffJPEGOverviewDS()                         */
/************************************************************************/

GTiffJPEGOverviewDS::GTiffJPEGOverviewDS(GTiffDataset* poParentDSIn, int nOverviewLevelIn,
                                         const void* pJPEGTable, int nJPEGTableSizeIn) :
    nJPEGTableSize(nJPEGTableSizeIn),
    poJPEGDS(NULL),
    nBlockId(-1)
{
    poParentDS = poParentDSIn;
    nOverviewLevel = nOverviewLevelIn;

    osTmpFilenameJPEGTable.Printf("/vsimem/jpegtable_%p", this);

    const GByte abyAdobeAPP14RGB[] = {
        0xFF, 0xEE, 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x00,
        0x64, 0x00, 0x00, 0x00, 0x00, 0x00 };
    bool bAddAdobe = ( poParentDS->nPlanarConfig == PLANARCONFIG_CONTIG &&
                       poParentDS->nPhotometric != PHOTOMETRIC_YCBCR && poParentDS->nBands == 3 );
    pabyJPEGTable = (GByte*) CPLMalloc(nJPEGTableSize + ((bAddAdobe) ? sizeof(abyAdobeAPP14RGB) : 0));
    memcpy(pabyJPEGTable, pJPEGTable, nJPEGTableSize);
    if( bAddAdobe )
    {
        memcpy(pabyJPEGTable + nJPEGTableSize, abyAdobeAPP14RGB, sizeof(abyAdobeAPP14RGB));
        nJPEGTableSize += sizeof(abyAdobeAPP14RGB);
    }
    CPL_IGNORE_RET_VAL(VSIFCloseL(VSIFileFromMemBuffer( osTmpFilenameJPEGTable, pabyJPEGTable, nJPEGTableSize, TRUE )));

    int nScaleFactor = 1 << nOverviewLevel;
    nRasterXSize = (poParentDS->nRasterXSize + nScaleFactor - 1) / nScaleFactor;
    nRasterYSize = (poParentDS->nRasterYSize + nScaleFactor - 1) / nScaleFactor;

    for(int i=1;i<=poParentDS->nBands;i++)
        SetBand(i, new GTiffJPEGOverviewBand(this, i));

    SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
    if ( poParentDS->nPhotometric == PHOTOMETRIC_YCBCR )
        SetMetadataItem( "COMPRESSION", "YCbCr JPEG", "IMAGE_STRUCTURE" );
    else
        SetMetadataItem( "COMPRESSION", "JPEG", "IMAGE_STRUCTURE" );
}

/************************************************************************/
/*                       ~GTiffJPEGOverviewDS()                         */
/************************************************************************/

GTiffJPEGOverviewDS::~GTiffJPEGOverviewDS()
{
    if( poJPEGDS != NULL )
        GDALClose( (GDALDatasetH) poJPEGDS );
    VSIUnlink(osTmpFilenameJPEGTable);
    if( osTmpFilename.size() )
        VSIUnlink(osTmpFilename);
}

/************************************************************************/
/*                            IRasterIO()                               */
/************************************************************************/

CPLErr GTiffJPEGOverviewDS::IRasterIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg)

{
    /* For non-single strip JPEG-IN-TIFF, the block based strategy will */
    /* be the most efficient one, to avoid decompressing the JPEG content */
    /* for each requested band */
    if( nBandCount > 1 && poParentDS->nPlanarConfig == PLANARCONFIG_CONTIG &&
        ((int)poParentDS->nBlockXSize < poParentDS->nRasterXSize ||
        poParentDS->nBlockYSize > 1) )
    {
        return BlockBasedRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                   pData, nBufXSize, nBufYSize,
                                   eBufType, nBandCount, panBandMap,
                                   nPixelSpace, nLineSpace, nBandSpace, psExtraArg );
    }
    else
    {
        return GDALDataset::IRasterIO(
                eRWFlag, nXOff, nYOff, nXSize, nYSize,
                pData, nBufXSize, nBufYSize, eBufType,
                nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace, psExtraArg);
    }

}

/************************************************************************/
/*                        GTiffJPEGOverviewBand()                       */
/************************************************************************/

GTiffJPEGOverviewBand::GTiffJPEGOverviewBand(GTiffJPEGOverviewDS* poDSIn, int nBandIn)
{
    poDS = poDSIn;
    nBand = nBandIn;
    eDataType = poDSIn->poParentDS->GetRasterBand(nBandIn)->GetRasterDataType();
    poDSIn->poParentDS->GetRasterBand(nBandIn)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    const int nScaleFactor = 1 << poDSIn->nOverviewLevel;
    nBlockXSize = (nBlockXSize + nScaleFactor - 1) / nScaleFactor;
    nBlockYSize = (nBlockYSize + nScaleFactor - 1) / nScaleFactor;
}

/************************************************************************/
/*                          IReadBlock()                                */
/************************************************************************/

CPLErr GTiffJPEGOverviewBand::IReadBlock( int nBlockXOff, int nBlockYOff, void *pImage )
{
    GTiffJPEGOverviewDS* poGDS = (GTiffJPEGOverviewDS*)poDS;

    /* Compute the source block ID */
    int nBlockId;
    if( nBlockYSize == 1 )
    {
        nBlockId = 0;
    }
    else
    {
        nBlocksPerRow = DIV_ROUND_UP(poGDS->poParentDS->nRasterXSize,
                                               poGDS->poParentDS->nBlockXSize);
        nBlockId = nBlockYOff * nBlocksPerRow + nBlockXOff;
    }
    if( poGDS->poParentDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
    {
        nBlockId += (nBand-1) * poGDS->poParentDS->nBlocksPerBand;
    }

    if( !poGDS->poParentDS->SetDirectory() )
        return CE_Failure;

    /* Make sure it is available */
    const int nDataTypeSize = GDALGetDataTypeSize(eDataType)/8;
    if( !poGDS->poParentDS->IsBlockAvailable(nBlockId) )
    {
        memset(pImage, 0, nBlockXSize * nBlockYSize * nDataTypeSize );
        return CE_None;
    }

    int nScaleFactor = 1 << poGDS->nOverviewLevel;
    if( poGDS->poJPEGDS == NULL || nBlockId != poGDS->nBlockId )
    {
        toff_t *panByteCounts = NULL;
        toff_t *panOffsets = NULL;
        vsi_l_offset nOffset = 0;
        vsi_l_offset nByteCount = 0;

        /* Find offset and size of the JPEG tile/strip */
        TIFF* hTIFF = poGDS->poParentDS->hTIFF;
        if( (( TIFFIsTiled( hTIFF )
            && TIFFGetField( hTIFF, TIFFTAG_TILEBYTECOUNTS, &panByteCounts )
            && TIFFGetField( hTIFF, TIFFTAG_TILEOFFSETS, &panOffsets ) )
            || ( !TIFFIsTiled( hTIFF )
            && TIFFGetField( hTIFF, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts )
            && TIFFGetField( hTIFF, TIFFTAG_STRIPOFFSETS, &panOffsets ) )) &&
            panByteCounts != NULL && panOffsets != NULL )
        {
            if( panByteCounts[nBlockId] < 2 )
                return CE_Failure;
            nOffset = panOffsets[nBlockId] + 2; /* skip leading 0xFF 0xF8 */
            nByteCount = panByteCounts[nBlockId] - 2;
        }
        else
        {
            return CE_Failure;
        }

        /* Special case for last strip that might be smaller than other strips */
        /* In which case we must invalidate the dataset */
        if( !TIFFIsTiled( hTIFF ) && poGDS->poParentDS->nBlockYSize > 1 &&
            (nBlockYOff + 1 == (int)DIV_ROUND_UP(poGDS->poParentDS->nRasterYSize, poGDS->poParentDS->nBlockYSize) ||
             (poGDS->poJPEGDS != NULL && poGDS->poJPEGDS->GetRasterYSize() != nBlockYSize * nScaleFactor)) )
        {
            if( poGDS->poJPEGDS != NULL )
                GDALClose( (GDALDatasetH) poGDS->poJPEGDS );
            poGDS->poJPEGDS = NULL;
        }

        CPLString osFileToOpen;
        poGDS->osTmpFilename.Printf("/vsimem/sparse_%p", poGDS);
        VSILFILE* fp = VSIFOpenL(poGDS->osTmpFilename, "wb+");

        /* If the size of the JPEG strip/tile is small enough, we will */
        /* read it from the TIFF file and forge a in-memory JPEG file with */
        /* the JPEG table followed by the JPEG data. */
        bool bInMemoryJPEGFile = ( nByteCount < 256 * 256 );
        if( bInMemoryJPEGFile )
        {
            /* If the previous file was opened as a /vsisparse/, we have to re-open */
            if( poGDS->poJPEGDS != NULL &&
                STARTS_WITH(poGDS->poJPEGDS->GetDescription(), "/vsisparse/") )
            {
                GDALClose( (GDALDatasetH) poGDS->poJPEGDS );
                poGDS->poJPEGDS = NULL;
            }
            osFileToOpen = poGDS->osTmpFilename;

            bool bError = FALSE;
            if( VSIFSeekL(fp, poGDS->nJPEGTableSize + nByteCount - 1, SEEK_SET) != 0 )
                bError = true;
            char ch = 0;
            if( !bError && VSIFWriteL(&ch, 1, 1, fp) != 1 )
                bError = true;
            GByte* pabyBuffer = VSIGetMemFileBuffer( poGDS->osTmpFilename, NULL, FALSE);
            memcpy(pabyBuffer, poGDS->pabyJPEGTable, poGDS->nJPEGTableSize);
            VSILFILE* fpTIF = VSI_TIFFGetVSILFile(TIFFClientdata( hTIFF ));
            if( !bError && VSIFSeekL(fpTIF, nOffset, SEEK_SET) != 0 )
                bError = true;
            if( VSIFReadL(pabyBuffer + poGDS->nJPEGTableSize, (size_t)nByteCount, 1, fpTIF) != 1 )
                bError = true;
            if( bError )
            {
                CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
                return CE_Failure;
            }
        }
        else
        {
            /* If the JPEG strip/tile is too big (e.g. a single-strip JPEG-in-TIFF) */
            /* we will use /vsisparse mechanism to make a fake JPEG file */

            /* Always re-open */
            GDALClose( (GDALDatasetH) poGDS->poJPEGDS );
            poGDS->poJPEGDS = NULL;

            osFileToOpen = CPLSPrintf("/vsisparse/%s", poGDS->osTmpFilename.c_str());

            if( VSIFPrintfL(fp, "<VSISparseFile><SubfileRegion><Filename relative='0'>%s</Filename>"
                        "<DestinationOffset>0</DestinationOffset>"
                        "<SourceOffset>0</SourceOffset>"
                        "<RegionLength>%d</RegionLength>"
                        "</SubfileRegion>"
                        "<SubfileRegion>"
                        "<Filename relative='0'>%s</Filename>"
                        "<DestinationOffset>%d</DestinationOffset>"
                        "<SourceOffset>" CPL_FRMT_GUIB "</SourceOffset>"
                        "<RegionLength>" CPL_FRMT_GUIB "</RegionLength>"
                        "</SubfileRegion></VSISparseFile>",
                        poGDS->osTmpFilenameJPEGTable.c_str(),
                        (int)poGDS->nJPEGTableSize,
                        poGDS->poParentDS->GetDescription(),
                        (int)poGDS->nJPEGTableSize,
                        nOffset,
                        nByteCount) < 0 )
            {
                CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
                return CE_Failure;
            }
        }
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));

        if( poGDS->poJPEGDS == NULL )
        {
            const char* apszDrivers[] = { "JPEG", NULL };
            
            CPLString osOldVal;
            if( poGDS->poParentDS->nPlanarConfig == PLANARCONFIG_CONTIG && poGDS->nBands == 4 )
            {
                osOldVal = CPLGetThreadLocalConfigOption("GDAL_JPEG_TO_RGB", "");
                CPLSetThreadLocalConfigOption("GDAL_JPEG_TO_RGB", "NO");
            }

            poGDS->poJPEGDS = (GDALDataset*) GDALOpenEx(osFileToOpen,
                                                        GDAL_OF_RASTER | GDAL_OF_INTERNAL,
                                                                apszDrivers,
                                                                NULL, NULL);
            if( poGDS->poJPEGDS != NULL )
            {
                /* Force all implicit overviews to be available, even for small tiles */
                CPLSetThreadLocalConfigOption("JPEG_FORCE_INTERNAL_OVERVIEWS", "YES");
                GDALGetOverviewCount(GDALGetRasterBand(poGDS->poJPEGDS, 1));
                CPLSetThreadLocalConfigOption("JPEG_FORCE_INTERNAL_OVERVIEWS", NULL);

                poGDS->nBlockId = nBlockId;
            }
            
            if( poGDS->poParentDS->nPlanarConfig == PLANARCONFIG_CONTIG && poGDS->nBands == 4 )
            {
                CPLSetThreadLocalConfigOption("GDAL_JPEG_TO_RGB", osOldVal.size() ? osOldVal.c_str() : NULL);
            }
        }
        else
        {
            /* Trick: we invalidate the JPEG dataset to force a reload */
            /* of the new content */
            CPLErrorReset();
            poGDS->poJPEGDS->FlushCache();
            if( CPLGetLastErrorNo() != 0 )
            {
                GDALClose( (GDALDatasetH) poGDS->poJPEGDS );
                poGDS->poJPEGDS = NULL;
                return CE_Failure;
            }
            poGDS->nBlockId = nBlockId;
        }
    }

    CPLErr eErr = CE_Failure;
    if( poGDS->poJPEGDS )
    {
        GDALDataset* l_poDS = poGDS->poJPEGDS;

        int nReqXOff = 0, nReqYOff, nReqXSize, nReqYSize;
        if( nBlockYSize == 1 )
        {
            nReqYOff = nBlockYOff * nScaleFactor;
            nReqXSize = l_poDS->GetRasterXSize();
            nReqYSize = nScaleFactor;
        }
        else
        {
            nReqYOff = 0;
            nReqXSize = nBlockXSize * nScaleFactor;
            nReqYSize = nBlockYSize * nScaleFactor;
        }
        int nBufXSize = nBlockXSize;
        int nBufYSize = nBlockYSize;
        if( nReqXOff + nReqXSize > l_poDS->GetRasterXSize() )
        {
            nReqXSize = l_poDS->GetRasterXSize() - nReqXOff;
            nBufXSize = nReqXSize / nScaleFactor;
            if( nBufXSize == 0 ) nBufXSize = 1;
        }
        if( nReqYOff + nReqYSize > l_poDS->GetRasterYSize() )
        {
            nReqYSize = l_poDS->GetRasterYSize() - nReqYOff;
            nBufYSize = nReqYSize / nScaleFactor;
            if( nBufYSize == 0 ) nBufYSize = 1;
        }

        int nSrcBand = ( poGDS->poParentDS->nPlanarConfig == PLANARCONFIG_SEPARATE ) ? 1 : nBand;
        if( nSrcBand <= l_poDS->GetRasterCount() )
        {
            eErr = l_poDS->GetRasterBand(nSrcBand)->RasterIO(GF_Read,
                                 nReqXOff, nReqYOff, nReqXSize, nReqYSize,
                                 pImage,
                                 nBufXSize, nBufYSize, eDataType,
                                 0, nBlockXSize * nDataTypeSize, NULL );
        }
    }

    return eErr;
}

/************************************************************************/
/*                        GTIFFSetJpegQuality()                         */
/* Called by GTIFFBuildOverviews() to set the jpeg quality on the IFD   */
/* of the .ovr file                                                     */
/************************************************************************/

void GTIFFSetJpegQuality(GDALDatasetH hGTIFFDS, int nJpegQuality)
{
    CPLAssert(EQUAL(GDALGetDriverShortName(GDALGetDatasetDriver(hGTIFFDS)), "GTIFF"));

    GTiffDataset* poDS = (GTiffDataset*)hGTIFFDS;
    poDS->nJpegQuality = nJpegQuality;

    poDS->ScanDirectories();

    for(int i=0;i<poDS->nOverviewCount;i++)
        poDS->papoOverviewDS[i]->nJpegQuality = nJpegQuality;
}

/************************************************************************/
/* ==================================================================== */
/*                            GTiffRasterBand                           */
/* ==================================================================== */
/************************************************************************/

class GTiffRasterBand : public GDALPamRasterBand
{
    friend class GTiffDataset;

    GDALColorInterp    eBandInterp;

    int                bHaveOffsetScale;
    double             dfOffset;
    double             dfScale;
    CPLString          osUnitType;
    CPLString          osDescription;

    int                DirectIO( GDALRWFlag eRWFlag,
                                  int nXOff, int nYOff, int nXSize, int nYSize,
                                  void * pData, int nBufXSize, int nBufYSize,
                                  GDALDataType eBufType,
                                  GSpacing nPixelSpace, GSpacing nLineSpace,
                                  GDALRasterIOExtraArg* psExtraArg );

    std::set<GTiffRasterBand **> aSetPSelf;
    static void     DropReferenceVirtualMem(void* pUserData);
    CPLVirtualMem * GetVirtualMemAutoInternal( GDALRWFlag eRWFlag,
                                               int *pnPixelSpace,
                                               GIntBig *pnLineSpace,
                                               char **papszOptions );
protected:
    GTiffDataset       *poGDS;
    GDALMultiDomainMetadata oGTiffMDMD;

    int                bNoDataSet;
    double             dfNoDataValue;

    void NullBlock( void *pData );
    CPLErr FillCacheForOtherBands( int nBlockXOff, int nBlockYOff );

public:
                   GTiffRasterBand( GTiffDataset *, int );
                  ~GTiffRasterBand();

    virtual CPLErr IReadBlock( int, int, void * );
    virtual CPLErr IWriteBlock( int, int, void * );

    virtual CPLErr IRasterIO( GDALRWFlag eRWFlag,
                                  int nXOff, int nYOff, int nXSize, int nYSize,
                                  void * pData, int nBufXSize, int nBufYSize,
                                  GDALDataType eBufType,
                                  GSpacing nPixelSpace, GSpacing nLineSpace,
                                  GDALRasterIOExtraArg* psExtraArg ) CPL_FINAL;

    virtual const char *GetDescription() const CPL_FINAL;
    virtual void        SetDescription( const char * ) CPL_FINAL;

    virtual GDALColorInterp GetColorInterpretation() /*CPL_FINAL*/;
    virtual GDALColorTable *GetColorTable() /*CPL_FINAL*/;
    virtual CPLErr          SetColorTable( GDALColorTable * ) CPL_FINAL;
    virtual double          GetNoDataValue( int * ) CPL_FINAL;
    virtual CPLErr          SetNoDataValue( double ) CPL_FINAL;
    virtual CPLErr DeleteNoDataValue() CPL_FINAL;

    virtual double GetOffset( int *pbSuccess = NULL ) CPL_FINAL;
    virtual CPLErr SetOffset( double dfNewValue ) CPL_FINAL;
    virtual double GetScale( int *pbSuccess = NULL ) CPL_FINAL;
    virtual CPLErr SetScale( double dfNewValue ) CPL_FINAL;
    virtual const char* GetUnitType() CPL_FINAL;
    virtual CPLErr SetUnitType( const char *pszNewValue ) CPL_FINAL;
    virtual CPLErr SetColorInterpretation( GDALColorInterp ) CPL_FINAL;

    virtual char      **GetMetadataDomainList() CPL_FINAL;
    virtual CPLErr  SetMetadata( char **, const char * = "" ) CPL_FINAL;
    virtual char  **GetMetadata( const char * pszDomain = "" ) CPL_FINAL;
    virtual CPLErr  SetMetadataItem( const char*, const char*,
                                     const char* = "" ) CPL_FINAL;
    virtual const char *GetMetadataItem( const char * pszName,
                                         const char * pszDomain = "" ) CPL_FINAL;
    virtual int    GetOverviewCount() CPL_FINAL;
    virtual GDALRasterBand *GetOverview( int ) CPL_FINAL;

    virtual GDALRasterBand *GetMaskBand() CPL_FINAL;
    virtual int             GetMaskFlags() CPL_FINAL;
    virtual CPLErr          CreateMaskBand( int nFlags ) CPL_FINAL;

    virtual CPLVirtualMem  *GetVirtualMemAuto( GDALRWFlag eRWFlag,
                                               int *pnPixelSpace,
                                               GIntBig *pnLineSpace,
                                               char **papszOptions ) CPL_FINAL;

    virtual CPLErr  GetHistogram( double dfMin, double dfMax,
                          int nBuckets, GUIntBig * panHistogram,
                          int bIncludeOutOfRange, int bApproxOK,
                          GDALProgressFunc, void *pProgressData ) CPL_FINAL;

    virtual CPLErr GetDefaultHistogram( double *pdfMin, double *pdfMax,
                                        int *pnBuckets, GUIntBig ** ppanHistogram,
                                        int bForce,
                                        GDALProgressFunc, void *pProgressData) CPL_FINAL;
};

/************************************************************************/
/*                           GTiffRasterBand()                          */
/************************************************************************/

GTiffRasterBand::GTiffRasterBand( GTiffDataset *poDSIn, int nBandIn) :
    bHaveOffsetScale(FALSE),
    dfOffset(0.0),
    dfScale(1.0),
    poGDS(poDSIn),
    bNoDataSet(FALSE),
    dfNoDataValue(-9999.0)
{
    poDS = poDSIn;
    nBand = nBandIn;

/* -------------------------------------------------------------------- */
/*      Get the GDAL data type.                                         */
/* -------------------------------------------------------------------- */
    const uint16 nBitsPerSample = poGDS->nBitsPerSample;
    const uint16 nSampleFormat = poGDS->nSampleFormat;

    eDataType = GDT_Unknown;

    if( nBitsPerSample <= 8 )
    {
        eDataType = GDT_Byte;
        if( nSampleFormat == SAMPLEFORMAT_INT )
            oGTiffMDMD.SetMetadataItem( "PIXELTYPE", "SIGNEDBYTE", "IMAGE_STRUCTURE" );
    }
    else if( nBitsPerSample <= 16 )
    {
        if( nSampleFormat == SAMPLEFORMAT_INT )
            eDataType = GDT_Int16;
        else
            eDataType = GDT_UInt16;
    }
    else if( nBitsPerSample == 32 )
    {
        if( nSampleFormat == SAMPLEFORMAT_COMPLEXINT )
            eDataType = GDT_CInt16;
        else if( nSampleFormat == SAMPLEFORMAT_IEEEFP )
            eDataType = GDT_Float32;
        else if( nSampleFormat == SAMPLEFORMAT_INT )
            eDataType = GDT_Int32;
        else
            eDataType = GDT_UInt32;
    }
    else if( nBitsPerSample == 64 )
    {
        if( nSampleFormat == SAMPLEFORMAT_IEEEFP )
            eDataType = GDT_Float64;
        else if( nSampleFormat == SAMPLEFORMAT_COMPLEXIEEEFP )
            eDataType = GDT_CFloat32;
        else if( nSampleFormat == SAMPLEFORMAT_COMPLEXINT )
            eDataType = GDT_CInt32;
    }
    else if( nBitsPerSample == 128 )
    {
        if( nSampleFormat == SAMPLEFORMAT_COMPLEXIEEEFP )
            eDataType = GDT_CFloat64;
    }

/* -------------------------------------------------------------------- */
/*      Try to work out band color interpretation.                      */
/* -------------------------------------------------------------------- */
    bool bLookForExtraSamples = false;

    if( poGDS->poColorTable != NULL && nBand == 1 )
        eBandInterp = GCI_PaletteIndex;
    else if( poGDS->nPhotometric == PHOTOMETRIC_RGB
             || (poGDS->nPhotometric == PHOTOMETRIC_YCBCR
                 && poGDS->nCompression == COMPRESSION_JPEG
                 && CPLTestBool( CPLGetConfigOption("CONVERT_YCBCR_TO_RGB",
                                                       "YES") )) )
    {
        if( nBand == 1 )
            eBandInterp = GCI_RedBand;
        else if( nBand == 2 )
            eBandInterp = GCI_GreenBand;
        else if( nBand == 3 )
            eBandInterp = GCI_BlueBand;
        else
            bLookForExtraSamples = true;
    }
    else if( poGDS->nPhotometric == PHOTOMETRIC_YCBCR )
    {
        if( nBand == 1 )
            eBandInterp = GCI_YCbCr_YBand;
        else if( nBand == 2 )
            eBandInterp = GCI_YCbCr_CbBand;
        else if( nBand == 3 )
            eBandInterp = GCI_YCbCr_CrBand;
        else
            bLookForExtraSamples = true;
    }
    else if( poGDS->nPhotometric == PHOTOMETRIC_SEPARATED )
    {
        if( nBand == 1 )
            eBandInterp = GCI_CyanBand;
        else if( nBand == 2 )
            eBandInterp = GCI_MagentaBand;
        else if( nBand == 3 )
            eBandInterp = GCI_YellowBand;
        else if( nBand == 4 )
            eBandInterp = GCI_BlackBand;
        else
            bLookForExtraSamples = true;
    }
    else if( poGDS->nPhotometric == PHOTOMETRIC_MINISBLACK && nBand == 1 )
        eBandInterp = GCI_GrayIndex;
    else
        bLookForExtraSamples = true;

    if( bLookForExtraSamples )
    {
        uint16 *v;
        uint16 count = 0;

        if( TIFFGetField( poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v ) )
        {
            const int nBaseSamples = poGDS->nSamplesPerPixel - count;
            const int nExpectedBaseSamples =
                (poGDS->nPhotometric == PHOTOMETRIC_MINISBLACK) ? 1 :
                (poGDS->nPhotometric == PHOTOMETRIC_MINISWHITE) ? 1 :
                (poGDS->nPhotometric == PHOTOMETRIC_RGB) ? 3 :
                (poGDS->nPhotometric == PHOTOMETRIC_YCBCR) ? 3 :
                (poGDS->nPhotometric == PHOTOMETRIC_SEPARATED) ? 4 : 0;

            if( nExpectedBaseSamples > 0 &&
                nBand == nExpectedBaseSamples + 1 &&
                nBaseSamples != nExpectedBaseSamples )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Wrong number of ExtraSamples : %d. %d were expected",
                         count, poGDS->nSamplesPerPixel - nExpectedBaseSamples);
            }

            if( nBand > nBaseSamples
                && nBand-nBaseSamples-1 < count
                && (v[nBand-nBaseSamples-1] == EXTRASAMPLE_ASSOCALPHA
                    || v[nBand-nBaseSamples-1] == EXTRASAMPLE_UNASSALPHA) )
                eBandInterp = GCI_AlphaBand;
            else
                eBandInterp = GCI_Undefined;
        }
        else
            eBandInterp = GCI_Undefined;
    }

/* -------------------------------------------------------------------- */
/*      Establish block size for strip or tiles.                        */
/* -------------------------------------------------------------------- */
    nBlockXSize = poGDS->nBlockXSize;
    nBlockYSize = poGDS->nBlockYSize;
}

/************************************************************************/
/*                          ~GTiffRasterBand()                          */
/************************************************************************/

GTiffRasterBand::~GTiffRasterBand()
{
    // So that any future DropReferenceVirtualMem() will not try to access the
    // raster band object, but this would not conform the advertised contract.
    if( aSetPSelf.size() != 0 )
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Virtual memory objects still exist at GTiffRasterBand destruction");
        std::set<GTiffRasterBand**>::iterator oIter = aSetPSelf.begin();
        for(; oIter != aSetPSelf.end(); ++oIter )
            *(*oIter) = NULL;
    }
}

/************************************************************************/
/*                        FetchBufferDirectIO                           */
/************************************************************************/

class FetchBufferDirectIO CPL_FINAL
{
    VSILFILE*    fp;
    GByte       *pTempBuffer;
    size_t       nTempBufferSize;

public:
            FetchBufferDirectIO(VSILFILE* fpIn, GByte* pTempBufferIn, size_t nTempBufferSizeIn) :
                    fp(fpIn),
                    pTempBuffer(pTempBufferIn),
                    nTempBufferSize(nTempBufferSizeIn) {}

    const GByte* FetchBytes(vsi_l_offset nOffset,
                            int nPixels, int nDTSize,
                            bool bIsByteSwapped, bool bIsComplex,
                            int nBlockId)
    {
        if( !FetchBytes(pTempBuffer, nOffset, nPixels, nDTSize, bIsByteSwapped,
                        bIsComplex, nBlockId) )
        {
            return NULL;
        }
        return pTempBuffer;
    }

    bool  FetchBytes(GByte* pabyDstBuffer,
                     vsi_l_offset nOffset,
                     int nPixels, int nDTSize,
                     bool bIsByteSwapped, bool bIsComplex,
                     int nBlockId)
    {
        vsi_l_offset nSeekForward = 0;
        if( nOffset <= VSIFTellL(fp) ||
            (nSeekForward = nOffset - VSIFTellL(fp)) > nTempBufferSize )
        {
            if( VSIFSeekL(fp, nOffset, SEEK_SET) != 0 )
            {
                CPLError(CE_Failure, CPLE_FileIO,
                         "Cannot seek to block %d", nBlockId);
                return false;
            }
        }
        else
        {
            while( nSeekForward > 0 )
            {
                size_t nToRead = (size_t) MIN( nTempBufferSize, nSeekForward );
                if( VSIFReadL(pTempBuffer, nToRead, 1, fp) != 1 )
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "Cannot seek to block %d", nBlockId);
                    return false;
                }
                nSeekForward -= nToRead;
            }
        }
        if( VSIFReadL(pabyDstBuffer, nPixels * nDTSize, 1, fp) != 1 )
        {
            CPLError(CE_Failure, CPLE_FileIO,
                    "Missing data for block %d", nBlockId);
            return false;
        }

        if( bIsByteSwapped )
        {
            if( bIsComplex )
                GDALSwapWords( pabyDstBuffer, nDTSize / 2, 2 * nPixels, nDTSize / 2);
            else
                GDALSwapWords( pabyDstBuffer, nDTSize, nPixels, nDTSize);
        }
        return true;
    }

    static const EMULATED_BOOL bMinimizeIO = true;
};

/************************************************************************/
/*                           DirectIO()                                 */
/************************************************************************/

/* Reads directly bytes from the file using ReadMultiRange(), and by-pass */
/* block reading. Restricted to simple TIFF configurations */
/* (uncompressed data, standard data types). Particularly useful to extract */
/* sub-windows of data on a large /vsicurl dataset). */
/* Returns -1 if DirectIO() can't be supported on that file */

int GTiffRasterBand::DirectIO( GDALRWFlag eRWFlag,
                                  int nXOff, int nYOff, int nXSize, int nYSize,
                                  void * pData, int nBufXSize, int nBufYSize,
                                  GDALDataType eBufType,
                                  GSpacing nPixelSpace, GSpacing nLineSpace,
                                  GDALRasterIOExtraArg* psExtraArg )
{
    const int nDTSizeBits = GDALGetDataTypeSize(eDataType);
    if( !(eRWFlag == GF_Read &&
          poGDS->nCompression == COMPRESSION_NONE &&
          (poGDS->nPhotometric == PHOTOMETRIC_MINISBLACK ||
           poGDS->nPhotometric == PHOTOMETRIC_RGB ||
           poGDS->nPhotometric == PHOTOMETRIC_PALETTE) &&
          poGDS->nBitsPerSample == nDTSizeBits &&
          poGDS->SetDirectory() /* very important to make hTIFF uptodate! */) )
    {
        return -1;
    }

    /* we only know how to deal with nearest neighbour in this optimized routine */
    if( (nXSize != nBufXSize || nYSize != nBufYSize) &&
        psExtraArg != NULL &&
        psExtraArg->eResampleAlg != GRIORA_NearestNeighbour )
    {
        return -1;
    }

    /*CPLDebug("GTiff", "DirectIO(%d,%d,%d,%d -> %dx%d)",
             nXOff, nYOff, nXSize, nYSize,
             nBufXSize, nBufYSize);*/

    /* Make sure that TIFFTAG_STRIPOFFSETS is up-to-date */
    if (poGDS->GetAccess() == GA_Update)
    {
        poGDS->FlushCache();
        VSI_TIFFFlushBufferedWrite( TIFFClientdata( poGDS->hTIFF ) );
    }

    if( TIFFIsTiled( poGDS->hTIFF ) )
    {
        if( poGDS->m_pTempBufferForCommonDirectIO == NULL )
        {
            const int nDTSize = nDTSizeBits / 8;
            poGDS->m_nTempBufferForCommonDirectIOSize =
                (size_t)(nBlockXSize * nBlockYSize * nDTSize *
                ((poGDS->nPlanarConfig == PLANARCONFIG_CONTIG) ? poGDS->nBands : 1));

            poGDS->m_pTempBufferForCommonDirectIO = (GByte*)VSI_MALLOC_VERBOSE(poGDS->m_nTempBufferForCommonDirectIOSize);
            if( poGDS->m_pTempBufferForCommonDirectIO == NULL )
                return CE_Failure;
        }

        VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( poGDS->hTIFF ));
        FetchBufferDirectIO oFetcher(fp, poGDS->m_pTempBufferForCommonDirectIO,
                                     poGDS->m_nTempBufferForCommonDirectIOSize);

        return poGDS->CommonDirectIO( oFetcher,
                            nXOff, nYOff, nXSize, nYSize,
                            pData, nBufXSize, nBufYSize,
                            eBufType,
                            1, &nBand,
                            nPixelSpace, nLineSpace,
                            0 );
    }

    /* Get strip offsets */
    toff_t *panTIFFOffsets = NULL;
    if ( !TIFFGetField( poGDS->hTIFF, TIFFTAG_STRIPOFFSETS, &panTIFFOffsets ) ||
         panTIFFOffsets == NULL )
    {
        return CE_Failure;
    }

    int nReqXSize = nXSize; /* sub-sampling or over-sampling can only be done at last stage */
    int nReqYSize = MIN(nBufYSize, nYSize); /* we can do sub-sampling at the extraction stage */
    void** ppData = (void**) VSI_MALLOC_VERBOSE(nReqYSize * sizeof(void*));
    vsi_l_offset* panOffsets = (vsi_l_offset*)
                            VSI_MALLOC_VERBOSE(nReqYSize * sizeof(vsi_l_offset));
    size_t* panSizes = (size_t*) VSI_MALLOC_VERBOSE(nReqYSize * sizeof(size_t));
    int nDTSize = GDALGetDataTypeSize(eDataType) / 8;
    void* pTmpBuffer = NULL;
    int eErr = CE_None;
    int nContigBands = ((poGDS->nPlanarConfig == PLANARCONFIG_CONTIG) ? poGDS->nBands : 1);
    int nSrcPixelSize = nDTSize * nContigBands;

    if (ppData == NULL || panOffsets == NULL || panSizes == NULL)
        eErr = CE_Failure;
    else if (nXSize != nBufXSize || nYSize != nBufYSize ||
             eBufType != eDataType ||
             nPixelSpace != GDALGetDataTypeSize(eBufType) / 8 ||
             nContigBands > 1)
    {
        /* We need a temporary buffer for over-sampling/sub-sampling */
        /* and/or data type conversion */
        pTmpBuffer = VSI_MALLOC_VERBOSE(nReqXSize * nReqYSize * nSrcPixelSize);
        if (pTmpBuffer == NULL)
            eErr = CE_Failure;
    }

    /* Prepare data extraction */
    const double dfSrcYInc = nYSize / (double) nBufYSize;

    int iLine;
    for(iLine=0;eErr == CE_None && iLine<nReqYSize;iLine++)
    {
        if (pTmpBuffer == NULL)
            ppData[iLine] = ((GByte*)pData) + iLine * nLineSpace;
        else
            ppData[iLine] = ((GByte*)pTmpBuffer) + iLine * nReqXSize * nSrcPixelSize;
        int nSrcLine;
        if (nBufYSize < nYSize) /* Sub-sampling in y */
            nSrcLine = nYOff + (int)((iLine + 0.5) * dfSrcYInc);
        else
            nSrcLine = nYOff + iLine;

        int nBlockXOff = 0;
        int nBlockYOff = nSrcLine / nBlockYSize;
        int nYOffsetInBlock = nSrcLine % nBlockYSize;
        nBlocksPerRow = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
        int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
        if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
        {
            nBlockId += (nBand-1) * poGDS->nBlocksPerBand;
        }

        panOffsets[iLine] = panTIFFOffsets[nBlockId];
        if (panOffsets[iLine] == 0) /* We don't support sparse files */
            eErr = -1;

        panOffsets[iLine] += (nXOff + nYOffsetInBlock * nBlockXSize) * nSrcPixelSize;
        panSizes[iLine] = nReqXSize * nSrcPixelSize;
    }

    /* Extract data from the file */
    if (eErr == CE_None)
    {
        VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( poGDS->hTIFF ));
        int nRet = VSIFReadMultiRangeL(nReqYSize, ppData, panOffsets, panSizes, fp);
        if (nRet != 0)
            eErr = CE_Failure;
    }

    /* Byte-swap if necessary */
    if (eErr == CE_None && TIFFIsByteSwapped(poGDS->hTIFF))
    {
        for(iLine=0;iLine<nReqYSize;iLine++)
        {
            if( GDALDataTypeIsComplex(eDataType) )
                GDALSwapWords( ppData[iLine], nDTSize / 2, 2 * nReqXSize * nContigBands, nDTSize / 2);
            else
                GDALSwapWords( ppData[iLine], nDTSize, nReqXSize * nContigBands, nDTSize);
        }
    }

    /* Over-sampling/sub-sampling and/or data type conversion */
    const double dfSrcXInc = nXSize / (double) nBufXSize;
    if (eErr == CE_None && pTmpBuffer != NULL)
    {
        for(int iY=0;iY<nBufYSize;iY++)
        {
            int iSrcY = (nBufYSize <= nYSize) ? iY :
                            (int)((iY + 0.5) * dfSrcYInc);

            GByte* pabySrcData = ((GByte*)ppData[iSrcY]) +
                        ((nContigBands > 1) ? (nBand-1) : 0) * nDTSize;
            GByte* pabyDstData = ((GByte*)pData) + iY * nLineSpace;
            if( nBufXSize == nXSize )
            {
                GDALCopyWords( pabySrcData,
                                eDataType, nSrcPixelSize,
                                pabyDstData,
                                eBufType, static_cast<int>(nPixelSpace), nBufXSize);
            }
            else
            {
                if( eDataType == GDT_Byte && eBufType == GDT_Byte )
                {
                    double dfSrcX = 0.5 * dfSrcXInc;
                    for(int iX=0;iX<nBufXSize;iX++, dfSrcX += dfSrcXInc)
                    {
                        int iSrcX = (int)dfSrcX;
                        pabyDstData[iX * nPixelSpace] = pabySrcData[iSrcX * nSrcPixelSize];
                    }
                }
                else
                {
                    double dfSrcX = 0.5 * dfSrcXInc;
                    for(int iX=0;iX<nBufXSize;iX++, dfSrcX += dfSrcXInc)
                    {
                        int iSrcX = (int)dfSrcX;
                        GDALCopyWords( pabySrcData + iSrcX * nSrcPixelSize,
                                    eDataType, 0,
                                    pabyDstData + iX * nPixelSpace,
                                    eBufType, 0, 1);
                    }
                }
            }
        }
    }

    /* Cleanup */
    CPLFree(pTmpBuffer);
    CPLFree(ppData);
    CPLFree(panOffsets);
    CPLFree(panSizes);

    return eErr;
}

/************************************************************************/
/*                           GetVirtualMemAuto()                        */
/************************************************************************/

CPLVirtualMem* GTiffRasterBand::GetVirtualMemAuto( GDALRWFlag eRWFlag,
                                                  int *pnPixelSpace,
                                                  GIntBig *pnLineSpace,
                                                  char **papszOptions )
{
    if( !CPLTestBool(CSLFetchNameValueDef(papszOptions, "USE_DEFAULT_IMPLEMENTATION", "NO")) )
    {
        CPLVirtualMem *psRet
            = GetVirtualMemAutoInternal(eRWFlag, pnPixelSpace, pnLineSpace,
                                        papszOptions);
        if( psRet != NULL )
        {
            CPLDebug("GTiff", "GetVirtualMemAuto(): Using memory file mapping");
            return psRet;
        }
    }

    CPLDebug("GTiff", "GetVirtualMemAuto(): Defaulting to base implementation");
    return GDALRasterBand::GetVirtualMemAuto(eRWFlag, pnPixelSpace,
                                             pnLineSpace, papszOptions);
}

/************************************************************************/
/*                           GetHistogram()                             */
/************************************************************************/

CPLErr  GTiffRasterBand::GetHistogram( double dfMin, double dfMax,
                          int nBuckets, GUIntBig * panHistogram,
                          int bIncludeOutOfRange, int bApproxOK,
                          GDALProgressFunc pfnProgress, void *pProgressData )
{
    poGDS->LoadGeoreferencingAndPamIfNeeded();
    return GDALPamRasterBand::GetHistogram(dfMin, dfMax, nBuckets, panHistogram,
                                           bIncludeOutOfRange, bApproxOK,
                                           pfnProgress, pProgressData);
}

/************************************************************************/
/*                       GetDefaultHistogram()                          */
/************************************************************************/

CPLErr GTiffRasterBand::GetDefaultHistogram( double *pdfMin, double *pdfMax,
                                             int *pnBuckets,
                                             GUIntBig ** ppanHistogram,
                                             int bForce,
                                             GDALProgressFunc pfnProgress,
                                             void *pProgressData )
{
    poGDS->LoadGeoreferencingAndPamIfNeeded();
    return GDALPamRasterBand::GetDefaultHistogram(pdfMin, pdfMax,
                                                  pnBuckets, ppanHistogram,
                                                  bForce,
                                                  pfnProgress, pProgressData);
}

/************************************************************************/
/*                     DropReferenceVirtualMem()                        */
/************************************************************************/

void GTiffRasterBand::DropReferenceVirtualMem(void* pUserData)
{
    /* This function may also be called when the dataset and rasterband */
    /* objects have been destroyed */
    /* If they are still alive, it updates the reference counter of the */
    /* base mapping to invalidate the pointer to it if needed */

    GTiffRasterBand** ppoSelf = (GTiffRasterBand**) pUserData;
    GTiffRasterBand* poSelf = *ppoSelf;

    if( poSelf != NULL )
    {
        if( --(poSelf->poGDS->nRefBaseMapping) == 0 )
        {
            poSelf->poGDS->pBaseMapping = NULL;
        }
        poSelf->aSetPSelf.erase(ppoSelf);
    }
    CPLFree(pUserData);
}

/************************************************************************/
/*                     GetVirtualMemAutoInternal()                      */
/************************************************************************/

CPLVirtualMem* GTiffRasterBand::GetVirtualMemAutoInternal( GDALRWFlag eRWFlag,
                                                           int *pnPixelSpace,
                                                           GIntBig *pnLineSpace,
                                                           char **papszOptions )
{
    int nLineSize = nBlockXSize * (GDALGetDataTypeSize(eDataType) / 8);
    if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
        nLineSize *= poGDS->nBands;

    if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
    {
        /* In case of a pixel interleaved file, we save virtual memory space */
        /* by reusing a base mapping that embraces the whole imagery */
        if( poGDS->pBaseMapping != NULL )
        {
            /* Offset between the base mapping and the requested mapping */
            vsi_l_offset nOffset = (vsi_l_offset)(nBand - 1) * GDALGetDataTypeSize(eDataType) / 8;

            GTiffRasterBand** ppoSelf = (GTiffRasterBand** )CPLCalloc(1, sizeof(GTiffRasterBand*));
            *ppoSelf = this;

            CPLVirtualMem* pVMem = CPLVirtualMemDerivedNew(
                    poGDS->pBaseMapping,
                    nOffset,
                    CPLVirtualMemGetSize(poGDS->pBaseMapping) - nOffset,
                    GTiffRasterBand::DropReferenceVirtualMem,
                    ppoSelf);
            if( pVMem == NULL )
            {
                CPLFree(ppoSelf);
                return NULL;
            }

            /* Mechanism used so that the memory mapping object can be */
            /* destroyed after the raster band */
            aSetPSelf.insert(ppoSelf);
            poGDS->nRefBaseMapping ++;
            *pnPixelSpace = GDALGetDataTypeSize(eDataType) / 8;
            if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
                *pnPixelSpace *= poGDS->nBands;
            *pnLineSpace = nLineSize;
            return pVMem;
        }
    }

    if( !poGDS->SetDirectory() ) /* very important to make hTIFF up-to-date */
        return NULL;
    VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( poGDS->hTIFF ));

    vsi_l_offset nLength = (vsi_l_offset)nRasterYSize * nLineSize;

    if( !(CPLIsVirtualMemFileMapAvailable() &&
          VSIFGetNativeFileDescriptorL(fp) != NULL &&
#if SIZEOF_VOIDP == 4
          nLength == (size_t)nLength &&
#endif
          poGDS->nCompression == COMPRESSION_NONE &&
          (poGDS->nPhotometric == PHOTOMETRIC_MINISBLACK ||
           poGDS->nPhotometric == PHOTOMETRIC_RGB ||
           poGDS->nPhotometric == PHOTOMETRIC_PALETTE) &&
          poGDS->nBitsPerSample == GDALGetDataTypeSize(eDataType) &&
          !TIFFIsTiled( poGDS->hTIFF ) && !TIFFIsByteSwapped(poGDS->hTIFF)) )
    {
        return NULL;
    }

    /* Make sure that TIFFTAG_STRIPOFFSETS is up-to-date */
    if (poGDS->GetAccess() == GA_Update)
    {
        poGDS->FlushCache();
        VSI_TIFFFlushBufferedWrite( TIFFClientdata( poGDS->hTIFF ) );
    }

    /* Get strip offsets */
    toff_t *panTIFFOffsets = NULL;
    if ( !TIFFGetField( poGDS->hTIFF, TIFFTAG_STRIPOFFSETS, &panTIFFOffsets ) ||
         panTIFFOffsets == NULL )
    {
        return NULL;
    }

    int nBlockSize =
        nBlockXSize * nBlockYSize * GDALGetDataTypeSize(eDataType) / 8;
    if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
        nBlockSize *= poGDS->nBands;

    int nBlocks = poGDS->nBlocksPerBand;
    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
        nBlocks *= poGDS->nBands;
    int i;
    for(i = 0; i < nBlocks; i ++)
    {
        if( panTIFFOffsets[i] != 0 )
            break;
    }
    if( i == nBlocks )
    {
        /* All zeroes */
        if( poGDS->eAccess == GA_Update )
        {
            /* Initialize the file with empty blocks so that the file has */
            /* the appropriate size */

            toff_t* panByteCounts = NULL;
            if( !TIFFGetField( poGDS->hTIFF, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts ) ||
                panByteCounts == NULL )
            {
                return NULL;
            }
            if( VSIFSeekL(fp, 0, SEEK_END) != 0 )
                return NULL;
            vsi_l_offset nBaseOffset = VSIFTellL(fp);

            /* Just write one tile with libtiff to put it in appropriate state */
            GByte* pabyData = (GByte*)VSI_CALLOC_VERBOSE(1, nBlockSize);
            if( pabyData == NULL )
            {
                return NULL;
            }
            int ret = static_cast<int>(TIFFWriteEncodedStrip(poGDS->hTIFF, 0, pabyData, nBlockSize));
            VSI_TIFFFlushBufferedWrite( TIFFClientdata( poGDS->hTIFF ) );
            VSIFree(pabyData);
            if( ret != nBlockSize )
            {
                return NULL;
            }
            CPLAssert(panTIFFOffsets[0] == nBaseOffset);
            CPLAssert(panByteCounts[0] == (toff_t)nBlockSize);

            /* Now simulate the writing of other blocks */
            vsi_l_offset nDataSize = (vsi_l_offset)nBlockSize * nBlocks;
            if( VSIFSeekL(fp, nBaseOffset + nDataSize - 1, SEEK_SET) != 0 )
                return NULL;
            char ch = 0;
            if( VSIFWriteL(&ch, 1, 1, fp) != 1 )
            {
                return NULL;
            }

            for(i = 1; i < nBlocks; i ++)
            {
                panTIFFOffsets[i] = nBaseOffset + i * (toff_t)nBlockSize;
                panByteCounts[i] = nBlockSize;
            }
        }
        else
        {
            CPLDebug("GTiff", "Sparse files not supported in file mapping");
            return NULL;
        }
    }

    GIntBig nBlockSpacing = 0;
    bool bCompatibleSpacing = true;
    toff_t nPrevOffset = 0;
    for(i = 0; i < poGDS->nBlocksPerBand; i ++)
    {
        toff_t nCurOffset;
        if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
            nCurOffset = panTIFFOffsets[poGDS->nBlocksPerBand * (nBand - 1) + i];
        else
            nCurOffset = panTIFFOffsets[i];
        if( nCurOffset == 0 )
        {
            bCompatibleSpacing = false;
            break;
        }
        if( i > 0 )
        {
            GIntBig nCurSpacing = nCurOffset - nPrevOffset;
            if( i == 1 )
            {
                if( nCurSpacing != (GIntBig)nBlockYSize * nLineSize )
                {
                    bCompatibleSpacing = false;
                    break;
                }
                nBlockSpacing = nCurSpacing;
            }
            else if( nBlockSpacing != nCurSpacing )
            {
                bCompatibleSpacing = false;
                break;
            }
        }
        nPrevOffset = nCurOffset;
    }

    if( !bCompatibleSpacing )
    {
        return NULL;
    }
    else
    {
        vsi_l_offset nOffset;
        if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
        {
            CPLAssert( poGDS->pBaseMapping == NULL );
            nOffset = panTIFFOffsets[0];
        }
        else
            nOffset = panTIFFOffsets[poGDS->nBlocksPerBand * (nBand - 1)];
        CPLVirtualMem* pVMem = CPLVirtualMemFileMapNew(
            fp, nOffset, nLength,
            (eRWFlag == GF_Write) ? VIRTUALMEM_READWRITE : VIRTUALMEM_READONLY,
            NULL, NULL);
        if( pVMem == NULL )
        {
            return NULL;
        }
        else
        {
            if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
            {
                poGDS->pBaseMapping = pVMem;
                pVMem = GetVirtualMemAutoInternal( eRWFlag,
                                                   pnPixelSpace,
                                                   pnLineSpace,
                                                   papszOptions );
                /* drop ref on base mapping */
                CPLVirtualMemFree(poGDS->pBaseMapping);
                if( pVMem == NULL )
                    poGDS->pBaseMapping = NULL;
            }
            else
            {
                *pnPixelSpace = GDALGetDataTypeSize(eDataType) / 8;
                if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
                    *pnPixelSpace *= poGDS->nBands;
                *pnLineSpace = nLineSize;
            }
            return pVMem;
        }
    }
}

/************************************************************************/
/*                            IRasterIO()                               */
/************************************************************************/

CPLErr GTiffDataset::IRasterIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg)

{
    CPLErr eErr;
    /* Try to pass the request to the most appropriate overview dataset */
    if( nBufXSize < nXSize && nBufYSize < nYSize )
    {
        int bTried;
        nJPEGOverviewVisibilityFlag ++;
        eErr = TryOverviewRasterIO( eRWFlag,
                                    nXOff, nYOff, nXSize, nYSize,
                                    pData, nBufXSize, nBufYSize,
                                    eBufType,
                                    nBandCount, panBandMap,
                                    nPixelSpace, nLineSpace,
                                    nBandSpace,
                                    psExtraArg,
                                    &bTried );
        nJPEGOverviewVisibilityFlag --;
        if( bTried )
            return eErr;
    }

    if( eVirtualMemIOUsage != VIRTUAL_MEM_IO_NO )
    {
        int nErr = VirtualMemIO(
                eRWFlag, nXOff, nYOff, nXSize, nYSize,
                pData, nBufXSize, nBufYSize, eBufType,
                nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace, psExtraArg);
        if (nErr >= 0)
            return (CPLErr)nErr;
    }
    if (bDirectIO)
    {
        int nErr = DirectIO(
                eRWFlag, nXOff, nYOff, nXSize, nYSize,
                pData, nBufXSize, nBufYSize, eBufType,
                nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace, psExtraArg);
        if (nErr >= 0)
            return (CPLErr)nErr;
    }

    nJPEGOverviewVisibilityFlag ++;
    eErr =  GDALPamDataset::IRasterIO(
                eRWFlag, nXOff, nYOff, nXSize, nYSize,
                pData, nBufXSize, nBufYSize, eBufType,
                nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace, psExtraArg);
    nJPEGOverviewVisibilityFlag --;
    return eErr;
}

/************************************************************************/
/*                        FetchBufferVirtualMemIO                       */
/************************************************************************/

class FetchBufferVirtualMemIO CPL_FINAL
{
    const GByte* pabySrcData;
    size_t       nMappingSize;
    GByte       *pTempBuffer;

public:
            FetchBufferVirtualMemIO(const GByte* pabySrcDataIn,
                                    size_t nMappingSizeIn,
                                    GByte* pTempBufferIn) :
                    pabySrcData(pabySrcDataIn),
                    nMappingSize(nMappingSizeIn),
                    pTempBuffer(pTempBufferIn) {}

    const GByte* FetchBytes(vsi_l_offset nOffset,
                            int nPixels, int nDTSize,
                            bool bIsByteSwapped, bool bIsComplex,
                            int nBlockId)
    {
        if( nOffset + nPixels * nDTSize > nMappingSize )
        {
            CPLError(CE_Failure, CPLE_FileIO,
                    "Missing data for block %d", nBlockId);
            return NULL;
        }
        if( !bIsByteSwapped )
            return pabySrcData + nOffset;
        memcpy(pTempBuffer, pabySrcData + nOffset, nPixels * nDTSize);
        if( bIsComplex )
            GDALSwapWords( pTempBuffer, nDTSize / 2, 2 * nPixels, nDTSize / 2);
        else
            GDALSwapWords( pTempBuffer, nDTSize, nPixels, nDTSize);
        return pTempBuffer;
    }

    bool  FetchBytes(GByte* pabyDstBuffer,
                     vsi_l_offset nOffset,
                     int nPixels, int nDTSize,
                     bool bIsByteSwapped, bool bIsComplex,
                     int nBlockId)
    {
        if( nOffset + nPixels * nDTSize > nMappingSize )
        {
            CPLError(CE_Failure, CPLE_FileIO,
                    "Missing data for block %d", nBlockId);
            return false;
        }
        memcpy(pabyDstBuffer, pabySrcData + nOffset, nPixels * nDTSize);
        if( bIsByteSwapped )
        {
            if( bIsComplex )
                GDALSwapWords( pabyDstBuffer, nDTSize / 2, 2 * nPixels, nDTSize / 2);
            else
                GDALSwapWords( pabyDstBuffer, nDTSize, nPixels, nDTSize);
        }
        return true;
    }

    static const EMULATED_BOOL bMinimizeIO = false;
};

/************************************************************************/
/*                         VirtualMemIO()                               */
/************************************************************************/

int GTiffDataset::VirtualMemIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg )
{
    if( eAccess == GA_Update || eRWFlag == GF_Write || bStreamingIn )
        return -1;

    /* we only know how to deal with nearest neighbour in this optimized routine */
    if( (nXSize != nBufXSize || nYSize != nBufYSize) &&
        psExtraArg != NULL &&
        psExtraArg->eResampleAlg != GRIORA_NearestNeighbour )
    {
        return -1;
    }

    if( !SetDirectory() )
        return CE_Failure;

    const GDALDataType eDataType = GetRasterBand(1)->GetRasterDataType();
    const int nDTSizeBits = GDALGetDataTypeSize(eDataType);
    if( !(nCompression == COMPRESSION_NONE &&
        (nPhotometric == PHOTOMETRIC_MINISBLACK ||
        nPhotometric == PHOTOMETRIC_RGB ||
        nPhotometric == PHOTOMETRIC_PALETTE) &&
        nBitsPerSample == nDTSizeBits) )
    {
        eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
        return -1;
    }

    size_t nMappingSize = 0;
    GByte* pabySrcData = NULL;
    if( STARTS_WITH(GetDescription(), "/vsimem/") )
    {
        vsi_l_offset nDataLength = 0;
        pabySrcData = VSIGetMemFileBuffer(GetDescription(), &nDataLength, FALSE);
        nMappingSize = (size_t)nDataLength;
        if( pabySrcData == NULL )
            return -1;
    }
    else if( psVirtualMemIOMapping == NULL )
    {
        VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( hTIFF ));
        if( !CPLIsVirtualMemFileMapAvailable() ||
            VSIFGetNativeFileDescriptorL(fp) == NULL )
        {
            eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
            return -1;
        }
        if( VSIFSeekL(fp, 0, SEEK_END) != 0 )
        {
            eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
            return -1;
        }
        vsi_l_offset nLength = VSIFTellL(fp);
        if( (size_t)nLength != nLength )
        {
            eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
            return -1;
        }
        if( eVirtualMemIOUsage == VIRTUAL_MEM_IO_IF_ENOUGH_RAM )
        {
            GIntBig nRAM = CPLGetUsablePhysicalRAM();
            if( (GIntBig)nLength > nRAM )
            {
                CPLDebug("GTiff", "Not enough RAM to map whole file into memory.");
                eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
                return -1;
            }
        }
        psVirtualMemIOMapping = CPLVirtualMemFileMapNew(
            fp, 0, nLength, VIRTUALMEM_READONLY, NULL, NULL);
        if( psVirtualMemIOMapping == NULL )
        {
            eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
            return -1;
        }
        eVirtualMemIOUsage = VIRTUAL_MEM_IO_YES;
    }

    if( psVirtualMemIOMapping )
    {
#ifdef DEBUG
        CPLDebug("GTiff", "Using VirtualMemIO");
#endif
        nMappingSize = CPLVirtualMemGetSize(psVirtualMemIOMapping);
        pabySrcData = (GByte*)CPLVirtualMemGetAddr(psVirtualMemIOMapping);
    }

    if( TIFFIsByteSwapped(hTIFF) && m_pTempBufferForCommonDirectIO == NULL )
    {
        const int nDTSize = nDTSizeBits / 8;
        m_nTempBufferForCommonDirectIOSize =
            (size_t)(nBlockXSize * nDTSize * ((nPlanarConfig == PLANARCONFIG_CONTIG) ? nBands : 1));
        if( TIFFIsTiled(hTIFF) )
            m_nTempBufferForCommonDirectIOSize *= nBlockYSize;

        m_pTempBufferForCommonDirectIO = (GByte*)VSI_MALLOC_VERBOSE(m_nTempBufferForCommonDirectIOSize);
        if( m_pTempBufferForCommonDirectIO == NULL )
            return CE_Failure;
    }
    FetchBufferVirtualMemIO oFetcher(pabySrcData, nMappingSize, m_pTempBufferForCommonDirectIO);

    return CommonDirectIO( oFetcher,
                           nXOff, nYOff, nXSize, nYSize,
                           pData, nBufXSize, nBufYSize,
                           eBufType,
                           nBandCount, panBandMap,
                           nPixelSpace, nLineSpace,
                           nBandSpace );
}

/************************************************************************/
/*                   CopyContigByteMultiBand()                          */
/************************************************************************/

static inline void CopyContigByteMultiBand(
                            const GByte* CPL_RESTRICT pabySrc, int nSrcStride,
                            GByte* CPL_RESTRICT pabyDest, int nDestStride,
                            int nIters, int nBandCount)
{
    if( nBandCount == 3 )
    {
        if( nSrcStride == 3 && nDestStride == 4 )
        {
            while( nIters >= 8 )
            {
                pabyDest[4*0+0] = pabySrc[3*0+0];
                pabyDest[4*0+1] = pabySrc[3*0+1];
                pabyDest[4*0+2] = pabySrc[3*0+2];
                pabyDest[4*1+0] = pabySrc[3*1+0];
                pabyDest[4*1+1] = pabySrc[3*1+1];
                pabyDest[4*1+2] = pabySrc[3*1+2];
                pabyDest[4*2+0] = pabySrc[3*2+0];
                pabyDest[4*2+1] = pabySrc[3*2+1];
                pabyDest[4*2+2] = pabySrc[3*2+2];
                pabyDest[4*3+0] = pabySrc[3*3+0];
                pabyDest[4*3+1] = pabySrc[3*3+1];
                pabyDest[4*3+2] = pabySrc[3*3+2];
                pabyDest[4*4+0] = pabySrc[3*4+0];
                pabyDest[4*4+1] = pabySrc[3*4+1];
                pabyDest[4*4+2] = pabySrc[3*4+2];
                pabyDest[4*5+0] = pabySrc[3*5+0];
                pabyDest[4*5+1] = pabySrc[3*5+1];
                pabyDest[4*5+2] = pabySrc[3*5+2];
                pabyDest[4*6+0] = pabySrc[3*6+0];
                pabyDest[4*6+1] = pabySrc[3*6+1];
                pabyDest[4*6+2] = pabySrc[3*6+2];
                pabyDest[4*7+0] = pabySrc[3*7+0];
                pabyDest[4*7+1] = pabySrc[3*7+1];
                pabyDest[4*7+2] = pabySrc[3*7+2];
                pabySrc += 3 * 8;
                pabyDest += 4 * 8;
                nIters -= 8;
            }
            while( nIters-- > 0 )
            {
                pabyDest[0] = pabySrc[0];
                pabyDest[1] = pabySrc[1];
                pabyDest[2] = pabySrc[2];
                pabySrc += 3;
                pabyDest += 4;
            }
        }
        else
        {
            while( nIters-- > 0 )
            {
                pabyDest[0] = pabySrc[0];
                pabyDest[1] = pabySrc[1];
                pabyDest[2] = pabySrc[2];
                pabySrc += nSrcStride;
                pabyDest += nDestStride;
            }
        }
    }
    else
    {
        while( nIters-- > 0 )
        {
            for(int iBand=0;iBand<nBandCount;iBand++)
                pabyDest[iBand] = pabySrc[iBand];
            pabySrc += nSrcStride;
            pabyDest += nDestStride;
        }
    }
}

/************************************************************************/
/*                         CommonDirectIO()                             */
/************************************************************************/

//#define DEBUG_REACHED_VIRTUAL_MEM_IO
#ifdef DEBUG_REACHED_VIRTUAL_MEM_IO
static int anReachedVirtualMemIO[52] = { 0 };
#define REACHED(x)  anReachedVirtualMemIO[x] = 1
#else
#define REACHED(x)
#endif

template<class FetchBuffer> CPLErr GTiffDataset::CommonDirectIO(
                               FetchBuffer& oFetcher,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace )
{
    const GDALDataType eDataType = GetRasterBand(1)->GetRasterDataType();
    const int nDTSize = GDALGetDataTypeSize(eDataType) / 8;
    const bool bIsComplex = CPL_TO_BOOL(GDALDataTypeIsComplex(eDataType));
    const int nBufDTSize = GDALGetDataTypeSize(eBufType) / 8;

    /* Get strip offsets */
    toff_t *panOffsets = NULL;
    if ( !TIFFGetField( hTIFF, (TIFFIsTiled( hTIFF )) ? TIFFTAG_TILEOFFSETS : TIFFTAG_STRIPOFFSETS, &panOffsets ) ||
         panOffsets == NULL )
    {
        return CE_Failure;
    }

    bool bUseContigImplementation =
        ( nPlanarConfig == PLANARCONFIG_CONTIG ) && (nBandCount > 1) && (nBandSpace == nBufDTSize);
    if( bUseContigImplementation )
    {
        for(int iBand = 0; iBand < nBandCount; iBand ++ )
        {
            int nBand = panBandMap[iBand];
            if( nBand != iBand + 1 )
            {
                bUseContigImplementation = false;
                break;
            }
        }
    }

    const int nBandsPerBlock = ( nPlanarConfig == PLANARCONFIG_SEPARATE ) ? 1 : nBands;
    const int nBandsPerBlockDTSize = nBandsPerBlock * nDTSize;
    const int nBlocksPerRow = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
    const bool bNoTypeChange = (eDataType == eBufType);
    const bool bNoXResampling = (nXSize == nBufXSize );
    const bool bNoXResamplingNoTypeChange = (bNoTypeChange && bNoXResampling);
    const bool bByteOnly = (bNoTypeChange && nDTSize == 1 );
    const bool bByteNoXResampling = ( bByteOnly && bNoXResamplingNoTypeChange );
    const bool bIsByteSwapped = CPL_TO_BOOL(TIFFIsByteSwapped(hTIFF));
    const double dfSrcXInc = nXSize / (double) nBufXSize;
    const double dfSrcYInc = nYSize / (double) nBufYSize;

    int bNoDataSetIn;
    double dfNoData = GetRasterBand(1)->GetNoDataValue( &bNoDataSetIn );
    GByte abyNoData = 0;
    if( !bNoDataSetIn )
        dfNoData = 0;
    else if( dfNoData >= 0 && dfNoData <= 255 )
        abyNoData = (GByte) (dfNoData + 0.5);

    if( FetchBuffer::bMinimizeIO &&
             TIFFIsTiled( hTIFF ) && bNoXResampling && (nYSize == nBufYSize ) &&
             nPlanarConfig == PLANARCONFIG_CONTIG && nBandCount > 1 )
    {
        GByte* pabyData = (GByte*)pData;
        for(int y=0;y<nBufYSize;)
        {
            const int nSrcLine = nYOff + y;
            const int nBlockYOff = nSrcLine / nBlockYSize;
            const int nYOffsetInBlock = nSrcLine % nBlockYSize;
            const int nUsedBlockHeight = MIN(nBufYSize - y, (int)nBlockYSize - nYOffsetInBlock);

            int nBlockXOff = nXOff / nBlockXSize;
            int nXOffsetInBlock = nXOff % nBlockXSize;
            int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

            int x = 0;
            while( x < nBufXSize )
            {
                const toff_t nCurOffset = panOffsets[nBlockId];
                const int nUsedBlockWidth = MIN((int)nBlockXSize - nXOffsetInBlock, nBufXSize - x);

                if( nCurOffset == 0 )
                {
                    REACHED(30);
                    for( int k=0;k<nUsedBlockHeight;k++)
                    {
                        GByte* pabyLocalData = pabyData + (y+k) * nLineSpace + x * nPixelSpace;
                        for(int iBand=0;iBand<nBandCount;iBand++)
                        {
                            GByte* pabyLocalDataBand = pabyLocalData + iBand * nBandSpace;

                            GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                    pabyLocalDataBand, eBufType, static_cast<int>(nPixelSpace),
                                    nUsedBlockWidth);
                        }
                    }
                }
                else
                {
                    const int nByteOffsetInBlock =
                        nYOffsetInBlock * nBlockXSize * nBandsPerBlockDTSize;
                    const GByte* pabyLocalSrcDataK0 = oFetcher.FetchBytes(
                            nCurOffset + nByteOffsetInBlock,
                            (int)nBlockXSize * nUsedBlockHeight * nBandsPerBlock,
                            nDTSize, bIsByteSwapped, bIsComplex, nBlockId);
                    if( pabyLocalSrcDataK0 == NULL )
                        return CE_Failure;

                    for( int k=0;k<nUsedBlockHeight;k++)
                    {
                        GByte* pabyLocalData = pabyData + (y+k) * nLineSpace + x * nPixelSpace;
                        const GByte* pabyLocalSrcData =
                            pabyLocalSrcDataK0 + (k * nBlockXSize + nXOffsetInBlock) * nBandsPerBlockDTSize;

                        if( bUseContigImplementation && nBands == nBandCount &&
                            nPixelSpace == nBandsPerBlockDTSize )
                        {
                            REACHED(31);
                            GDALCopyWords(pabyLocalSrcData,
                                            eDataType, nDTSize,
                                            pabyLocalData,
                                            eBufType, nBufDTSize,
                                            nUsedBlockWidth * nBands);
                        }
                        else
                        {
                            REACHED(32);
                            for(int iBand=0;iBand<nBandCount;iBand++)
                            {
                                GByte* pabyLocalDataBand = pabyLocalData + iBand * nBandSpace;
                                const GByte* pabyLocalSrcDataBand = pabyLocalSrcData + (panBandMap[iBand]-1) * nDTSize;

                                GDALCopyWords(pabyLocalSrcDataBand,
                                                eDataType, nBandsPerBlockDTSize,
                                                pabyLocalDataBand,
                                                eBufType, static_cast<int>(nPixelSpace),
                                                nUsedBlockWidth);
                            }
                        }
                    }
                }

                nXOffsetInBlock = 0;
                nBlockXOff ++;
                nBlockId ++;
                x += nUsedBlockWidth;
            }

            y += nUsedBlockHeight;
        }
    }
    else if( FetchBuffer::bMinimizeIO &&
             TIFFIsTiled( hTIFF ) && bNoXResampling && (nYSize == nBufYSize ) /*&&
             (nPlanarConfig == PLANARCONFIG_SEPARATE || nBandCount == 1)*/ )
    {
        for(int iBand=0;iBand<nBandCount;iBand++)
        {
            GByte* pabyData = (GByte*)pData + iBand * nBandSpace;
            const int nBand = panBandMap[iBand];
            for(int y=0;y<nBufYSize;)
            {
                const int nSrcLine = nYOff + y;
                const int nBlockYOff = nSrcLine / nBlockYSize;
                const int nYOffsetInBlock = nSrcLine % nBlockYSize;
                const int nUsedBlockHeight = MIN(nBufYSize - y, (int)nBlockYSize - nYOffsetInBlock);

                int nBlockXOff = nXOff / nBlockXSize;
                int nXOffsetInBlock = nXOff % nBlockXSize;
                int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
                if ( nPlanarConfig == PLANARCONFIG_SEPARATE )
                {
                    REACHED(33);
                    nBlockId += nBlocksPerBand * (nBand - 1);
                }
                else
                {
                    REACHED(34);
                }

                int x = 0;
                while( x < nBufXSize )
                {
                    const toff_t nCurOffset = panOffsets[nBlockId];
                    const int nUsedBlockWidth = MIN((int)nBlockXSize - nXOffsetInBlock, nBufXSize - x);

                    if( nCurOffset == 0 )
                    {
                        REACHED(35);
                        for( int k=0;k<nUsedBlockHeight;k++)
                        {
                            GByte* pabyLocalData = pabyData + (y+k) * nLineSpace + x * nPixelSpace;

                            GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                    pabyLocalData, eBufType, static_cast<int>(nPixelSpace),
                                    nUsedBlockWidth);
                        }
                    }
                    else
                    {
                        const int nByteOffsetInBlock =
                            nYOffsetInBlock * nBlockXSize * nBandsPerBlockDTSize;
                        const GByte* pabyLocalSrcDataK0 = oFetcher.FetchBytes(
                                nCurOffset + nByteOffsetInBlock,
                                (int)nBlockXSize * nUsedBlockHeight * nBandsPerBlock,
                                nDTSize, bIsByteSwapped, bIsComplex, nBlockId);
                        if( pabyLocalSrcDataK0 == NULL )
                            return CE_Failure;

                        if ( nPlanarConfig == PLANARCONFIG_CONTIG )
                        {
                            REACHED(36);
                            pabyLocalSrcDataK0 += (nBand - 1) * nDTSize;
                        }
                        else
                        {
                            REACHED(37);
                        }

                        for( int k=0;k<nUsedBlockHeight;k++)
                        {
                            GByte* pabyLocalData = pabyData + (y+k) * nLineSpace + x * nPixelSpace;
                            const GByte* pabyLocalSrcData =
                                pabyLocalSrcDataK0 + (k * nBlockXSize + nXOffsetInBlock) * nBandsPerBlockDTSize;

                            GDALCopyWords(pabyLocalSrcData,
                                            eDataType, nBandsPerBlockDTSize,
                                            pabyLocalData,
                                            eBufType, static_cast<int>(nPixelSpace),
                                            nUsedBlockWidth);
                        }
                    }

                    nXOffsetInBlock = 0;
                    nBlockXOff ++;
                    nBlockId ++;
                    x += nUsedBlockWidth;
                }

                y += nUsedBlockHeight;
            }
        }
    }
    else if( FetchBuffer::bMinimizeIO &&
             TIFFIsTiled( hTIFF ) &&
             nPlanarConfig == PLANARCONFIG_CONTIG && nBandCount > 1 )
    {
        GByte* pabyData = (GByte*)pData;
        int anSrcYOffset[256];
        for(int y=0;y<nBufYSize;)
        {
            const double dfYOffStart = nYOff + (y + 0.5) * dfSrcYInc;
            const int nSrcLine = (int)dfYOffStart;
            const int nYOffsetInBlock = nSrcLine % nBlockYSize;
            const int nBlockYOff = nSrcLine / nBlockYSize;
            const int nBaseByteOffsetInBlock = nYOffsetInBlock * nBlockXSize * nBandsPerBlockDTSize;
            int ychunk = 1;
            int nLastSrcLineK = nSrcLine;
            anSrcYOffset[0] = 0;
            for(int k=1;k<nBufYSize-y;k++)
            {
                int nSrcLineK = nYOff + (int)((y + k + 0.5) * dfSrcYInc);
                const int nBlockYOffK = nSrcLineK / nBlockYSize;
                if( k < 256)
                    anSrcYOffset[k] = ((nSrcLineK % nBlockYSize) - nYOffsetInBlock) * nBlockXSize * nBandsPerBlockDTSize;
                if( nBlockYOffK != nBlockYOff )
                {
                    break;
                }
                ychunk ++;
                nLastSrcLineK = nSrcLineK;
            }
            const int nUsedBlockHeight = nLastSrcLineK - nSrcLine + 1;
            //CPLAssert(nUsedBlockHeight <= nBlockYSize);

            double dfSrcX = nXOff + 0.5 * dfSrcXInc;
            int nCurBlockXOff = 0;
            int nNextBlockXOff = 0;
            toff_t nCurOffset = 0;
            const GByte* pabyLocalSrcDataStartLine = NULL;
            for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
            {
                int nSrcPixel = (int)dfSrcX;
                if( nSrcPixel >= nNextBlockXOff )
                {
                    const int nBlockXOff = nSrcPixel / nBlockXSize;
                    nCurBlockXOff = nBlockXOff * nBlockXSize;
                    nNextBlockXOff = nCurBlockXOff + nBlockXSize;
                    int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
                    nCurOffset = panOffsets[nBlockId];
                    if( nCurOffset != 0 )
                    {
                        pabyLocalSrcDataStartLine = oFetcher.FetchBytes(
                                nCurOffset + nBaseByteOffsetInBlock,
                                (int)nBlockXSize * nBandsPerBlock * nUsedBlockHeight, nDTSize,
                                bIsByteSwapped, bIsComplex, nBlockId);
                        if( pabyLocalSrcDataStartLine == NULL )
                            return CE_Failure;
                    }
                }

                if( nCurOffset == 0 )
                {
                    REACHED(38);

                    for( int k = 0; k < ychunk; k ++ )
                    {
                        GByte* const pabyLocalData = pabyData + (y+k) * nLineSpace + x * nPixelSpace;
                        for(int iBand=0;iBand<nBandCount;iBand++)
                        {
                            GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                          pabyLocalData + nBandSpace * iBand, eBufType, 0,
                                          1);
                        }
                    }
                }

                else
                {
                    const int nXOffsetInBlock = nSrcPixel - nCurBlockXOff;
                    double dfYOff = dfYOffStart;
                    const GByte* const pabyLocalSrcDataK0 =
                        pabyLocalSrcDataStartLine + nXOffsetInBlock * nBandsPerBlockDTSize;
                    GByte* pabyLocalData = pabyData + y * nLineSpace + x * nPixelSpace;
                    for( int k = 0; k < ychunk; k ++, pabyLocalData += nLineSpace )
                    {
                        const GByte* pabyLocalSrcData;
                        if( ychunk <= 256 )
                        {
                            REACHED(39);
                            pabyLocalSrcData = pabyLocalSrcDataK0 + anSrcYOffset[k];
                        }
                        else
                        {
                            REACHED(40);
                            const int nYOffsetInBlockK = ((int)dfYOff) % nBlockYSize;
                            //CPLAssert(nYOffsetInBlockK - nYOffsetInBlock <= nUsedBlockHeight);
                            pabyLocalSrcData = pabyLocalSrcDataK0 +
                                (nYOffsetInBlockK - nYOffsetInBlock) * nBlockXSize * nBandsPerBlockDTSize;
                            dfYOff += dfSrcYInc;
                        }

                        if( bByteOnly )
                        {
                            REACHED(41);
                            for(int iBand=0;iBand<nBandCount;iBand++)
                            {
                                GByte* pabyLocalDataBand = pabyLocalData + iBand * nBandSpace;
                                const GByte* pabyLocalSrcDataBand = pabyLocalSrcData + (panBandMap[iBand]-1) /* * nDTSize*/;
                                *pabyLocalDataBand = *pabyLocalSrcDataBand;
                            }
                        }
                        else
                        {
                            REACHED(42);
                            for(int iBand=0;iBand<nBandCount;iBand++)
                            {
                                GByte* pabyLocalDataBand = pabyLocalData + iBand * nBandSpace;
                                const GByte* pabyLocalSrcDataBand = pabyLocalSrcData + (panBandMap[iBand]-1) * nDTSize;

                                GDALCopyWords(pabyLocalSrcDataBand,
                                                eDataType, 0,
                                                pabyLocalDataBand,
                                                eBufType, 0,
                                                1);
                            }
                        }
                    }
                }
            }

            y += ychunk;
        }
    }
    else if( FetchBuffer::bMinimizeIO &&
             TIFFIsTiled( hTIFF ) /* &&
             (nPlanarConfig == PLANARCONFIG_SEPARATE || nBandCount == 1) */ )
    {
        for(int iBand=0;iBand<nBandCount;iBand++)
        {
            GByte* pabyData = (GByte*)pData + iBand * nBandSpace;
            const int nBand = panBandMap[iBand];
            int anSrcYOffset[256];
            for(int y=0;y<nBufYSize;)
            {
                const double dfYOffStart = nYOff + (y + 0.5) * dfSrcYInc;
                const int nSrcLine = (int)dfYOffStart;
                const int nYOffsetInBlock = nSrcLine % nBlockYSize;
                const int nBlockYOff = nSrcLine / nBlockYSize;
                const int nBaseByteOffsetInBlock = nYOffsetInBlock * nBlockXSize * nBandsPerBlockDTSize;
                int ychunk = 1;
                int nLastSrcLineK = nSrcLine;
                anSrcYOffset[0] = 0;
                for(int k=1;k<nBufYSize-y;k++)
                {
                    int nSrcLineK = nYOff + (int)((y + k + 0.5) * dfSrcYInc);
                    const int nBlockYOffK = nSrcLineK / nBlockYSize;
                    if( k < 256)
                        anSrcYOffset[k] = ((nSrcLineK % nBlockYSize) - nYOffsetInBlock) * nBlockXSize * nBandsPerBlockDTSize;
                    if( nBlockYOffK != nBlockYOff )
                    {
                        break;
                    }
                    ychunk ++;
                    nLastSrcLineK = nSrcLineK;
                }
                const int nUsedBlockHeight = nLastSrcLineK - nSrcLine + 1;
                //CPLAssert(nUsedBlockHeight <= nBlockYSize);

                double dfSrcX = nXOff + 0.5 * dfSrcXInc;
                int nCurBlockXOff = 0;
                int nNextBlockXOff = 0;
                toff_t nCurOffset = 0;
                const GByte* pabyLocalSrcDataStartLine = NULL;
                for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                {
                    int nSrcPixel = (int)dfSrcX;
                    if( nSrcPixel >= nNextBlockXOff )
                    {
                        const int nBlockXOff = nSrcPixel / nBlockXSize;
                        nCurBlockXOff = nBlockXOff * nBlockXSize;
                        nNextBlockXOff = nCurBlockXOff + nBlockXSize;
                        int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
                        if ( nPlanarConfig == PLANARCONFIG_SEPARATE )
                        {
                            REACHED(43);
                            nBlockId += nBlocksPerBand * (nBand - 1);
                        }
                        else
                        {
                            REACHED(44);
                        }
                        nCurOffset = panOffsets[nBlockId];
                        if( nCurOffset != 0 )
                        {
                            pabyLocalSrcDataStartLine = oFetcher.FetchBytes(
                                    nCurOffset + nBaseByteOffsetInBlock,
                                    (int)nBlockXSize * nBandsPerBlock * nUsedBlockHeight, nDTSize,
                                    bIsByteSwapped, bIsComplex, nBlockId);
                            if( pabyLocalSrcDataStartLine == NULL )
                                return CE_Failure;

                            if ( nPlanarConfig == PLANARCONFIG_CONTIG )
                            {
                                REACHED(45);
                                pabyLocalSrcDataStartLine += (nBand - 1) * nDTSize;
                            }
                            else
                            {
                                REACHED(46);
                            }

                        }
                    }

                    if( nCurOffset == 0 )
                    {
                        REACHED(47);

                        for( int k = 0; k < ychunk; k ++ )
                        {
                            GByte* const pabyLocalData = pabyData + (y+k) * nLineSpace + x * nPixelSpace;

                            GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                            pabyLocalData, eBufType, 0,
                                            1);
                        }
                    }

                    else
                    {
                        const int nXOffsetInBlock = nSrcPixel - nCurBlockXOff;
                        double dfYOff = dfYOffStart;
                        const GByte* const pabyLocalSrcDataK0 =
                            pabyLocalSrcDataStartLine + nXOffsetInBlock * nBandsPerBlockDTSize;
                        GByte* pabyLocalData = pabyData + y * nLineSpace + x * nPixelSpace;
                        for( int k = 0; k < ychunk; k ++, pabyLocalData += nLineSpace )
                        {
                            const GByte* pabyLocalSrcData;
                            if( ychunk <= 256 )
                            {
                                REACHED(48);
                                pabyLocalSrcData = pabyLocalSrcDataK0 + anSrcYOffset[k];
                            }
                            else
                            {
                                REACHED(49);
                                const int nYOffsetInBlockK = ((int)dfYOff) % nBlockYSize;
                                //CPLAssert(nYOffsetInBlockK - nYOffsetInBlock <= nUsedBlockHeight);
                                pabyLocalSrcData = pabyLocalSrcDataK0 +
                                    (nYOffsetInBlockK - nYOffsetInBlock) * nBlockXSize * nBandsPerBlockDTSize;
                                dfYOff += dfSrcYInc;
                            }

                            if( bByteOnly )
                            {
                                REACHED(50);

                                *pabyLocalData = *pabyLocalSrcData;
                            }
                            else
                            {
                                REACHED(51);

                                GDALCopyWords(pabyLocalSrcData,
                                                eDataType, 0,
                                                pabyLocalData,
                                                eBufType, 0,
                                                1);
                            }
                        }
                    }
                }

                y += ychunk;
            }
        }
    }
    else if( bUseContigImplementation )
    {
        if( !FetchBuffer::bMinimizeIO && TIFFIsTiled( hTIFF ) )
        {
            GByte* pabyData = (GByte*)pData;
            for(int y=0;y<nBufYSize;y++)
            {
                const int nSrcLine = nYOff + (int)((y + 0.5) * dfSrcYInc);
                const int nBlockYOff = nSrcLine / nBlockYSize;
                const int nYOffsetInBlock = nSrcLine % nBlockYSize;
                const int nBaseByteOffsetInBlock = nYOffsetInBlock * nBlockXSize * nBandsPerBlockDTSize;

                if( bNoXResampling )
                {
                    GByte* pabyLocalData = pabyData + y * nLineSpace;
                    int nBlockXOff = nXOff / nBlockXSize;
                    int nXOffsetInBlock = nXOff % nBlockXSize;
                    int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

                    int x = 0;
                    while( x < nBufXSize )
                    {
                        const int nByteOffsetInBlock = nBaseByteOffsetInBlock +
                                        nXOffsetInBlock * nBandsPerBlockDTSize;
                        const toff_t nCurOffset = panOffsets[nBlockId];
                        const int nUsedBlockWidth = MIN((int)nBlockXSize - nXOffsetInBlock, nBufXSize - x);

                        int nIters = nUsedBlockWidth;
                        if( nCurOffset == 0 )
                        {
                            if( bByteNoXResampling )
                            {
                                REACHED(0);
                                while( nIters-- > 0 )
                                {
                                    for(int iBand=0;iBand<nBandCount;iBand++)
                                        pabyLocalData[iBand] = abyNoData;
                                    pabyLocalData += nPixelSpace;
                                }
                            }
                            else
                            {
                                REACHED(1);
                                while( nIters-- > 0 )
                                {
                                    GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                          pabyLocalData, eBufType, static_cast<int>(nBandSpace),
                                          nBandCount);
                                    pabyLocalData += nPixelSpace;
                                }
                            }
                        }
                        else
                        {
                            if( bNoTypeChange && nBands == nBandCount &&
                                nPixelSpace == nBandsPerBlockDTSize )
                            {
                                REACHED(2);
                                if( !oFetcher.FetchBytes(
                                        pabyLocalData,
                                        nCurOffset + nByteOffsetInBlock,
                                        nIters * nBandsPerBlock, nDTSize,
                                        bIsByteSwapped, bIsComplex, nBlockId) )
                                {
                                    return CE_Failure;
                                }
                                pabyLocalData += nIters * nBandsPerBlock * nDTSize;
                            }
                            else
                            {
                                const GByte* pabyLocalSrcData = oFetcher.FetchBytes(
                                        nCurOffset + nByteOffsetInBlock,
                                        nIters * nBandsPerBlock, nDTSize,
                                        bIsByteSwapped, bIsComplex, nBlockId);
                                if( pabyLocalSrcData == NULL )
                                    return CE_Failure;
                                if( bByteNoXResampling )
                                {
                                    REACHED(3);
                                    CopyContigByteMultiBand(pabyLocalSrcData,
                                                        nBandsPerBlockDTSize,
                                                        pabyLocalData,
                                                        static_cast<int>(nPixelSpace),
                                                        nIters,
                                                        nBandCount);
                                    pabyLocalData += nIters * nPixelSpace;
                                }
                                else
                                {
                                    REACHED(4);
                                    while( nIters-- > 0 )
                                    {
                                        GDALCopyWords(pabyLocalSrcData,
                                                      eDataType, nDTSize,
                                                      pabyLocalData,
                                                      eBufType, static_cast<int>(nBandSpace),
                                                      nBandCount);
                                        pabyLocalSrcData += nBandsPerBlockDTSize;
                                        pabyLocalData += nPixelSpace;
                                    }
                                }
                            }
                        }

                        nXOffsetInBlock = 0;
                        nBlockXOff ++;
                        nBlockId ++;
                        x += nUsedBlockWidth;
                    }
                }
                else /* contig, tiled, potential resampling and data type change */
                {
                    const GByte* pabyLocalSrcDataStartLine = NULL;
                    GByte* pabyLocalData = pabyData + y * nLineSpace;
                    double dfSrcX = nXOff + 0.5 * dfSrcXInc;
                    int nCurBlockXOff = 0;
                    int nNextBlockXOff = 0;
                    toff_t nCurOffset = 0;
                    for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                    {
                        int nSrcPixel = (int)dfSrcX;
                        if( nSrcPixel >= nNextBlockXOff )
                        {
                            const int nBlockXOff = nSrcPixel / nBlockXSize;
                            nCurBlockXOff = nBlockXOff * nBlockXSize;
                            nNextBlockXOff = nCurBlockXOff + nBlockXSize;
                            int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
                            nCurOffset = panOffsets[nBlockId];
                            if( nCurOffset != 0 )
                            {
                                pabyLocalSrcDataStartLine = oFetcher.FetchBytes(
                                        nCurOffset + nBaseByteOffsetInBlock,
                                        (int)nBlockXSize * nBandsPerBlock, nDTSize,
                                        bIsByteSwapped, bIsComplex, nBlockId);
                                if( pabyLocalSrcDataStartLine == NULL )
                                    return CE_Failure;
                            }
                        }
                        const int nXOffsetInBlock = nSrcPixel - nCurBlockXOff;

                        if( nCurOffset == 0 )
                        {
                            REACHED(5);
                            GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                          pabyLocalData,
                                          eBufType, static_cast<int>(nBandSpace),
                                          nBandCount);
                            pabyLocalData += nPixelSpace;
                        }
                        else
                        {
                            const GByte* pabyLocalSrcData = pabyLocalSrcDataStartLine +
                                    nXOffsetInBlock * nBandsPerBlockDTSize;

                            REACHED(6);
                            if( bByteOnly )
                            {
                                for(int iBand = 0; iBand < nBands; iBand ++ )
                                    pabyLocalData[iBand] = pabyLocalSrcData[iBand];
                            }
                            else
                            {
                                GDALCopyWords(pabyLocalSrcData,
                                            eDataType, nDTSize,
                                            pabyLocalData,
                                            eBufType, static_cast<int>(nBandSpace),
                                            nBandCount);
                            }
                            pabyLocalData += nPixelSpace;
                        }
                    }
                }
            }
        }
        else /* contig, stripped organized */
        {
            GByte* pabyData = (GByte*)pData;
            for(int y=0;y<nBufYSize;y++)
            {
                const int nSrcLine = nYOff + (int)((y + 0.5) * dfSrcYInc);
                const int nBlockYOff = nSrcLine / nBlockYSize;
                const int nYOffsetInBlock = nSrcLine % nBlockYSize;
                const int nBlockId = nBlockYOff;
                const toff_t nCurOffset = panOffsets[nBlockId];
                if( nCurOffset == 0 )
                {
                    REACHED(7);
                    for(int x=0;x<nBufXSize;x++)
                    {
                        GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                      pabyData + y * nLineSpace + x * nPixelSpace,
                                      eBufType, static_cast<int>(nBandSpace),
                                      nBandCount);
                    }
                }
                else
                {
                    GByte* pabyLocalData = pabyData + y * nLineSpace;
                    const int nBaseByteOffsetInBlock = (nYOffsetInBlock * nBlockXSize + nXOff) * nBandsPerBlockDTSize;

                    if( bNoXResamplingNoTypeChange && nBands == nBandCount &&
                        nPixelSpace == nBandsPerBlockDTSize )
                    {
                        REACHED(8);
                        if( !oFetcher.FetchBytes(
                            pabyLocalData,
                            nCurOffset + nBaseByteOffsetInBlock,
                            nXSize * nBandsPerBlock, nDTSize, bIsByteSwapped, bIsComplex, nBlockId) )
                        {
                            return CE_Failure;
                        }
                    }
                    else
                    {
                        const GByte* pabyLocalSrcData = oFetcher.FetchBytes(
                            nCurOffset + nBaseByteOffsetInBlock,
                            nXSize * nBandsPerBlock, nDTSize, bIsByteSwapped, bIsComplex, nBlockId);
                        if( pabyLocalSrcData == NULL )
                            return CE_Failure;

                        if( bByteNoXResampling )
                        {
                            REACHED(9);
                            CopyContigByteMultiBand(pabyLocalSrcData,
                                                nBandsPerBlockDTSize,
                                                pabyLocalData,
                                                static_cast<int>(nPixelSpace),
                                                nBufXSize,
                                                nBandCount);
                        }
                        else if( bByteOnly )
                        {
                            REACHED(10);
                            double dfSrcX = 0.5 * dfSrcXInc;
                            for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                            {
                                int nSrcPixelMinusXOff = (int)dfSrcX;
                                for(int iBand=0;iBand<nBandCount;iBand++)
                                {
                                    pabyLocalData[x * nPixelSpace + iBand /* * nBandSpace*/] =
                                        pabyLocalSrcData[nSrcPixelMinusXOff * nBandsPerBlockDTSize + iBand];
                                }
                            }
                        }
                        else
                        {
                            REACHED(11);
                            double dfSrcX = 0.5 * dfSrcXInc;
                            for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                            {
                                int nSrcPixelMinusXOff = (int)dfSrcX;
                                GDALCopyWords(pabyLocalSrcData + nSrcPixelMinusXOff * nBandsPerBlockDTSize,
                                            eDataType, nDTSize,
                                            pabyLocalData + x * nPixelSpace,
                                            eBufType, static_cast<int>(nBandSpace),
                                            nBandCount);
                            }
                        }
                    }
                }
            }
        }
    }
    else /* non contig reading case */
    {
        if( !FetchBuffer::bMinimizeIO && TIFFIsTiled( hTIFF ) )
        {
            for(int iBand = 0; iBand < nBandCount; iBand ++ )
            {
                const int nBand = panBandMap[iBand];
                GByte* const pabyData = (GByte*)pData + iBand * nBandSpace;
                for(int y=0;y<nBufYSize;y++)
                {
                    const int nSrcLine = nYOff + (int)((y + 0.5) * dfSrcYInc);
                    const int nBlockYOff = nSrcLine / nBlockYSize;
                    const int nYOffsetInBlock = nSrcLine % nBlockYSize;

                    int nBaseByteOffsetInBlock = nYOffsetInBlock * nBlockXSize * nBandsPerBlockDTSize;
                    if ( nPlanarConfig == PLANARCONFIG_CONTIG )
                    {
                        REACHED(12);
                        nBaseByteOffsetInBlock += (nBand-1) * nDTSize;
                    }
                    else
                    {
                        REACHED(13);
                    }

                    if( bNoXResampling )
                    {
                        GByte* pabyLocalData = pabyData + y * nLineSpace;
                        int nBlockXOff = nXOff / nBlockXSize;
                        int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
                        if ( nPlanarConfig == PLANARCONFIG_SEPARATE )
                        {
                            REACHED(14);
                            nBlockId += nBlocksPerBand * (nBand - 1);
                        }
                        else
                        {
                            REACHED(15);
                        }
                        int nXOffsetInBlock = nXOff % nBlockXSize;

                        int x = 0;
                        while( x < nBufXSize )
                        {
                            const int nByteOffsetInBlock = nBaseByteOffsetInBlock +
                                    nXOffsetInBlock * nBandsPerBlockDTSize;
                            const toff_t nCurOffset = panOffsets[nBlockId];
                            const int nUsedBlockWidth = MIN((int)nBlockXSize - nXOffsetInBlock, nBufXSize - x);
                            int nIters = nUsedBlockWidth;

                            if( nCurOffset == 0 )
                            {
                                REACHED(16);
                                GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                              pabyLocalData, eBufType, static_cast<int>(nPixelSpace),
                                              nIters);
                                pabyLocalData += nIters * nPixelSpace;
                            }
                            else
                            {
                                if( bNoTypeChange && nPixelSpace == nBandsPerBlockDTSize )
                                {
                                    REACHED(17);
                                    if( !oFetcher.FetchBytes( pabyLocalData,
                                            nCurOffset + nByteOffsetInBlock,
                                            (nIters - 1) * nBandsPerBlock + 1, nDTSize,
                                            bIsByteSwapped, bIsComplex, nBlockId) )
                                    {
                                        return CE_Failure;
                                    }
                                    pabyLocalData += nIters * nPixelSpace;
                                }
                                else
                                {
                                    const GByte* pabyLocalSrcData = oFetcher.FetchBytes(
                                        nCurOffset + nByteOffsetInBlock,
                                        (nIters - 1) * nBandsPerBlock + 1, nDTSize,
                                        bIsByteSwapped, bIsComplex, nBlockId);
                                    if( pabyLocalSrcData == NULL )
                                        return CE_Failure;

                                    REACHED(18);
                                    GDALCopyWords(pabyLocalSrcData, eDataType, nBandsPerBlockDTSize,
                                                    pabyLocalData, eBufType, static_cast<int>(nPixelSpace),
                                                    nIters);
                                    pabyLocalData += nIters * nPixelSpace;
                                }
                            }

                            nXOffsetInBlock = 0;
                            nBlockXOff ++;
                            nBlockId ++;
                            x += nUsedBlockWidth;
                        }
                    }
                    else /* non contig reading, tiled, potential resampling and data type change */
                    {
                        const GByte* pabyLocalSrcDataStartLine = NULL;
                        GByte* pabyLocalData = pabyData + y * nLineSpace;
                        double dfSrcX = nXOff + 0.5 * dfSrcXInc;
                        int nCurBlockXOff = 0;
                        int nNextBlockXOff = 0;
                        toff_t nCurOffset = 0;
                        for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                        {
                            int nSrcPixel = (int)dfSrcX;
                            if( nSrcPixel >= nNextBlockXOff )
                            {
                                const int nBlockXOff = nSrcPixel / nBlockXSize;
                                nCurBlockXOff = nBlockXOff * nBlockXSize;
                                nNextBlockXOff = nCurBlockXOff + nBlockXSize;
                                int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;
                                if ( nPlanarConfig == PLANARCONFIG_SEPARATE )
                                {
                                    REACHED(19);
                                    nBlockId += nBlocksPerBand * (nBand - 1);
                                }
                                else
                                {
                                    REACHED(20);
                                }
                                nCurOffset = panOffsets[nBlockId];
                                if( nCurOffset != 0 )
                                {
                                    pabyLocalSrcDataStartLine = oFetcher.FetchBytes(
                                            nCurOffset + nBaseByteOffsetInBlock,
                                            (int)nBlockXSize * nBandsPerBlock, nDTSize,
                                            bIsByteSwapped, bIsComplex, nBlockId);
                                    if( pabyLocalSrcDataStartLine == NULL )
                                        return CE_Failure;
                                }
                            }
                            const int nXOffsetInBlock = nSrcPixel - nCurBlockXOff;

                            if( nCurOffset == 0 )
                            {
                                REACHED(21);
                                GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                              pabyLocalData,
                                              eBufType, 0,
                                              1);
                                pabyLocalData += nPixelSpace;
                            }
                            else
                            {
                                const GByte* pabyLocalSrcData = pabyLocalSrcDataStartLine +
                                        nXOffsetInBlock * nBandsPerBlockDTSize;

                                REACHED(22);
                                if( bByteOnly )
                                    *pabyLocalData = *pabyLocalSrcData;
                                else
                                {
                                    GDALCopyWords(pabyLocalSrcData,
                                                eDataType, 0,
                                                pabyLocalData,
                                                eBufType, 0,
                                                1);
                                }
                                pabyLocalData += nPixelSpace;
                            }
                        }
                    }
                }
            }
        }
        else /* non contig reading, stripped */
        {
            for(int iBand = 0; iBand < nBandCount; iBand ++ )
            {
                const int nBand = panBandMap[iBand];
                GByte* pabyData = (GByte*)pData + iBand * nBandSpace;
                for(int y=0;y<nBufYSize;y++)
                {
                    const int nSrcLine = nYOff + (int)((y + 0.5) * dfSrcYInc);
                    const int nBlockYOff = nSrcLine / nBlockYSize;
                    const int nYOffsetInBlock = nSrcLine % nBlockYSize;
                    int nBlockId = nBlockYOff;
                    if ( nPlanarConfig == PLANARCONFIG_SEPARATE )
                    {
                        REACHED(23);
                        nBlockId += nBlocksPerBand * (nBand - 1);
                    }
                    else
                    {
                        REACHED(24);
                    }
                    const toff_t nCurOffset = panOffsets[nBlockId];
                    if( nCurOffset == 0 )
                    {
                        REACHED(25);
                        GDALCopyWords(&dfNoData, GDT_Float64, 0,
                                      pabyData + y * nLineSpace, eBufType, static_cast<int>(nPixelSpace),
                                      nBufXSize);
                    }
                    else
                    {
                        int nBaseByteOffsetInBlock =
                            (nYOffsetInBlock * nBlockXSize + nXOff) * nBandsPerBlockDTSize;
                        if ( nPlanarConfig == PLANARCONFIG_CONTIG )
                            nBaseByteOffsetInBlock += (nBand-1) * nDTSize;

                        GByte* pabyLocalData = pabyData + y * nLineSpace;
                        if( bNoXResamplingNoTypeChange && nPixelSpace == nBandsPerBlockDTSize )
                        {
                            REACHED(26);
                            if( !oFetcher.FetchBytes(pabyLocalData,
                                        nCurOffset + nBaseByteOffsetInBlock,
                                        (nXSize-1) * nBandsPerBlock + 1, nDTSize,
                                        bIsByteSwapped, bIsComplex, nBlockId) )
                            {
                                return CE_Failure;
                            }
                        }
                        else
                        {
                            const GByte* pabyLocalSrcData = oFetcher.FetchBytes(
                                nCurOffset + nBaseByteOffsetInBlock,
                                (nXSize-1) * nBandsPerBlock + 1, nDTSize,
                                bIsByteSwapped, bIsComplex, nBlockId);
                            if( pabyLocalSrcData == NULL )
                                return CE_Failure;

                            if( bNoXResamplingNoTypeChange )
                            {
                                REACHED(27);
                                GDALCopyWords(pabyLocalSrcData,
                                              eDataType, nBandsPerBlockDTSize,
                                              pabyLocalData, eBufType, static_cast<int>(nPixelSpace),
                                              nBufXSize);
                            }
                            else if( bByteOnly )
                            {
                                REACHED(28);
                                double dfSrcX = 0.5 * dfSrcXInc;
                                for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                                {
                                    int nSrcPixelMinusXOff = (int)dfSrcX;
                                    pabyLocalData[x * nPixelSpace] =
                                        pabyLocalSrcData[nSrcPixelMinusXOff * nBandsPerBlockDTSize];
                                }
                            }
                            else
                            {
                                REACHED(29);
                                double dfSrcX = 0.5 * dfSrcXInc;
                                for(int x=0;x<nBufXSize;x++, dfSrcX += dfSrcXInc)
                                {
                                    int nSrcPixelMinusXOff = (int)dfSrcX;
                                    GDALCopyWords(pabyLocalSrcData +
                                                    nSrcPixelMinusXOff * nBandsPerBlockDTSize,
                                                eDataType, 0,
                                                pabyLocalData + x * nPixelSpace,
                                                eBufType, 0,
                                                1);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return CE_None;
}

/************************************************************************/
/*                           DirectIO()                                 */
/************************************************************************/

/* Reads directly bytes from the file using ReadMultiRange(), and by-pass */
/* block reading. Restricted to simple TIFF configurations */
/* (uncompressed data, standard data types). Particularly useful to extract */
/* sub-windows of data on a large /vsicurl dataset). */
/* Returns -1 if DirectIO() can't be supported on that file */

int GTiffDataset::DirectIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace,
                               GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg )
{
    const GDALDataType eDataType = GetRasterBand(1)->GetRasterDataType();
    const int nDTSizeBits = GDALGetDataTypeSize(eDataType);
    if( !(eRWFlag == GF_Read &&
          nCompression == COMPRESSION_NONE &&
          (nPhotometric == PHOTOMETRIC_MINISBLACK ||
           nPhotometric == PHOTOMETRIC_RGB ||
           nPhotometric == PHOTOMETRIC_PALETTE) &&
          nBitsPerSample == nDTSizeBits &&
          SetDirectory() /* very important to make hTIFF uptodate! */) )
    {
        return -1;
    }

    /* we only know how to deal with nearest neighbour in this optimized routine */
    if( (nXSize != nBufXSize || nYSize != nBufYSize) &&
        psExtraArg != NULL &&
        psExtraArg->eResampleAlg != GRIORA_NearestNeighbour )
    {
        return -1;
    }

    /* if the file is band interleave or only one band is requested, then */
    /* fallback to band DirectIO */
    bool bUseBandRasterIO = false;
    if( nPlanarConfig == PLANARCONFIG_SEPARATE || nBandCount == 1 )
    {
        bUseBandRasterIO = true;
    }
    else
    {
        /* For the sake of simplicity, only deals with "naturally ordered" bands */
        for(int iBand = 0; iBand < nBandCount; iBand ++ )
        {
            if( panBandMap[iBand] != iBand + 1)
            {
                bUseBandRasterIO = true;
                break;
            }
        }
    }
    if( bUseBandRasterIO )
    {
        CPLErr eErr = CE_None;
        for(int iBand = 0; eErr == CE_None && iBand < nBandCount; iBand ++ )
        {
            eErr = GetRasterBand(panBandMap[iBand])->RasterIO(
                                       eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                       (GByte*)pData + iBand * nBandSpace,
                                       nBufXSize, nBufYSize,
                                       eBufType,
                                       nPixelSpace, nLineSpace,
                                       psExtraArg);
        }
        return eErr;
    }

    /*CPLDebug("GTiff", "DirectIO(%d,%d,%d,%d -> %dx%d)",
             nXOff, nYOff, nXSize, nYSize,
             nBufXSize, nBufYSize);*/

    /* No need to look if overviews can satisfy the request as it has already */
    /* been done in GTiffDataset::IRasterIO() */

    /* Make sure that TIFFTAG_STRIPOFFSETS is up-to-date */
    if (GetAccess() == GA_Update)
    {
        FlushCache();
        VSI_TIFFFlushBufferedWrite( TIFFClientdata( hTIFF ) );
    }

    if( TIFFIsTiled( hTIFF ) )
    {
        if( m_pTempBufferForCommonDirectIO == NULL )
        {
            const int nDTSize = nDTSizeBits / 8;
            m_nTempBufferForCommonDirectIOSize =
                (size_t)(nBlockXSize * nBlockYSize * nDTSize *
                ((nPlanarConfig == PLANARCONFIG_CONTIG) ? nBands : 1));

            m_pTempBufferForCommonDirectIO = (GByte*)VSI_MALLOC_VERBOSE(m_nTempBufferForCommonDirectIOSize);
            if( m_pTempBufferForCommonDirectIO == NULL )
                return CE_Failure;
        }

        VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( hTIFF ));
        FetchBufferDirectIO oFetcher(fp, m_pTempBufferForCommonDirectIO,
                                     m_nTempBufferForCommonDirectIOSize);

        return CommonDirectIO( oFetcher,
                            nXOff, nYOff, nXSize, nYSize,
                            pData, nBufXSize, nBufYSize,
                            eBufType,
                            nBandCount, panBandMap,
                            nPixelSpace, nLineSpace,
                            nBandSpace );
    }

    /* Get strip offsets */
    toff_t *panTIFFOffsets = NULL;
    if ( !TIFFGetField( hTIFF, TIFFTAG_STRIPOFFSETS, &panTIFFOffsets ) ||
         panTIFFOffsets == NULL )
    {
        return CE_Failure;
    }

    int nReqXSize = nXSize; /* sub-sampling or over-sampling can only be done at last stage */
    int nReqYSize = MIN(nBufYSize, nYSize); /* we can do sub-sampling at the extraction stage */
    void** ppData = (void**) VSI_MALLOC_VERBOSE(nReqYSize * sizeof(void*));
    vsi_l_offset* panOffsets = (vsi_l_offset*)
                            VSI_MALLOC_VERBOSE(nReqYSize * sizeof(vsi_l_offset));
    size_t* panSizes = (size_t*) VSI_MALLOC_VERBOSE(nReqYSize * sizeof(size_t));
    int nDTSize = GDALGetDataTypeSize(eDataType) / 8;
    void* pTmpBuffer = NULL;
    int eErr = CE_None;
    int nContigBands = nBands;
    int nSrcPixelSize = nDTSize * nContigBands;

    if (ppData == NULL || panOffsets == NULL || panSizes == NULL)
        eErr = CE_Failure;
    /* For now we always allocate a temp buffer as it's easier */
    else /*if (nXSize != nBufXSize || nYSize != nBufYSize ||
             eBufType != eDataType ||
             nPixelSpace != GDALGetDataTypeSize(eBufType) / 8 ||
             check if the user buffer is large enough)*/
    {
        /* We need a temporary buffer for over-sampling/sub-sampling */
        /* and/or data type conversion */
        pTmpBuffer = VSI_MALLOC_VERBOSE(nReqXSize * nReqYSize * nSrcPixelSize);
        if (pTmpBuffer == NULL)
            eErr = CE_Failure;
    }

    /* Prepare data extraction */
    const double dfSrcYInc = nYSize / (double) nBufYSize;

    int iLine;
    for(iLine=0;eErr == CE_None && iLine<nReqYSize;iLine++)
    {
        /*if (pTmpBuffer == NULL)
            ppData[iLine] = ((GByte*)pData) + iLine * nLineSpace;
        else*/
            ppData[iLine] = ((GByte*)pTmpBuffer) + iLine * nReqXSize * nSrcPixelSize;
        int nSrcLine;
        if (nBufYSize < nYSize) /* Sub-sampling in y */
            nSrcLine = nYOff + (int)((iLine + 0.5) * dfSrcYInc);
        else
            nSrcLine = nYOff + iLine;

        int nBlockXOff = 0;
        int nBlockYOff = nSrcLine / nBlockYSize;
        int nYOffsetInBlock = nSrcLine % nBlockYSize;
        int nBlocksPerRow = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
        int nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

        panOffsets[iLine] = panTIFFOffsets[nBlockId];
        if (panOffsets[iLine] == 0) /* We don't support sparse files */
            eErr = -1;

        panOffsets[iLine] += (nXOff + nYOffsetInBlock * nBlockXSize) * nSrcPixelSize;
        panSizes[iLine] = nReqXSize * nSrcPixelSize;
    }

    /* Extract data from the file */
    if (eErr == CE_None)
    {
        VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( hTIFF ));
        int nRet = VSIFReadMultiRangeL(nReqYSize, ppData, panOffsets, panSizes, fp);
        if (nRet != 0)
            eErr = CE_Failure;
    }

    /* Byte-swap if necessary */
    if (eErr == CE_None && TIFFIsByteSwapped(hTIFF))
    {
        for(iLine=0;iLine<nReqYSize;iLine++)
        {
            if( GDALDataTypeIsComplex(eDataType) )
                GDALSwapWords( ppData[iLine], nDTSize / 2, 2 * nReqXSize * nContigBands, nDTSize / 2);
            else
                GDALSwapWords( ppData[iLine], nDTSize, nReqXSize * nContigBands, nDTSize);
        }
    }

    /* Over-sampling/sub-sampling and/or data type conversion */
    const double dfSrcXInc = nXSize / (double) nBufXSize;
    if (eErr == CE_None && pTmpBuffer != NULL)
    {
        for(int iY=0;iY<nBufYSize;iY++)
        {
            int iSrcY = (nBufYSize <= nYSize) ? iY :
                            (int)((iY + 0.5) * dfSrcYInc);
            /* Optimization: no resampling, no data type change, number of bands requested == number of bands */
            /* and buffer is packed pixel-interleaved */
            if (nBufXSize == nXSize && nContigBands == nBandCount &&
                eDataType == eBufType &&
                nBandSpace == nDTSize && nPixelSpace == nBandCount * nBandSpace )
            {
                memcpy( ((GByte*)pData) + iY * nLineSpace, ppData[iSrcY],
                        static_cast<size_t>(nReqXSize * nPixelSpace) );
            }
            /* Other optimization: no resampling, no data type change, */
            /* data type is Byte */
            else if (nBufXSize == nXSize &&
                     eDataType == eBufType && eDataType == GDT_Byte )
            {
                GByte* pabySrcData = ((GByte*)ppData[iSrcY]);
                GByte* pabyDstData = ((GByte*)pData) + iY * nLineSpace;
                if( nBandSpace == 1 && nPixelSpace > nBandCount )
                {
                    /* buffer is pixel-interleaved (with some stridding between pixels) */
                    CopyContigByteMultiBand(pabySrcData, nSrcPixelSize,
                                            pabyDstData, static_cast<int>(nPixelSpace),
                                            nBufXSize, nBandCount);
                }
                else
                {
                    for(int iBand = 0; iBand < nBandCount; iBand ++ )
                    {
                        GDALCopyWords(pabySrcData + iBand, GDT_Byte, nSrcPixelSize,
                                      pabyDstData + iBand * nBandSpace, GDT_Byte, static_cast<int>(nPixelSpace),
                                      nBufXSize);
                    }
                }
            }
            /* General case */
            else
            {
                for(int iBand = 0; iBand < nBandCount; iBand ++ )
                {
                    GByte* pabySrcData = ((GByte*)ppData[iSrcY]) + iBand * nDTSize;
                    GByte* pabyDstData = ((GByte*)pData) + iBand * nBandSpace + iY * nLineSpace;
                    if( eDataType == GDT_Byte && eBufType == GDT_Byte )
                    {
                        double dfSrcX = 0.5 * dfSrcXInc;
                        for(int iX=0;iX<nBufXSize;iX++, dfSrcX += dfSrcXInc)
                        {
                            int iSrcX = (int)dfSrcX;
                            pabyDstData[iX * nPixelSpace] = pabySrcData[iSrcX * nSrcPixelSize];
                        }
                    }
                    else
                    {
                        double dfSrcX = 0.5 * dfSrcXInc;
                        for(int iX=0;iX<nBufXSize;iX++, dfSrcX += dfSrcXInc)
                        {
                            int iSrcX = (int)dfSrcX;
                            GDALCopyWords( pabySrcData + iSrcX * nSrcPixelSize,
                                        eDataType, 0,
                                        pabyDstData + iX * nPixelSpace,
                                        eBufType, 0, 1);
                        }
                    }
                }
            }
        }
    }

    /* Cleanup */
    CPLFree(pTmpBuffer);
    CPLFree(ppData);
    CPLFree(panOffsets);
    CPLFree(panSizes);

    return eErr;
}


/************************************************************************/
/*                            IRasterIO()                               */
/************************************************************************/

CPLErr GTiffRasterBand::IRasterIO( GDALRWFlag eRWFlag,
                                  int nXOff, int nYOff, int nXSize, int nYSize,
                                  void * pData, int nBufXSize, int nBufYSize,
                                  GDALDataType eBufType,
                                  GSpacing nPixelSpace, GSpacing nLineSpace,
                                  GDALRasterIOExtraArg* psExtraArg )
{
    CPLErr eErr;

    //CPLDebug("GTiff", "RasterIO(%d, %d, %d, %d, %d, %d)",
    //         nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize);

    /* Try to pass the request to the most appropriate overview dataset */
    if( nBufXSize < nXSize && nBufYSize < nYSize )
    {
        int bTried;
        poGDS->nJPEGOverviewVisibilityFlag ++;
        eErr = TryOverviewRasterIO( eRWFlag,
                                    nXOff, nYOff, nXSize, nYSize,
                                    pData, nBufXSize, nBufYSize,
                                    eBufType,
                                    nPixelSpace, nLineSpace,
                                    psExtraArg,
                                    &bTried );
        poGDS->nJPEGOverviewVisibilityFlag --;
        if( bTried )
            return eErr;
    }


    if( poGDS->eVirtualMemIOUsage != VIRTUAL_MEM_IO_NO )
    {
        int nErr = poGDS->VirtualMemIO(
                eRWFlag, nXOff, nYOff, nXSize, nYSize,
                pData, nBufXSize, nBufYSize, eBufType,
                1, &nBand, nPixelSpace, nLineSpace, 0, psExtraArg);
        if (nErr >= 0)
            return (CPLErr)nErr;
    }
    if (poGDS->bDirectIO)
    {
        int nErr = DirectIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                        pData, nBufXSize, nBufYSize, eBufType,
                        nPixelSpace, nLineSpace, psExtraArg);
        if (nErr >= 0)
            return (CPLErr)nErr;
    }

    if (poGDS->nBands != 1 &&
        poGDS->nPlanarConfig == PLANARCONFIG_CONTIG &&
        eRWFlag == GF_Read &&
        nXSize == nBufXSize && nYSize == nBufYSize)
    {
        int nBlockX1 = nXOff / nBlockXSize;
        int nBlockY1 = nYOff / nBlockYSize;
        int nBlockX2 = (nXOff + nXSize - 1) / nBlockXSize;
        int nBlockY2 = (nYOff + nYSize - 1) / nBlockYSize;
        int nXBlocks = nBlockX2 - nBlockX1 + 1;
        int nYBlocks = nBlockY2 - nBlockY1 + 1;
        GIntBig nRequiredMem = (GIntBig)poGDS->nBands * nXBlocks * nYBlocks *
                                nBlockXSize * nBlockYSize *
                               (GDALGetDataTypeSize(eDataType) / 8);
        if (nRequiredMem > GDALGetCacheMax64())
        {
            if (!poGDS->bHasWarnedDisableAggressiveBandCaching)
            {
                CPLDebug("GTiff", "Disable aggressive band caching. Cache not big enough. "
                         "At least " CPL_FRMT_GIB " bytes necessary", nRequiredMem);
                poGDS->bHasWarnedDisableAggressiveBandCaching = TRUE;
            }
            poGDS->bLoadingOtherBands = TRUE;
        }
    }

    poGDS->nJPEGOverviewVisibilityFlag ++;
    eErr = GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                        pData, nBufXSize, nBufYSize, eBufType,
                                        nPixelSpace, nLineSpace, psExtraArg);
    poGDS->nJPEGOverviewVisibilityFlag --;

    poGDS->bLoadingOtherBands = FALSE;

    return eErr;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr GTiffRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                    void * pImage )

{
    int nBlockBufSize, nBlockId, nBlockIdBand0;
    CPLErr eErr = CE_None;

    if (!poGDS->SetDirectory())
        return CE_Failure;

    if( TIFFIsTiled(poGDS->hTIFF) )
        nBlockBufSize = static_cast<int>(TIFFTileSize( poGDS->hTIFF ));
    else
    {
        CPLAssert( nBlockXOff == 0 );
        nBlockBufSize = static_cast<int>(TIFFStripSize( poGDS->hTIFF ));
    }

    CPLAssert(nBlocksPerRow != 0);
    nBlockIdBand0 = nBlockXOff + nBlockYOff * nBlocksPerRow;
    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
        nBlockId = nBlockIdBand0 + (nBand-1) * poGDS->nBlocksPerBand;
    else
        nBlockId = nBlockIdBand0;

/* -------------------------------------------------------------------- */
/*      The bottom most partial tiles and strips are sometimes only     */
/*      partially encoded.  This code reduces the requested data so     */
/*      an error won't be reported in this case. (#1179)                */
/* -------------------------------------------------------------------- */
    int nBlockReqSize = nBlockBufSize;

    if( (nBlockYOff+1) * nBlockYSize > nRasterYSize )
    {
        nBlockReqSize = (nBlockBufSize / nBlockYSize)
            * (nBlockYSize - (((nBlockYOff+1) * nBlockYSize) % nRasterYSize));
    }

    poGDS->WaitCompletionForBlock(nBlockId);

/* -------------------------------------------------------------------- */
/*      Handle the case of a strip or tile that doesn't exist yet.      */
/*      Just set to zeros and return.                                   */
/* -------------------------------------------------------------------- */
    if( nBlockId != poGDS->nLoadedBlock && !poGDS->IsBlockAvailable(nBlockId) )
    {
        NullBlock( pImage );
        return CE_None;
    }

    if( poGDS->bStreamingIn &&
        !(poGDS->nBands > 1 && poGDS->nPlanarConfig == PLANARCONFIG_CONTIG && nBlockId == poGDS->nLoadedBlock) )
    {
        toff_t* panOffsets = NULL;
        TIFFGetField( poGDS->hTIFF, (TIFFIsTiled( poGDS->hTIFF )) ? TIFFTAG_TILEOFFSETS : TIFFTAG_STRIPOFFSETS , &panOffsets );
        if( panOffsets == NULL )
            return CE_Failure;
        if( panOffsets[nBlockId] < VSIFTellL(poGDS->fpL) )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Trying to load block %d at offset " CPL_FRMT_GUIB
                     " whereas current pos is " CPL_FRMT_GUIB " (backward read not supported)",
                     nBlockId, (GUIntBig)panOffsets[nBlockId],
                     (GUIntBig)VSIFTellL(poGDS->fpL) );
            return CE_Failure;
        }
    }

/* -------------------------------------------------------------------- */
/*      Handle simple case (separate, onesampleperpixel)                */
/* -------------------------------------------------------------------- */
    if( poGDS->nBands == 1
        || poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
    {
        if( nBlockReqSize < nBlockBufSize )
            memset( pImage, 0, nBlockBufSize );

        if( TIFFIsTiled( poGDS->hTIFF ) )
        {
            if( TIFFReadEncodedTile( poGDS->hTIFF, nBlockId, pImage,
                                     nBlockReqSize ) == -1
                && !poGDS->bIgnoreReadErrors )
            {
                memset( pImage, 0, nBlockBufSize );
                CPLError( CE_Failure, CPLE_AppDefined,
                          "TIFFReadEncodedTile() failed.\n" );

                eErr = CE_Failure;
            }
        }
        else
        {
            if( TIFFReadEncodedStrip( poGDS->hTIFF, nBlockId, pImage,
                                      nBlockReqSize ) == -1
                && !poGDS->bIgnoreReadErrors )
            {
                memset( pImage, 0, nBlockBufSize );
                CPLError( CE_Failure, CPLE_AppDefined,
                        "TIFFReadEncodedStrip() failed.\n" );

                eErr = CE_Failure;
            }
        }

        return eErr;
    }

/* -------------------------------------------------------------------- */
/*      Load desired block                                              */
/* -------------------------------------------------------------------- */
    eErr = poGDS->LoadBlockBuf( nBlockId );
    if( eErr != CE_None )
    {
        memset( pImage, 0,
                nBlockXSize * nBlockYSize
                * (GDALGetDataTypeSize(eDataType) / 8) );
        return eErr;
    }

/* -------------------------------------------------------------------- */
/*      Special case for YCbCr subsampled data.                         */
/* -------------------------------------------------------------------- */
#ifdef notdef
    if( (eBandInterp == GCI_YCbCr_YBand
         || eBandInterp == GCI_YCbCr_CbBand
         ||  eBandInterp == GCI_YCbCr_CrBand)
        && poGDS->nBitsPerSample == 8 )
    {
        uint16 hs, vs;
        int iX, iY;

        TIFFGetFieldDefaulted( poGDS->hTIFF, TIFFTAG_YCBCRSUBSAMPLING,
                               &hs, &vs);

        for( iY = 0; iY < nBlockYSize; iY++ )
        {
            for( iX = 0; iX < nBlockXSize; iX++ )
            {
                int iBlock = (iY / vs) * (nBlockXSize/hs) + (iX / hs);
                GByte *pabySrcBlock = poGDS->pabyBlockBuf +
                    (vs * hs + 2) * iBlock;

                if( eBandInterp == GCI_YCbCr_YBand )
                    ((GByte *)pImage)[iY*nBlockXSize + iX] =
                        pabySrcBlock[(iX % hs) + (iY % vs) * hs];
                else if( eBandInterp == GCI_YCbCr_CbBand )
                    ((GByte *)pImage)[iY*nBlockXSize + iX] =
                        pabySrcBlock[vs * hs + 0];
                else if( eBandInterp == GCI_YCbCr_CrBand )
                    ((GByte *)pImage)[iY*nBlockXSize + iX] =
                        pabySrcBlock[vs * hs + 1];
            }
        }

        return CE_None;
    }
#endif

    int nWordBytes = poGDS->nBitsPerSample / 8;
    GByte* pabyImage = poGDS->pabyBlockBuf + (nBand - 1) * nWordBytes;

    GDALCopyWords(pabyImage, eDataType, poGDS->nBands * nWordBytes,
                  pImage, eDataType, nWordBytes,
                  nBlockXSize * nBlockYSize);

    if (eErr == CE_None)
        eErr = FillCacheForOtherBands(nBlockXOff, nBlockYOff);

    return eErr;
}


/************************************************************************/
/*                       FillCacheForOtherBands()                       */
/************************************************************************/

CPLErr GTiffRasterBand::FillCacheForOtherBands( int nBlockXOff, int nBlockYOff )

{
    CPLErr eErr = CE_None;
/* -------------------------------------------------------------------- */
/*      In the fairly common case of pixel interleaved 8bit data        */
/*      that is multi-band, lets push the rest of the data into the     */
/*      block cache too, to avoid (hopefully) having to redecode it.    */
/*                                                                      */
/*      Our following logic actually depends on the fact that the       */
/*      this block is already loaded, so subsequent calls will end      */
/*      up back in this method and pull from the loaded block.          */
/*                                                                      */
/*      Be careful not entering this portion of code from               */
/*      the other bands, otherwise we'll get very deep nested calls     */
/*      and O(nBands^2) performance !                                   */
/*                                                                      */
/*      If there are many bands and the block cache size is not big     */
/*      enough to accommodate the size of all the blocks, don't enter   */
/* -------------------------------------------------------------------- */
    if( poGDS->nBands != 1 && !poGDS->bLoadingOtherBands &&
        nBlockXSize * nBlockYSize * (GDALGetDataTypeSize(eDataType) / 8) < GDALGetCacheMax64() / poGDS->nBands)
    {
        int iOtherBand;

        poGDS->bLoadingOtherBands = TRUE;

        for( iOtherBand = 1; iOtherBand <= poGDS->nBands; iOtherBand++ )
        {
            if( iOtherBand == nBand )
                continue;

            GDALRasterBlock *poBlock;

            poBlock = poGDS->GetRasterBand(iOtherBand)->
                GetLockedBlockRef(nBlockXOff,nBlockYOff);
            if (poBlock == NULL)
            {
                eErr = CE_Failure;
                break;
            }
            poBlock->DropLock();
        }

        poGDS->bLoadingOtherBands = FALSE;
    }

    return eErr;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr GTiffRasterBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
                                     void * pImage )

{
    int nBlockId;
    CPLErr      eErr = CE_None;

    if (poGDS->bDebugDontWriteBlocks)
        return CE_None;

    if (poGDS->bWriteErrorInFlushBlockBuf)
    {
        /* Report as an error if a previously loaded block couldn't be */
        /* written correctly */
        poGDS->bWriteErrorInFlushBlockBuf = FALSE;
        return CE_Failure;
    }

    if (!poGDS->SetDirectory())
        return CE_Failure;

    CPLAssert( poGDS != NULL
               && nBlockXOff >= 0
               && nBlockYOff >= 0
               && pImage != NULL );
    CPLAssert(nBlocksPerRow != 0);

/* -------------------------------------------------------------------- */
/*      Handle case of "separate" images                                */
/* -------------------------------------------------------------------- */
    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE
        || poGDS->nBands == 1 )
    {
        nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow
            + (nBand-1) * poGDS->nBlocksPerBand;

        eErr = poGDS->WriteEncodedTileOrStrip(nBlockId, pImage, TRUE);

        return eErr;
    }

/* -------------------------------------------------------------------- */
/*      Handle case of pixel interleaved (PLANARCONFIG_CONTIG) images.  */
/* -------------------------------------------------------------------- */
    nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

    eErr = poGDS->LoadBlockBuf( nBlockId );
    if( eErr != CE_None )
        return eErr;

/* -------------------------------------------------------------------- */
/*      On write of pixel interleaved data, we might as well flush      */
/*      out any other bands that are dirty in our cache.  This is       */
/*      especially helpful when writing compressed blocks.              */
/* -------------------------------------------------------------------- */
    int iBand;
    int nWordBytes = poGDS->nBitsPerSample / 8;
    int nBands = poGDS->nBands;

    for( iBand = 0; iBand < nBands; iBand++ )
    {
        const GByte *pabyThisImage = NULL;
        GDALRasterBlock *poBlock = NULL;

        if( iBand+1 == nBand )
            pabyThisImage = (GByte *) pImage;
        else
        {
            poBlock = ((GTiffRasterBand *)poGDS->GetRasterBand( iBand+1 ))
                ->TryGetLockedBlockRef( nBlockXOff, nBlockYOff );

            if( poBlock == NULL )
                continue;

            if( !poBlock->GetDirty() )
            {
                poBlock->DropLock();
                continue;
            }

            pabyThisImage = (GByte *) poBlock->GetDataRef();
        }

        GByte *pabyOut = poGDS->pabyBlockBuf + iBand*nWordBytes;

        GDALCopyWords(pabyThisImage, eDataType, nWordBytes,
                      pabyOut, eDataType, nWordBytes * nBands,
                      nBlockXSize * nBlockYSize);

        if( poBlock != NULL )
        {
            poBlock->MarkClean();
            poBlock->DropLock();
        }
    }

    poGDS->bLoadedBlockDirty = TRUE;

    return CE_None;
}

/************************************************************************/
/*                           SetDescription()                           */
/************************************************************************/

void GTiffRasterBand::SetDescription( const char *pszDescription )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( pszDescription == NULL )
        pszDescription = "";

    osDescription = pszDescription;
}

/************************************************************************/
/*                           GetDescription()                           */
/************************************************************************/

const char *GTiffRasterBand::GetDescription() const
{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    return osDescription;
}

/************************************************************************/
/*                             GetOffset()                              */
/************************************************************************/

double GTiffRasterBand::GetOffset( int *pbSuccess )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( pbSuccess )
        *pbSuccess = bHaveOffsetScale;
    return dfOffset;
}

/************************************************************************/
/*                             SetOffset()                              */
/************************************************************************/

CPLErr GTiffRasterBand::SetOffset( double dfNewValue )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( !bHaveOffsetScale || dfNewValue != dfOffset )
        poGDS->bMetadataChanged = TRUE;

    bHaveOffsetScale = TRUE;
    dfOffset = dfNewValue;
    return CE_None;
}

/************************************************************************/
/*                              GetScale()                              */
/************************************************************************/

double GTiffRasterBand::GetScale( int *pbSuccess )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( pbSuccess )
        *pbSuccess = bHaveOffsetScale;
    return dfScale;
}

/************************************************************************/
/*                              SetScale()                              */
/************************************************************************/

CPLErr GTiffRasterBand::SetScale( double dfNewValue )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( !bHaveOffsetScale || dfNewValue != dfScale )
        poGDS->bMetadataChanged = TRUE;

    bHaveOffsetScale = TRUE;
    dfScale = dfNewValue;
    return CE_None;
}

/************************************************************************/
/*                            GetUnitType()                             */
/************************************************************************/

const char* GTiffRasterBand::GetUnitType()

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    return osUnitType.c_str();
}

/************************************************************************/
/*                           SetUnitType()                              */
/************************************************************************/

CPLErr GTiffRasterBand::SetUnitType( const char* pszNewValue )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    CPLString osNewValue(pszNewValue ? pszNewValue : "");
    if( osNewValue.compare(osUnitType) != 0 )
        poGDS->bMetadataChanged = TRUE;

    osUnitType = osNewValue;
    return CE_None;
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **GTiffRasterBand::GetMetadataDomainList()
{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    return CSLDuplicate(oGTiffMDMD.GetDomainList());
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char **GTiffRasterBand::GetMetadata( const char * pszDomain )

{
    if( pszDomain == NULL || !EQUAL(pszDomain, "IMAGE_STRUCTURE") )
    {
        poGDS->LoadGeoreferencingAndPamIfNeeded();
    }

    return oGTiffMDMD.GetMetadata( pszDomain );
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/

CPLErr GTiffRasterBand::SetMetadata( char ** papszMD, const char *pszDomain )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( poGDS->bStreamingOut && poGDS->bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify metadata at that point in a streamed output file");
        return CE_Failure;
    }

    if( pszDomain == NULL || !EQUAL(pszDomain,"_temporary_") )
    {
        if( papszMD != NULL || GetMetadata(pszDomain) != NULL )
        {
            poGDS->bMetadataChanged = TRUE;
            // Cancel any existing metadata from PAM file
            if( eAccess == GA_Update &&
                GDALPamRasterBand::GetMetadata(pszDomain) != NULL )
                GDALPamRasterBand::SetMetadata(NULL, pszDomain);
        }
    }

    return oGTiffMDMD.SetMetadata( papszMD, pszDomain );
}

/************************************************************************/
/*                          GetMetadataItem()                           */
/************************************************************************/

const char *GTiffRasterBand::GetMetadataItem( const char * pszName,
                                              const char * pszDomain )

{
    if( pszDomain == NULL || !EQUAL(pszDomain, "IMAGE_STRUCTURE") )
    {
        poGDS->LoadGeoreferencingAndPamIfNeeded();
    }

    if( pszName != NULL && pszDomain != NULL && EQUAL(pszDomain, "TIFF") )
    {
        int nBlockXOff, nBlockYOff;

        if( EQUAL(pszName, "JPEGTABLES") )
        {
            if( !poGDS->SetDirectory() )
                return NULL;

            uint32 nJPEGTableSize = 0;
            void* pJPEGTable = NULL;
            if( TIFFGetField(poGDS->hTIFF, TIFFTAG_JPEGTABLES, &nJPEGTableSize, &pJPEGTable) != 1 ||
                pJPEGTable == NULL || (int)nJPEGTableSize <= 0 )
            {
                return NULL;
            }
            char* pszHex = CPLBinaryToHex( nJPEGTableSize, (const GByte*)pJPEGTable );
            const char* pszReturn = CPLSPrintf("%s", pszHex);
            CPLFree(pszHex);
            return pszReturn;
        }
        else if( sscanf(pszName, "BLOCK_OFFSET_%d_%d", &nBlockXOff, &nBlockYOff) == 2 )
        {
            if( !poGDS->SetDirectory() )
                return NULL;

            nBlocksPerRow = DIV_ROUND_UP(poGDS->nRasterXSize, poGDS->nBlockXSize);
            nBlocksPerColumn = DIV_ROUND_UP(poGDS->nRasterYSize, poGDS->nBlockYSize);
            if( nBlockXOff < 0 || nBlockXOff >= nBlocksPerRow ||
                nBlockYOff < 0 || nBlockYOff >= nBlocksPerColumn )
                return NULL;

            int nBlockId = nBlockYOff * nBlocksPerRow + nBlockXOff;
            if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
            {
                nBlockId += (nBand-1) * poGDS->nBlocksPerBand;
            }

            if( !poGDS->IsBlockAvailable(nBlockId) )
            {
                return NULL;
            }

            toff_t *panOffsets = NULL;
            TIFF* hTIFF = poGDS->hTIFF;
            if( (( TIFFIsTiled( hTIFF )
                && TIFFGetField( hTIFF, TIFFTAG_TILEOFFSETS, &panOffsets ) )
                || ( !TIFFIsTiled( hTIFF )
                && TIFFGetField( hTIFF, TIFFTAG_STRIPOFFSETS, &panOffsets ) )) &&
                panOffsets != NULL )
            {
                return CPLSPrintf(CPL_FRMT_GUIB, (GUIntBig)panOffsets[nBlockId]);
            }
            else
            {
                return NULL;
            }
        }
        else if( sscanf(pszName, "BLOCK_SIZE_%d_%d", &nBlockXOff, &nBlockYOff) == 2 )
        {
            if( !poGDS->SetDirectory() )
                return NULL;

            nBlocksPerRow = DIV_ROUND_UP(poGDS->nRasterXSize, poGDS->nBlockXSize);
            nBlocksPerColumn = DIV_ROUND_UP(poGDS->nRasterYSize, poGDS->nBlockYSize);
            if( nBlockXOff < 0 || nBlockXOff >= nBlocksPerRow ||
                nBlockYOff < 0 || nBlockYOff >= nBlocksPerColumn )
                return NULL;

            int nBlockId = nBlockYOff * nBlocksPerRow + nBlockXOff;
            if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
            {
                nBlockId += (nBand-1) * poGDS->nBlocksPerBand;
            }

            if( !poGDS->IsBlockAvailable(nBlockId) )
            {
                return NULL;
            }

            toff_t *panByteCounts = NULL;
            TIFF* hTIFF = poGDS->hTIFF;
            if( (( TIFFIsTiled( hTIFF )
                && TIFFGetField( hTIFF, TIFFTAG_TILEBYTECOUNTS, &panByteCounts ) )
                || ( !TIFFIsTiled( hTIFF )
                && TIFFGetField( hTIFF, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts ) )) &&
                panByteCounts != NULL )
            {
                return CPLSPrintf(CPL_FRMT_GUIB, (GUIntBig)panByteCounts[nBlockId]);
            }
            else
            {
                return NULL;
            }
        }
    }
    return oGTiffMDMD.GetMetadataItem( pszName, pszDomain );
}

/************************************************************************/
/*                          SetMetadataItem()                           */
/************************************************************************/

CPLErr GTiffRasterBand::SetMetadataItem( const char *pszName,
                                         const char *pszValue,
                                         const char *pszDomain )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( poGDS->bStreamingOut && poGDS->bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify metadata at that point in a streamed output file");
        return CE_Failure;
    }

    if( pszDomain == NULL || !EQUAL(pszDomain,"_temporary_") )
    {
        poGDS->bMetadataChanged = TRUE;
        // Cancel any existing metadata from PAM file
        if( eAccess == GA_Update &&
            GDALPamRasterBand::GetMetadataItem(pszName, pszDomain) != NULL )
            GDALPamRasterBand::SetMetadataItem(pszName, NULL, pszDomain);
    }

    return oGTiffMDMD.SetMetadataItem( pszName, pszValue, pszDomain );
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp GTiffRasterBand::GetColorInterpretation()

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    return eBandInterp;
}

/************************************************************************/
/*                         GTiffGetAlphaValue()                         */
/************************************************************************/

 /* Note: was EXTRASAMPLE_ASSOCALPHA in GDAL < 1.10 */
#define DEFAULT_ALPHA_TYPE              EXTRASAMPLE_UNASSALPHA

static uint16 GTiffGetAlphaValue(const char* pszValue, uint16 nDefault)
{
    if (pszValue == NULL)
        return nDefault;
    else if (EQUAL(pszValue, "YES"))
        return DEFAULT_ALPHA_TYPE;
    else if (EQUAL(pszValue, "PREMULTIPLIED"))
        return EXTRASAMPLE_ASSOCALPHA;
    else if (EQUAL(pszValue, "NON-PREMULTIPLIED"))
        return EXTRASAMPLE_UNASSALPHA;
    else if (EQUAL(pszValue, "NO") ||
             EQUAL(pszValue, "UNSPECIFIED"))
        return EXTRASAMPLE_UNSPECIFIED;
    else
        return nDefault;
}

/************************************************************************/
/*                       SetColorInterpretation()                       */
/************************************************************************/

CPLErr GTiffRasterBand::SetColorInterpretation( GDALColorInterp eInterp )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( eInterp == eBandInterp )
        return CE_None;

    eBandInterp = eInterp;

    if( poGDS->bCrystalized )
    {
        CPLDebug("GTIFF", "ColorInterpretation %s for band %d goes to PAM instead of TIFF tag",
                 GDALGetColorInterpretationName(eInterp), nBand);
        return GDALPamRasterBand::SetColorInterpretation( eInterp );
    }

    /* greyscale + alpha */
    if( eInterp == GCI_AlphaBand
        && nBand == 2
        && poGDS->nSamplesPerPixel == 2
        && poGDS->nPhotometric == PHOTOMETRIC_MINISBLACK )
    {
        uint16 v[1];
        v[0] = GTiffGetAlphaValue(CPLGetConfigOption("GTIFF_ALPHA", NULL),
                                  DEFAULT_ALPHA_TYPE);

        TIFFSetField(poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, 1, v);
        return CE_None;
    }

    /* Try to autoset TIFFTAG_PHOTOMETRIC = PHOTOMETRIC_RGB if possible */
    if( poGDS->nBands >= 3 &&
        poGDS->nCompression != COMPRESSION_JPEG &&
        poGDS->nPhotometric != PHOTOMETRIC_RGB &&
        CSLFetchNameValue( poGDS->papszCreationOptions, "PHOTOMETRIC") == NULL &&
        ((nBand == 1 && eInterp == GCI_RedBand) ||
         (nBand == 2 && eInterp == GCI_GreenBand) ||
         (nBand == 3 && eInterp == GCI_BlueBand)) )
    {
        if( poGDS->GetRasterBand(1)->GetColorInterpretation() == GCI_RedBand &&
            poGDS->GetRasterBand(2)->GetColorInterpretation() == GCI_GreenBand &&
            poGDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand )
        {
            poGDS->nPhotometric = PHOTOMETRIC_RGB;
            TIFFSetField(poGDS->hTIFF, TIFFTAG_PHOTOMETRIC, poGDS->nPhotometric);

            /* We need to update the number of extra samples */
            uint16 *v;
            uint16 count = 0;
            uint16 nNewExtraSamplesCount = static_cast<uint16>(poGDS->nBands - 3);
            if( poGDS->nBands >= 4 &&
                TIFFGetField( poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v ) &&
                count > nNewExtraSamplesCount )
            {
                uint16* pasNewExtraSamples =
                    (uint16*)CPLMalloc( nNewExtraSamplesCount * sizeof(uint16) );
                memcpy( pasNewExtraSamples, v + count - nNewExtraSamplesCount,
                        nNewExtraSamplesCount * sizeof(uint16) );

                TIFFSetField(poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, nNewExtraSamplesCount, pasNewExtraSamples);

                CPLFree(pasNewExtraSamples);
            }
        }
        poGDS->bCheckIfColorInterpMustGoToPamAtCrystalization = true;
        return CE_None;
    }

    // On the contrary, cancel the above if needed
    if( poGDS->nCompression != COMPRESSION_JPEG &&
        poGDS->nPhotometric == PHOTOMETRIC_RGB &&
        CSLFetchNameValue( poGDS->papszCreationOptions, "PHOTOMETRIC") == NULL &&
        ((nBand == 1 && eInterp != GCI_RedBand) ||
         (nBand == 2 && eInterp != GCI_GreenBand) ||
         (nBand == 3 && eInterp != GCI_BlueBand)) )
    {
        poGDS->nPhotometric = PHOTOMETRIC_MINISBLACK;
        TIFFSetField(poGDS->hTIFF, TIFFTAG_PHOTOMETRIC, poGDS->nPhotometric);

        /* We need to update the number of extra samples */
        uint16 *v;
        uint16 count = 0;
        uint16 nNewExtraSamplesCount = static_cast<uint16>(poGDS->nBands - 1);
        if( poGDS->nBands >= 2 )
        {
            TIFFGetField( poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v );
            if( nNewExtraSamplesCount > count )
            {
                uint16* pasNewExtraSamples =
                    (uint16*)CPLMalloc( nNewExtraSamplesCount * sizeof(uint16) );
                for(int i=0; i < (int)(nNewExtraSamplesCount - count); i++)
                    pasNewExtraSamples[i] = EXTRASAMPLE_UNSPECIFIED;
                if (count > 0 )
                {
                    memcpy( pasNewExtraSamples + nNewExtraSamplesCount - count, v,
                            count * sizeof(uint16) );
                }

                TIFFSetField(poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, nNewExtraSamplesCount, pasNewExtraSamples);

                CPLFree(pasNewExtraSamples);
            }
        }

        poGDS->bCheckIfColorInterpMustGoToPamAtCrystalization = true;
    }

    /* Mark alpha band in extrasamples */
    if( eInterp == GCI_AlphaBand )
    {
        uint16 *v;
        uint16 count = 0;
        if( TIFFGetField( poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v ) )
        {
            int nBaseSamples = poGDS->nSamplesPerPixel - count;

            for(int i=1;i<=poGDS->nBands;i++)
            {
                if( i != nBand &&
                    poGDS->GetRasterBand(i)->GetColorInterpretation()  == GCI_AlphaBand )
                {
                    if( i == nBaseSamples + 1 &&
                        CSLFetchNameValue(poGDS->papszCreationOptions, "ALPHA") != NULL )
                    {
                        CPLError(CE_Warning, CPLE_AppDefined,
                                "Band %d was already identified as alpha band, "
                                "and band %d is now marked as alpha too. "
                                "Presumably ALPHA creation option is not needed",
                                i, nBand);
                    }
                    else
                    {
                        CPLError(CE_Warning, CPLE_AppDefined,
                                "Band %d was already identified as alpha band, "
                                "and band %d is now marked as alpha too",
                                i, nBand);
                    }
                }
            }

            if( nBand > nBaseSamples && nBand - nBaseSamples - 1 < count )
            {
                // We need to allocate a new array as (current) libtiff
                // versions will not like that we reuse the array we got from
                // TIFFGetField().

                uint16* pasNewExtraSamples =
                    (uint16*)CPLMalloc( count * sizeof(uint16) );
                memcpy( pasNewExtraSamples, v, count * sizeof(uint16) );
                pasNewExtraSamples[nBand - nBaseSamples - 1] =
                    GTiffGetAlphaValue(CPLGetConfigOption("GTIFF_ALPHA", NULL),
                                            DEFAULT_ALPHA_TYPE);

                TIFFSetField(poGDS->hTIFF, TIFFTAG_EXTRASAMPLES, count, pasNewExtraSamples);

                CPLFree(pasNewExtraSamples);

                return CE_None;
            }
        }
    }

    if( poGDS->nPhotometric != PHOTOMETRIC_MINISBLACK &&
        CSLFetchNameValue( poGDS->papszCreationOptions, "PHOTOMETRIC") == NULL )
    {
        poGDS->nPhotometric = PHOTOMETRIC_MINISBLACK;
        TIFFSetField(poGDS->hTIFF, TIFFTAG_PHOTOMETRIC, poGDS->nPhotometric);
    }

    poGDS->bCheckIfColorInterpMustGoToPamAtCrystalization = true;
    return CE_None;
}

/************************************************************************/
/*                           GetColorTable()                            */
/************************************************************************/

GDALColorTable *GTiffRasterBand::GetColorTable()

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( nBand == 1 )
        return poGDS->poColorTable;
    else
        return NULL;
}

/************************************************************************/
/*                           SetColorTable()                            */
/************************************************************************/

CPLErr GTiffRasterBand::SetColorTable( GDALColorTable * poCT )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

/* -------------------------------------------------------------------- */
/*      Check if this is even a candidate for applying a PCT.           */
/* -------------------------------------------------------------------- */
    if( nBand != 1)
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "SetColorTable() can only be called on band 1." );
        return CE_Failure;
    }

    if( poGDS->nSamplesPerPixel != 1 && poGDS->nSamplesPerPixel != 2)
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "SetColorTable() not supported for multi-sample TIFF files." );
        return CE_Failure;
    }

    if( eDataType != GDT_Byte && eDataType != GDT_UInt16 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "SetColorTable() only supported for Byte or UInt16 bands "
                  "in TIFF format." );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      We are careful about calling SetDirectory() to avoid            */
/*      prematurely crystallizing the directory.  (#2820)               */
/* -------------------------------------------------------------------- */
    if( poGDS->bCrystalized )
    {
        if (!poGDS->SetDirectory())
            return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Is this really a request to clear the color table?              */
/* -------------------------------------------------------------------- */
    if( poCT == NULL || poCT->GetColorEntryCount() == 0 )
    {
        TIFFSetField( poGDS->hTIFF, TIFFTAG_PHOTOMETRIC,
                      PHOTOMETRIC_MINISBLACK );

#ifdef HAVE_UNSETFIELD
        TIFFUnsetField( poGDS->hTIFF, TIFFTAG_COLORMAP );
#else
        CPLDebug( "GTiff",
                  "TIFFUnsetField() not supported, colormap may not be cleared." );
#endif

        if( poGDS->poColorTable )
        {
            delete poGDS->poColorTable;
            poGDS->poColorTable = NULL;
        }

        return CE_None;
    }

/* -------------------------------------------------------------------- */
/*      Write out the colortable, and update the configuration.         */
/* -------------------------------------------------------------------- */
    int nColors;

    if( eDataType == GDT_Byte )
        nColors = 256;
    else
        nColors = 65536;

    unsigned short *panTRed, *panTGreen, *panTBlue;

    panTRed = (unsigned short *) CPLMalloc(sizeof(unsigned short)*nColors);
    panTGreen = (unsigned short *) CPLMalloc(sizeof(unsigned short)*nColors);
    panTBlue = (unsigned short *) CPLMalloc(sizeof(unsigned short)*nColors);

    for( int iColor = 0; iColor < nColors; iColor++ )
    {
        if( iColor < poCT->GetColorEntryCount() )
        {
            GDALColorEntry  sRGB;

            poCT->GetColorEntryAsRGB( iColor, &sRGB );

            panTRed[iColor] = (unsigned short) (257 * sRGB.c1);
            panTGreen[iColor] = (unsigned short) (257 * sRGB.c2);
            panTBlue[iColor] = (unsigned short) (257 * sRGB.c3);
        }
        else
        {
            panTRed[iColor] = panTGreen[iColor] = panTBlue[iColor] = 0;
        }
    }

    TIFFSetField( poGDS->hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE );
    TIFFSetField( poGDS->hTIFF, TIFFTAG_COLORMAP,
                  panTRed, panTGreen, panTBlue );

    CPLFree( panTRed );
    CPLFree( panTGreen );
    CPLFree( panTBlue );

    if( poGDS->poColorTable )
        delete poGDS->poColorTable;

    /* libtiff 3.X needs setting this in all cases (creation or update) */
    /* whereas libtiff 4.X would just need it if there */
    /* was no color table before */
#if 0
    else
#endif
        poGDS->bNeedsRewrite = TRUE;

    poGDS->poColorTable = poCT->Clone();
    eBandInterp = GCI_PaletteIndex;

    return CE_None;
}

/************************************************************************/
/*                           GetNoDataValue()                           */
/************************************************************************/

double GTiffRasterBand::GetNoDataValue( int * pbSuccess )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( bNoDataSet )
    {
        if( pbSuccess )
            *pbSuccess = TRUE;

        return dfNoDataValue;
    }

    if( poGDS->bNoDataSet )
    {
        if( pbSuccess )
            *pbSuccess = TRUE;

        return poGDS->dfNoDataValue;
    }

    return GDALPamRasterBand::GetNoDataValue( pbSuccess );
}

/************************************************************************/
/*                           SetNoDataValue()                           */
/************************************************************************/

CPLErr GTiffRasterBand::SetNoDataValue( double dfNoData )

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( poGDS->bNoDataSet && poGDS->dfNoDataValue == dfNoData )
        return CE_None;
    if( poGDS->bStreamingOut && poGDS->bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify nodata at that point in a streamed output file");
        return CE_Failure;
    }

    poGDS->bNoDataSet = TRUE;
    poGDS->dfNoDataValue = dfNoData;

    poGDS->bNoDataChanged = TRUE;

    bNoDataSet = TRUE;
    dfNoDataValue = dfNoData;
    return CE_None;
}

/************************************************************************/
/*                        DeleteNoDataValue()                           */
/************************************************************************/

CPLErr GTiffRasterBand::DeleteNoDataValue()

{
    poGDS->LoadGeoreferencingAndPamIfNeeded();

    if( !poGDS->bNoDataSet )
        return CE_None;
    if( poGDS->bStreamingOut && poGDS->bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify nodata at that point in a streamed output file");
        return CE_Failure;
    }

    poGDS->bNoDataSet = FALSE;
    poGDS->dfNoDataValue = -9999.0;

    poGDS->bNoDataChanged = TRUE;

    bNoDataSet = FALSE;
    dfNoDataValue = -9999.0;;
    return CE_None;
}

/************************************************************************/
/*                             NullBlock()                              */
/*                                                                      */
/*      Set the block data to the null value if it is set, or zero      */
/*      if there is no null data value.                                 */
/************************************************************************/

void GTiffRasterBand::NullBlock( void *pData )

{
    int nWords = nBlockXSize * nBlockYSize;
    int nChunkSize = MAX(1,GDALGetDataTypeSize(eDataType)/8);

    int bNoDataSetIn;
    double dfNoData = GetNoDataValue( &bNoDataSetIn );
    if( !bNoDataSetIn )
    {
#ifdef ESRI_BUILD
        if ( poGDS->nBitsPerSample >= 2 )
            memset( pData, 0, nWords*nChunkSize );
        else
            memset( pData, 1, nWords*nChunkSize );
#else
        memset( pData, 0, nWords*nChunkSize );
#endif
    }
    else
    {
        /* Will convert nodata value to the right type and copy efficiently */
        GDALCopyWords( &dfNoData, GDT_Float64, 0,
                       pData, eDataType, nChunkSize, nWords);
    }
}

/************************************************************************/
/*                          GetOverviewCount()                          */
/************************************************************************/

int GTiffRasterBand::GetOverviewCount()

{
    poGDS->ScanDirectories();

    if( poGDS->nOverviewCount > 0 )
    {
        return poGDS->nOverviewCount;
    }
    else
    {
        int nOverviewCount = GDALRasterBand::GetOverviewCount();
        if( nOverviewCount > 0 )
            return nOverviewCount;

        /* Implicit JPEG overviews are normally hidden, except when doing */
        /* IRasterIO() operations. */
        if( poGDS->nJPEGOverviewVisibilityFlag )
            return poGDS->GetJPEGOverviewCount();
        else
            return 0;
    }
}

/************************************************************************/
/*                            GetOverview()                             */
/************************************************************************/

GDALRasterBand *GTiffRasterBand::GetOverview( int i )

{
    poGDS->ScanDirectories();

    if( poGDS->nOverviewCount > 0 )
    {
        /* Do we have internal overviews ? */
        if( i < 0 || i >= poGDS->nOverviewCount )
            return NULL;
        else
            return poGDS->papoOverviewDS[i]->GetRasterBand(nBand);
    }
    else
    {
        GDALRasterBand* poOvrBand = GDALRasterBand::GetOverview( i );
        if( poOvrBand != NULL )
            return poOvrBand;

        /* For consistency with GetOverviewCount(), we should also test */
        /* nJPEGOverviewVisibilityFlag, but it is also convenient to be able */
        /* to query them for testing purposes. */
        if( i >= 0 && i < poGDS->GetJPEGOverviewCount() )
            return poGDS->papoJPEGOverviewDS[i]->GetRasterBand(nBand);
        else
            return NULL;
    }
}

/************************************************************************/
/*                           GetMaskFlags()                             */
/************************************************************************/

int GTiffRasterBand::GetMaskFlags()
{
    poGDS->ScanDirectories();

    if( poGDS->poMaskDS != NULL )
    {
        if( poGDS->poMaskDS->GetRasterCount() == 1)
        {
            return GMF_PER_DATASET;
        }
        else
        {
            return 0;
        }
    }
    else
        return GDALPamRasterBand::GetMaskFlags();
}

/************************************************************************/
/*                            GetMaskBand()                             */
/************************************************************************/

GDALRasterBand *GTiffRasterBand::GetMaskBand()
{
    poGDS->ScanDirectories();

    if( poGDS->poMaskDS != NULL )
    {
        if( poGDS->poMaskDS->GetRasterCount() == 1)
            return poGDS->poMaskDS->GetRasterBand(1);
        else
            return poGDS->poMaskDS->GetRasterBand(nBand);
    }
    else
        return GDALPamRasterBand::GetMaskBand();
}

/************************************************************************/
/* ==================================================================== */
/*                             GTiffSplitBand                            */
/* ==================================================================== */
/************************************************************************/

class GTiffSplitBand CPL_FINAL : public GTiffRasterBand
{
    friend class GTiffDataset;

  public:

                   GTiffSplitBand( GTiffDataset *, int );
    virtual       ~GTiffSplitBand();

    virtual CPLErr IReadBlock( int, int, void * );
    virtual CPLErr IWriteBlock( int, int, void * );
};

/************************************************************************/
/*                           GTiffSplitBand()                           */
/************************************************************************/

GTiffSplitBand::GTiffSplitBand( GTiffDataset *poDSIn, int nBandIn )
        : GTiffRasterBand( poDSIn, nBandIn )

{
    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
}

/************************************************************************/
/*                          ~GTiffSplitBand()                          */
/************************************************************************/

GTiffSplitBand::~GTiffSplitBand()
{
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr GTiffSplitBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                   void * pImage )

{
    (void) nBlockXOff;

    /* Optimization when reading the same line in a contig multi-band TIFF */
    if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG && poGDS->nBands > 1 &&
        poGDS->nLastLineRead == nBlockYOff )
    {
        goto extract_band_data;
    }

    if (!poGDS->SetDirectory())
        return CE_Failure;

    if (poGDS->nPlanarConfig == PLANARCONFIG_CONTIG &&
        poGDS->nBands > 1)
    {
        if (poGDS->pabyBlockBuf == NULL)
        {
            poGDS->pabyBlockBuf = (GByte *) VSI_MALLOC_VERBOSE(TIFFScanlineSize(poGDS->hTIFF));
            if( poGDS->pabyBlockBuf == NULL )
            {
                return CE_Failure;
            }
        }
    }
    else
    {
        CPLAssert(TIFFScanlineSize(poGDS->hTIFF) == nBlockXSize);
    }

/* -------------------------------------------------------------------- */
/*      Read through to target scanline.                                */
/* -------------------------------------------------------------------- */
    if( poGDS->nLastLineRead >= nBlockYOff )
        poGDS->nLastLineRead = -1;

    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE && poGDS->nBands > 1 )
    {
        /* If we change of band, we must start reading the */
        /* new strip from its beginning */
        if ( poGDS->nLastBandRead != nBand )
            poGDS->nLastLineRead = -1;
        poGDS->nLastBandRead = nBand;
    }

    while( poGDS->nLastLineRead < nBlockYOff )
    {
        ++poGDS->nLastLineRead;
        if( TIFFReadScanline( poGDS->hTIFF,
                              poGDS->pabyBlockBuf ? poGDS->pabyBlockBuf : pImage,
                              poGDS->nLastLineRead,
                              (poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE) ? (uint16) (nBand-1) : 0 ) == -1
            && !poGDS->bIgnoreReadErrors )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "TIFFReadScanline() failed." );
            poGDS->nLastLineRead = -1;
            return CE_Failure;
        }
    }

extract_band_data:
/* -------------------------------------------------------------------- */
/*      Extract band data from contig buffer.                           */
/* -------------------------------------------------------------------- */
    if ( poGDS->pabyBlockBuf != NULL )
    {
        int iPixel, iSrcOffset= nBand - 1, iDstOffset=0;

        for( iPixel = 0; iPixel < nBlockXSize; iPixel++, iSrcOffset+=poGDS->nBands, iDstOffset++ )
        {
            ((GByte *) pImage)[iDstOffset] = poGDS->pabyBlockBuf[iSrcOffset];
        }
    }

    return CE_None;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr GTiffSplitBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
                                    void * pImage )

{
    (void) nBlockXOff;
    (void) nBlockYOff;
    (void) pImage;

    CPLError( CE_Failure, CPLE_AppDefined,
              "Split bands are read-only." );
    return CE_Failure;
}

/************************************************************************/
/* ==================================================================== */
/*                             GTiffRGBABand                            */
/* ==================================================================== */
/************************************************************************/

class GTiffRGBABand CPL_FINAL : public GTiffRasterBand
{
    friend class GTiffDataset;

  public:

                   GTiffRGBABand( GTiffDataset *, int );

    virtual CPLErr IReadBlock( int, int, void * );
    virtual CPLErr IWriteBlock( int, int, void * );

    virtual GDALColorInterp GetColorInterpretation();
};


/************************************************************************/
/*                           GTiffRGBABand()                            */
/************************************************************************/

GTiffRGBABand::GTiffRGBABand( GTiffDataset *poDSIn, int nBandIn )
        : GTiffRasterBand( poDSIn, nBandIn )

{
    eDataType = GDT_Byte;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr GTiffRGBABand::IWriteBlock( int, int, void * )

{
    CPLError( CE_Failure, CPLE_AppDefined,
              "RGBA interpreted raster bands are read-only." );
    return CE_Failure;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr GTiffRGBABand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                    void * pImage )

{
    int nBlockBufSize, nBlockId;
    CPLErr eErr = CE_None;

    if (!poGDS->SetDirectory())
        return CE_Failure;

    CPLAssert(nBlocksPerRow != 0);
    nBlockBufSize = 4 * nBlockXSize * nBlockYSize;
    nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

/* -------------------------------------------------------------------- */
/*      Allocate a temporary buffer for this strip.                     */
/* -------------------------------------------------------------------- */
    if( poGDS->pabyBlockBuf == NULL )
    {
        poGDS->pabyBlockBuf = (GByte *) VSI_MALLOC3_VERBOSE( 4, nBlockXSize, nBlockYSize );
        if( poGDS->pabyBlockBuf == NULL )
            return( CE_Failure );
    }

/* -------------------------------------------------------------------- */
/*      Read the strip                                                  */
/* -------------------------------------------------------------------- */
    if( poGDS->nLoadedBlock != nBlockId )
    {
        if( TIFFIsTiled( poGDS->hTIFF ) )
        {
            if( TIFFReadRGBATile(poGDS->hTIFF,
                                 nBlockXOff * nBlockXSize,
                                 nBlockYOff * nBlockYSize,
                                 (uint32 *) poGDS->pabyBlockBuf) == -1
                && !poGDS->bIgnoreReadErrors )
            {
                /* Once TIFFError() is properly hooked, this can go away */
                CPLError( CE_Failure, CPLE_AppDefined,
                          "TIFFReadRGBATile() failed." );

                memset( poGDS->pabyBlockBuf, 0, nBlockBufSize );

                eErr = CE_Failure;
            }
        }
        else
        {
            if( TIFFReadRGBAStrip(poGDS->hTIFF,
                                  nBlockId * nBlockYSize,
                                  (uint32 *) poGDS->pabyBlockBuf) == -1
                && !poGDS->bIgnoreReadErrors )
            {
                /* Once TIFFError() is properly hooked, this can go away */
                CPLError( CE_Failure, CPLE_AppDefined,
                          "TIFFReadRGBAStrip() failed." );

                memset( poGDS->pabyBlockBuf, 0, nBlockBufSize );

                eErr = CE_Failure;
            }
        }
    }

    poGDS->nLoadedBlock = nBlockId;

/* -------------------------------------------------------------------- */
/*      Handle simple case of eight bit data, and pixel interleaving.   */
/* -------------------------------------------------------------------- */
    int   iDestLine, nBO;
    int   nThisBlockYSize;

    if( (nBlockYOff+1) * nBlockYSize > GetYSize()
        && !TIFFIsTiled( poGDS->hTIFF ) )
        nThisBlockYSize = GetYSize() - nBlockYOff * nBlockYSize;
    else
        nThisBlockYSize = nBlockYSize;

#ifdef CPL_LSB
    nBO = nBand - 1;
#else
    nBO = 4 - nBand;
#endif

    for( iDestLine = 0; iDestLine < nThisBlockYSize; iDestLine++ )
    {
        int nSrcOffset;

        nSrcOffset = (nThisBlockYSize - iDestLine - 1) * nBlockXSize * 4;

        GDALCopyWords( poGDS->pabyBlockBuf + nBO + nSrcOffset, GDT_Byte, 4,
                       ((GByte *) pImage)+iDestLine*nBlockXSize, GDT_Byte, 1,
                       nBlockXSize );
    }

    if (eErr == CE_None)
        eErr = FillCacheForOtherBands(nBlockXOff, nBlockYOff);

    return eErr;
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp GTiffRGBABand::GetColorInterpretation()

{
    if( nBand == 1 )
        return GCI_RedBand;
    else if( nBand == 2 )
        return GCI_GreenBand;
    else if( nBand == 3 )
        return GCI_BlueBand;
    else
        return GCI_AlphaBand;
}

/************************************************************************/
/* ==================================================================== */
/*                             GTiffOddBitsBand                         */
/* ==================================================================== */
/************************************************************************/

class GTiffOddBitsBand : public GTiffRasterBand
{
    friend class GTiffDataset;
  public:

                   GTiffOddBitsBand( GTiffDataset *, int );
    virtual       ~GTiffOddBitsBand();

    virtual CPLErr IReadBlock( int, int, void * );
    virtual CPLErr IWriteBlock( int, int, void * );
};


/************************************************************************/
/*                           GTiffOddBitsBand()                         */
/************************************************************************/

GTiffOddBitsBand::GTiffOddBitsBand( GTiffDataset *poGDSIn, int nBandIn )
        : GTiffRasterBand( poGDSIn, nBandIn )

{
    eDataType = GDT_Byte;
    if( poGDS->nSampleFormat == SAMPLEFORMAT_IEEEFP )
        eDataType = GDT_Float32;
    else if( poGDS->nBitsPerSample > 8 && poGDS->nBitsPerSample < 16 )
        eDataType = GDT_UInt16;
    else if( poGDS->nBitsPerSample > 16 )
        eDataType = GDT_UInt32;
}

/************************************************************************/
/*                          ~GTiffOddBitsBand()                          */
/************************************************************************/

GTiffOddBitsBand::~GTiffOddBitsBand()

{
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr GTiffOddBitsBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
                                      void *pImage )

{
    int nBlockId;
    CPLErr      eErr = CE_None;

    if (poGDS->bWriteErrorInFlushBlockBuf)
    {
        /* Report as an error if a previously loaded block couldn't be */
        /* written correctly */
        poGDS->bWriteErrorInFlushBlockBuf = FALSE;
        return CE_Failure;
    }

    if (!poGDS->SetDirectory())
        return CE_Failure;

    CPLAssert( poGDS != NULL
               && nBlockXOff >= 0
               && nBlockYOff >= 0
               && pImage != NULL );

    if( eDataType == GDT_Float32 )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Writing float data with nBitsPerSample < 32 is unsupported");
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Load the block buffer.                                          */
/* -------------------------------------------------------------------- */
    CPLAssert(nBlocksPerRow != 0);
    nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
        nBlockId += (nBand-1) * poGDS->nBlocksPerBand;

    /* Only read content from disk in the CONTIG case */
    eErr = poGDS->LoadBlockBuf( nBlockId,
                                poGDS->nPlanarConfig == PLANARCONFIG_CONTIG && poGDS->nBands > 1 );
    if( eErr != CE_None )
        return eErr;

    GUInt32 nMaxVal = (1 << poGDS->nBitsPerSample) - 1;

/* -------------------------------------------------------------------- */
/*      Handle case of "separate" images or single band images where    */
/*      no interleaving with other data is required.                    */
/* -------------------------------------------------------------------- */
    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE
        || poGDS->nBands == 1 )
    {
        int iBit, iPixel, iBitOffset = 0;
        int     iX, iY, nBitsPerLine;

        // bits per line rounds up to next byte boundary.
        nBitsPerLine = nBlockXSize * poGDS->nBitsPerSample;
        if( (nBitsPerLine & 7) != 0 )
            nBitsPerLine = (nBitsPerLine + 7) & (~7);

        /* Initialize to zero as we set the buffer with binary or operations */
        if (poGDS->nBitsPerSample != 24)
            memset(poGDS->pabyBlockBuf, 0, (nBitsPerLine / 8) * nBlockYSize);

        iPixel = 0;
        for( iY = 0; iY < nBlockYSize; iY++ )
        {
            iBitOffset = iY * nBitsPerLine;

            /* Small optimization in 1 bit case */
            if (poGDS->nBitsPerSample == 1)
            {
                for( iX = 0; iX < nBlockXSize; iX++ )
                {
                    if (((GByte *) pImage)[iPixel++])
                        poGDS->pabyBlockBuf[iBitOffset>>3] |= (0x80 >>(iBitOffset & 7));
                    iBitOffset++;
                }

                continue;
            }

            if (poGDS->nBitsPerSample == 12 )
            {
                for( iX = 0; iX < nBlockXSize; iX++ )
                {
                    GUInt32 nInWord = ((GUInt16 *) pImage)[iPixel++];
                    if (nInWord > nMaxVal)
                    {
                        nInWord = nMaxVal;
                        if( !poGDS->bClipWarn )
                        {
                            poGDS->bClipWarn = TRUE;
                            CPLError( CE_Warning, CPLE_AppDefined,
                                      "One or more pixels clipped to fit %d bit domain.", poGDS->nBitsPerSample );
                        }
                    }

                    if( (iBitOffset % 8) == 0 )
                    {
                        poGDS->pabyBlockBuf[iBitOffset>>3] = (GByte) (nInWord >> 4);
                        /* let 4 lower bits to zero as they're going to be overridden by the next word */
                        poGDS->pabyBlockBuf[(iBitOffset>>3)+1] = (GByte) ((nInWord & 0xf) << 4);
                    }
                    else
                    {
                        /* we must or to preserve the 4 upper bits written for the previous word */
                        poGDS->pabyBlockBuf[iBitOffset>>3] |= (GByte) (nInWord >> 8);
                        poGDS->pabyBlockBuf[(iBitOffset>>3)+1] = (GByte) (nInWord & 0xff);
                    }

                    iBitOffset += poGDS->nBitsPerSample;
                }
                continue;
            }

            for( iX = 0; iX < nBlockXSize; iX++ )
            {
                GUInt32  nInWord = 0;
                if( eDataType == GDT_Byte )
                    nInWord = ((GByte *) pImage)[iPixel++];
                else if( eDataType == GDT_UInt16 )
                    nInWord = ((GUInt16 *) pImage)[iPixel++];
                else if( eDataType == GDT_UInt32 )
                    nInWord = ((GUInt32 *) pImage)[iPixel++];
                else {
                    CPLAssert(0);
                }

                if (nInWord > nMaxVal)
                {
                    nInWord = nMaxVal;
                    if( !poGDS->bClipWarn )
                    {
                        poGDS->bClipWarn = TRUE;
                        CPLError( CE_Warning, CPLE_AppDefined,
                                  "One or more pixels clipped to fit %d bit domain.", poGDS->nBitsPerSample );
                    }
                }

                if (poGDS->nBitsPerSample == 24)
                {
/* -------------------------------------------------------------------- */
/*      Special case for 24bit data which is pre-byteswapped since      */
/*      the size falls on a byte boundary ... ugh (#2361).              */
/* -------------------------------------------------------------------- */
#ifdef CPL_MSB
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 0] =
                        (GByte) nInWord;
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 1] =
                        (GByte) (nInWord >> 8);
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 2] =
                        (GByte) (nInWord >> 16);
#else
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 0] =
                        (GByte) (nInWord >> 16);
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 1] =
                        (GByte) (nInWord >> 8);
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 2] =
                        (GByte) nInWord;
#endif
                    iBitOffset += 24;
                }
                else
                {
                    for( iBit = 0; iBit < poGDS->nBitsPerSample; iBit++ )
                    {
                        if (nInWord & (1 << (poGDS->nBitsPerSample - 1 - iBit)))
                            poGDS->pabyBlockBuf[iBitOffset>>3] |= (0x80 >>(iBitOffset & 7));
                        iBitOffset++;
                    }
                }
            }
        }

        poGDS->bLoadedBlockDirty = TRUE;

        return eErr;
    }

/* -------------------------------------------------------------------- */
/*      Handle case of pixel interleaved (PLANARCONFIG_CONTIG) images.  */
/* -------------------------------------------------------------------- */

/* -------------------------------------------------------------------- */
/*      On write of pixel interleaved data, we might as well flush      */
/*      out any other bands that are dirty in our cache.  This is       */
/*      especially helpful when writing compressed blocks.              */
/* -------------------------------------------------------------------- */
    int iBand;

    for( iBand = 0; iBand < poGDS->nBands; iBand++ )
    {
        const GByte *pabyThisImage = NULL;
        GDALRasterBlock *poBlock = NULL;
        int iBit, iPixel, iBitOffset = 0;
        int     iPixelBitSkip, iBandBitOffset, iX, iY, nBitsPerLine;

        if( iBand+1 == nBand )
            pabyThisImage = (GByte *) pImage;
        else
        {
            poBlock = ((GTiffOddBitsBand *)poGDS->GetRasterBand( iBand+1 ))
                ->TryGetLockedBlockRef( nBlockXOff, nBlockYOff );

            if( poBlock == NULL )
                continue;

            if( !poBlock->GetDirty() )
            {
                poBlock->DropLock();
                continue;
            }

            pabyThisImage = (GByte *) poBlock->GetDataRef();
        }

        iPixelBitSkip = poGDS->nBitsPerSample * poGDS->nBands;
        iBandBitOffset = iBand * poGDS->nBitsPerSample;

        // bits per line rounds up to next byte boundary.
        nBitsPerLine = nBlockXSize * iPixelBitSkip;
        if( (nBitsPerLine & 7) != 0 )
            nBitsPerLine = (nBitsPerLine + 7) & (~7);

        iPixel = 0;
        for( iY = 0; iY < nBlockYSize; iY++ )
        {
            iBitOffset = iBandBitOffset + iY * nBitsPerLine;

            if (poGDS->nBitsPerSample == 12 )
            {
                for( iX = 0; iX < nBlockXSize; iX++ )
                {
                    GUInt32 nInWord = ((GUInt16 *) pabyThisImage)[iPixel++];
                    if (nInWord > nMaxVal)
                    {
                        nInWord = nMaxVal;
                        if( !poGDS->bClipWarn )
                        {
                            poGDS->bClipWarn = TRUE;
                            CPLError( CE_Warning, CPLE_AppDefined,
                                      "One or more pixels clipped to fit %d bit domain.", poGDS->nBitsPerSample );
                        }
                    }

                    if( (iBitOffset % 8) == 0 )
                    {
                        poGDS->pabyBlockBuf[iBitOffset>>3] = (GByte) (nInWord >> 4);
                        poGDS->pabyBlockBuf[(iBitOffset>>3)+1] =
                            (GByte) (((nInWord & 0xf) << 4) | (poGDS->pabyBlockBuf[(iBitOffset>>3)+1] & 0xf));
                    }
                    else
                    {
                        poGDS->pabyBlockBuf[iBitOffset>>3] =
                            (GByte) ((poGDS->pabyBlockBuf[iBitOffset>>3] & 0xf0) |
                                     (nInWord >> 8));
                        poGDS->pabyBlockBuf[(iBitOffset>>3)+1] = (GByte) (nInWord & 0xff);
                    }

                    iBitOffset += iPixelBitSkip;
                }
                continue;
            }

            for( iX = 0; iX < nBlockXSize; iX++ )
            {
                GUInt32  nInWord = 0;
                if( eDataType == GDT_Byte )
                    nInWord = ((GByte *) pabyThisImage)[iPixel++];
                else if( eDataType == GDT_UInt16 )
                    nInWord = ((GUInt16 *) pabyThisImage)[iPixel++];
                else if( eDataType == GDT_UInt32 )
                    nInWord = ((GUInt32 *) pabyThisImage)[iPixel++];
                else {
                    CPLAssert(0);
                }

                if (nInWord > nMaxVal)
                {
                    nInWord = nMaxVal;
                    if( !poGDS->bClipWarn )
                    {
                        poGDS->bClipWarn = TRUE;
                        CPLError( CE_Warning, CPLE_AppDefined,
                                  "One or more pixels clipped to fit %d bit domain.", poGDS->nBitsPerSample );
                    }
                }

                if (poGDS->nBitsPerSample == 24)
                {
/* -------------------------------------------------------------------- */
/*      Special case for 24bit data which is pre-byteswapped since      */
/*      the size falls on a byte boundary ... ugh (#2361).              */
/* -------------------------------------------------------------------- */
#ifdef CPL_MSB
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 0] =
                        (GByte) nInWord;
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 1] =
                        (GByte) (nInWord >> 8);
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 2] =
                        (GByte) (nInWord >> 16);
#else
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 0] =
                        (GByte) (nInWord >> 16);
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 1] =
                        (GByte) (nInWord >> 8);
                    poGDS->pabyBlockBuf[(iBitOffset>>3) + 2] =
                        (GByte) nInWord;
#endif
                    iBitOffset += 24;
                }
                else
                {
                    for( iBit = 0; iBit < poGDS->nBitsPerSample; iBit++ )
                    {
                        if (nInWord & (1 << (poGDS->nBitsPerSample - 1 - iBit)))
                            poGDS->pabyBlockBuf[iBitOffset>>3] |= (0x80 >>(iBitOffset & 7));
                        else
                        {
                            // We must explicitly unset the bit as we
                            // may update an existing block.
                            poGDS->pabyBlockBuf[iBitOffset>>3] &= ~(0x80 >>(iBitOffset & 7));
                        }

                        iBitOffset++;
                    }
                }

                iBitOffset= iBitOffset + iPixelBitSkip - poGDS->nBitsPerSample;
            }
        }

        if( poBlock != NULL )
        {
            poBlock->MarkClean();
            poBlock->DropLock();
        }
    }

    poGDS->bLoadedBlockDirty = TRUE;

    return CE_None;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr GTiffOddBitsBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                    void * pImage )

{
    int nBlockId;
    CPLErr eErr = CE_None;

    if (!poGDS->SetDirectory())
        return CE_Failure;

    CPLAssert(nBlocksPerRow != 0);
    nBlockId = nBlockXOff + nBlockYOff * nBlocksPerRow;

    if( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
        nBlockId += (nBand-1) * poGDS->nBlocksPerBand;

/* -------------------------------------------------------------------- */
/*      Handle the case of a strip in a writable file that doesn't      */
/*      exist yet, but that we want to read.  Just set to zeros and     */
/*      return.                                                         */
/* -------------------------------------------------------------------- */
    if( nBlockId != poGDS->nLoadedBlock && !poGDS->IsBlockAvailable(nBlockId) )
    {
        NullBlock( pImage );
        return CE_None;
    }

/* -------------------------------------------------------------------- */
/*      Load the block buffer.                                          */
/* -------------------------------------------------------------------- */
    eErr = poGDS->LoadBlockBuf( nBlockId );
    if( eErr != CE_None )
        return eErr;

    if (  poGDS->nBitsPerSample == 1 && (poGDS->nBands == 1 || poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE ) )
    {
/* -------------------------------------------------------------------- */
/*      Translate 1bit data to eight bit.                               */
/* -------------------------------------------------------------------- */
        int iDstOffset=0, iLine;
        const GByte * const pabyBlockBuf = poGDS->pabyBlockBuf;

        for( iLine = 0; iLine < nBlockYSize; iLine++ )
        {
            int iSrcOffset, iPixel;

            iSrcOffset = ((nBlockXSize+7) >> 3) * 8 * iLine;

            GByte bSetVal = (poGDS->bPromoteTo8Bits) ? 255 : 1;

            for( iPixel = 0; iPixel < nBlockXSize; iPixel++, iSrcOffset++ )
            {
                if( pabyBlockBuf[iSrcOffset >>3] & (0x80 >> (iSrcOffset & 0x7)) )
                    ((GByte *) pImage)[iDstOffset++] = bSetVal;
                else
                    ((GByte *) pImage)[iDstOffset++] = 0;
            }
        }
    }
/* -------------------------------------------------------------------- */
/*      Handle the case of 16- and 24-bit floating point data as per    */
/*      TIFF Technical Note 3.                                          */
/* -------------------------------------------------------------------- */
    else if( eDataType == GDT_Float32 )
    {
        int i, nBlockPixels, nWordBytes, iSkipBytes;
        GByte *pabyImage;

        nWordBytes = poGDS->nBitsPerSample / 8;
        pabyImage = poGDS->pabyBlockBuf + (nBand - 1) * nWordBytes;
        iSkipBytes = ( poGDS->nPlanarConfig == PLANARCONFIG_SEPARATE ) ?
            nWordBytes : poGDS->nBands * nWordBytes;

        nBlockPixels = nBlockXSize * nBlockYSize;
        if ( poGDS->nBitsPerSample == 16 )
        {
            for( i = 0; i < nBlockPixels; i++ )
            {
                ((GUInt32 *) pImage)[i] =
                    HalfToFloat( *((GUInt16 *)pabyImage) );
                pabyImage += iSkipBytes;
            }
        }
        else if ( poGDS->nBitsPerSample == 24 )
        {
            for( i = 0; i < nBlockPixels; i++ )
            {
#ifdef CPL_MSB
                ((GUInt32 *) pImage)[i] =
                    TripleToFloat( ((GUInt32)*(pabyImage + 0) << 16)
                                   | ((GUInt32)*(pabyImage + 1) << 8)
                                   | (GUInt32)*(pabyImage + 2) );
#else
                ((GUInt32 *) pImage)[i] =
                    TripleToFloat( ((GUInt32)*(pabyImage + 2) << 16)
                                   | ((GUInt32)*(pabyImage + 1) << 8)
                                   | (GUInt32)*pabyImage );
#endif
                pabyImage += iSkipBytes;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Special case for moving 12bit data somewhat more efficiently.   */
/* -------------------------------------------------------------------- */
    else if( poGDS->nBitsPerSample == 12 )
    {
        int iPixel, iBitOffset = 0;
        int iPixelBitSkip, iBandBitOffset, iX, iY, nBitsPerLine;

        if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
        {
            iPixelBitSkip = poGDS->nBands * poGDS->nBitsPerSample;
            iBandBitOffset = (nBand-1) * poGDS->nBitsPerSample;
        }
        else
        {
            iPixelBitSkip = poGDS->nBitsPerSample;
            iBandBitOffset = 0;
        }

        // bits per line rounds up to next byte boundary.
        nBitsPerLine = nBlockXSize * iPixelBitSkip;
        if( (nBitsPerLine & 7) != 0 )
            nBitsPerLine = (nBitsPerLine + 7) & (~7);

        iPixel = 0;
        for( iY = 0; iY < nBlockYSize; iY++ )
        {
            iBitOffset = iBandBitOffset + iY * nBitsPerLine;

            for( iX = 0; iX < nBlockXSize; iX++ )
            {
                int iByte = iBitOffset>>3;

                if( (iBitOffset & 0x7) == 0 )
                {
                    /* starting on byte boundary */

                    ((GUInt16 *) pImage)[iPixel++] =
                        (poGDS->pabyBlockBuf[iByte] << 4)
                        | (poGDS->pabyBlockBuf[iByte+1] >> 4);
                }
                else
                {
                    /* starting off byte boundary */

                    ((GUInt16 *) pImage)[iPixel++] =
                        ((poGDS->pabyBlockBuf[iByte] & 0xf) << 8)
                        | (poGDS->pabyBlockBuf[iByte+1]);
                }
                iBitOffset += iPixelBitSkip;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Special case for 24bit data which is pre-byteswapped since      */
/*      the size falls on a byte boundary ... ugh (#2361).              */
/* -------------------------------------------------------------------- */
    else if( poGDS->nBitsPerSample == 24 )
    {
        int iPixel;
        int     iPixelByteSkip, iBandByteOffset, iX, iY, nBytesPerLine;

        if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
        {
            iPixelByteSkip = (poGDS->nBands * poGDS->nBitsPerSample) / 8;
            iBandByteOffset = ((nBand-1) * poGDS->nBitsPerSample) / 8;
        }
        else
        {
            iPixelByteSkip = poGDS->nBitsPerSample / 8;
            iBandByteOffset = 0;
        }

        nBytesPerLine = nBlockXSize * iPixelByteSkip;

        iPixel = 0;
        for( iY = 0; iY < nBlockYSize; iY++ )
        {
            GByte *pabyImage =
                poGDS->pabyBlockBuf + iBandByteOffset + iY * nBytesPerLine;

            for( iX = 0; iX < nBlockXSize; iX++ )
            {
#ifdef CPL_MSB
                ((GUInt32 *) pImage)[iPixel++] =
                    ((GUInt32)*(pabyImage + 2) << 16)
                    | ((GUInt32)*(pabyImage + 1) << 8)
                    | (GUInt32)*(pabyImage + 0);
#else
                ((GUInt32 *) pImage)[iPixel++] =
                    ((GUInt32)*(pabyImage + 0) << 16)
                    | ((GUInt32)*(pabyImage + 1) << 8)
                    | (GUInt32)*(pabyImage + 2);
#endif
                pabyImage += iPixelByteSkip;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Handle 1-32 bit integer data.                                   */
/* -------------------------------------------------------------------- */
    else
    {
        int iBit, iPixel, iBitOffset = 0;
        int     iPixelBitSkip, iBandBitOffset, iX, iY, nBitsPerLine;

        if( poGDS->nPlanarConfig == PLANARCONFIG_CONTIG )
        {
            iPixelBitSkip = poGDS->nBands * poGDS->nBitsPerSample;
            iBandBitOffset = (nBand-1) * poGDS->nBitsPerSample;
        }
        else
        {
            iPixelBitSkip = poGDS->nBitsPerSample;
            iBandBitOffset = 0;
        }

        // bits per line rounds up to next byte boundary.
        nBitsPerLine = nBlockXSize * iPixelBitSkip;
        if( (nBitsPerLine & 7) != 0 )
            nBitsPerLine = (nBitsPerLine + 7) & (~7);

        const GByte * const pabyBlockBuf = poGDS->pabyBlockBuf;
        const int nBitsPerSample = poGDS->nBitsPerSample;
        iPixel = 0;

        for( iY = 0; iY < nBlockYSize; iY++ )
        {
            iBitOffset = iBandBitOffset + iY * nBitsPerLine;

            for( iX = 0; iX < nBlockXSize; iX++ )
            {
                int  nOutWord = 0;

                for( iBit = 0; iBit < nBitsPerSample; iBit++ )
                {
                    if( pabyBlockBuf[iBitOffset>>3]
                        & (0x80 >>(iBitOffset & 7)) )
                        nOutWord |= (1 << (nBitsPerSample - 1 - iBit));
                    iBitOffset++;
                }

                iBitOffset= iBitOffset + iPixelBitSkip - nBitsPerSample;

                if( eDataType == GDT_Byte )
                    ((GByte *) pImage)[iPixel++] = (GByte) nOutWord;
                else if( eDataType == GDT_UInt16 )
                    ((GUInt16 *) pImage)[iPixel++] = (GUInt16) nOutWord;
                else if( eDataType == GDT_UInt32 )
                    ((GUInt32 *) pImage)[iPixel++] = nOutWord;
                else {
                    CPLAssert(0);
                }
            }
        }
    }

    return CE_None;
}


/************************************************************************/
/* ==================================================================== */
/*                             GTiffBitmapBand                          */
/* ==================================================================== */
/************************************************************************/

class GTiffBitmapBand : public GTiffOddBitsBand
{
    friend class GTiffDataset;

    GDALColorTable *poColorTable;

  public:

                   GTiffBitmapBand( GTiffDataset *, int );
    virtual       ~GTiffBitmapBand();

    virtual GDALColorInterp GetColorInterpretation();
    virtual GDALColorTable *GetColorTable();
};


/************************************************************************/
/*                           GTiffBitmapBand()                          */
/************************************************************************/

GTiffBitmapBand::GTiffBitmapBand( GTiffDataset *poDSIn, int nBandIn )
        : GTiffOddBitsBand( poDSIn, nBandIn )

{
    eDataType = GDT_Byte;

    if( poDSIn->poColorTable != NULL )
        poColorTable = poDSIn->poColorTable->Clone();
    else
    {
#ifdef ESRI_BUILD
        poColorTable = NULL;
#else
        GDALColorEntry oWhite, oBlack;

        oWhite.c1 = 255;
        oWhite.c2 = 255;
        oWhite.c3 = 255;
        oWhite.c4 = 255;

        oBlack.c1 = 0;
        oBlack.c2 = 0;
        oBlack.c3 = 0;
        oBlack.c4 = 255;

        poColorTable = new GDALColorTable();

        if( poDSIn->nPhotometric == PHOTOMETRIC_MINISWHITE )
        {
            poColorTable->SetColorEntry( 0, &oWhite );
            poColorTable->SetColorEntry( 1, &oBlack );
        }
        else
        {
            poColorTable->SetColorEntry( 0, &oBlack );
            poColorTable->SetColorEntry( 1, &oWhite );
        }
#endif /* not defined ESRI_BUILD */
    }
}

/************************************************************************/
/*                          ~GTiffBitmapBand()                          */
/************************************************************************/

GTiffBitmapBand::~GTiffBitmapBand()

{
    delete poColorTable;
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp GTiffBitmapBand::GetColorInterpretation()

{
    if (poGDS->bPromoteTo8Bits)
        return GCI_Undefined;
    else
        return GCI_PaletteIndex;
}

/************************************************************************/
/*                           GetColorTable()                            */
/************************************************************************/

GDALColorTable *GTiffBitmapBand::GetColorTable()

{
    if (poGDS->bPromoteTo8Bits)
        return NULL;
    else
        return poColorTable;
}

/************************************************************************/
/* ==================================================================== */
/*                          GTiffSplitBitmapBand                        */
/* ==================================================================== */
/************************************************************************/

class GTiffSplitBitmapBand CPL_FINAL : public GTiffBitmapBand
{
    friend class GTiffDataset;

  public:

                   GTiffSplitBitmapBand( GTiffDataset *, int );
    virtual       ~GTiffSplitBitmapBand();

    virtual CPLErr IReadBlock( int, int, void * );
    virtual CPLErr IWriteBlock( int, int, void * );
};


/************************************************************************/
/*                       GTiffSplitBitmapBand()                         */
/************************************************************************/

GTiffSplitBitmapBand::GTiffSplitBitmapBand( GTiffDataset *poDSIn, int nBandIn )
        : GTiffBitmapBand( poDSIn, nBandIn )

{
    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
}

/************************************************************************/
/*                      ~GTiffSplitBitmapBand()                         */
/************************************************************************/

GTiffSplitBitmapBand::~GTiffSplitBitmapBand()

{
}


/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr GTiffSplitBitmapBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                         void * pImage )

{
    (void) nBlockXOff;

    if (!poGDS->SetDirectory())
        return CE_Failure;

    if (poGDS->pabyBlockBuf == NULL)
    {
        poGDS->pabyBlockBuf = (GByte *) VSI_MALLOC_VERBOSE(TIFFScanlineSize(poGDS->hTIFF));
        if( poGDS->pabyBlockBuf == NULL )
        {
            return CE_Failure;
        }
    }

/* -------------------------------------------------------------------- */
/*      Read through to target scanline.                                */
/* -------------------------------------------------------------------- */
    if( poGDS->nLastLineRead >= nBlockYOff )
        poGDS->nLastLineRead = -1;

    while( poGDS->nLastLineRead < nBlockYOff )
    {
        ++poGDS->nLastLineRead;
        if( TIFFReadScanline( poGDS->hTIFF, poGDS->pabyBlockBuf, poGDS->nLastLineRead, 0 ) == -1
            && !poGDS->bIgnoreReadErrors )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "TIFFReadScanline() failed." );
            poGDS->nLastLineRead = -1;
            return CE_Failure;
        }
    }

/* -------------------------------------------------------------------- */
/*      Translate 1bit data to eight bit.                               */
/* -------------------------------------------------------------------- */
    int iPixel, iSrcOffset=0, iDstOffset=0;

    for( iPixel = 0; iPixel < nBlockXSize; iPixel++, iSrcOffset++ )
    {
        if( poGDS->pabyBlockBuf[iSrcOffset >>3] & (0x80 >> (iSrcOffset & 0x7)) )
            ((GByte *) pImage)[iDstOffset++] = 1;
        else
            ((GByte *) pImage)[iDstOffset++] = 0;
    }

    return CE_None;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr GTiffSplitBitmapBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
                                          void * pImage )

{
    (void) nBlockXOff;
    (void) nBlockYOff;
    (void) pImage;

    CPLError( CE_Failure, CPLE_AppDefined,
              "Split bitmap bands are read-only." );
    return CE_Failure;
}

/************************************************************************/
/* ==================================================================== */
/*                            GTiffDataset                              */
/* ==================================================================== */
/************************************************************************/


/************************************************************************/
/*                            GTiffDataset()                            */
/************************************************************************/

GTiffDataset::GTiffDataset() :
    nPlanarConfig(0), nSamplesPerPixel(0), nBitsPerSample(0), nRowsPerStrip(0),
    nPhotometric(0), nSampleFormat(0), nCompression(0), nBlocksPerBand(0),
    nBlockXSize(0), nBlockYSize(0)
{
    nLoadedBlock = -1;
    bLoadedBlockDirty = FALSE;
    pabyBlockBuf = NULL;
    bWriteErrorInFlushBlockBuf = FALSE;
    hTIFF = NULL;
    fpL = NULL;
    bStreamingIn = FALSE;
    bStreamingOut = FALSE;
    fpToWrite = NULL;
    nLastWrittenBlockId = -1;
    bNeedsRewrite = FALSE;
    bMetadataChanged = FALSE;
    bColorProfileMetadataChanged = FALSE;
    bGeoTIFFInfoChanged = FALSE;
    bForceUnsetGTOrGCPs = FALSE;
    bForceUnsetProjection = FALSE;
    bCrystalized = TRUE;
    bCheckIfColorInterpMustGoToPamAtCrystalization = false;
    poColorTable = NULL;
    bNoDataChanged = FALSE;
    bNoDataSet = FALSE;
    dfNoDataValue = -9999.0;
    pszProjection = CPLStrdup("");
    bLookedForProjection = FALSE;
    bLookedForMDAreaOrPoint = FALSE;
    bBase = TRUE;
    bCloseTIFFHandle = FALSE;
    bTreatAsRGBA = FALSE;
    nOverviewCount = 0;
    papoOverviewDS = NULL;

    nJPEGOverviewVisibilityFlag = FALSE;
    nJPEGOverviewCount = -1;
    nJPEGOverviewCountOri = 0;
    papoJPEGOverviewDS = NULL;

    nDirOffset = 0;
    poActiveDS = NULL;
    ppoActiveDSRef = NULL;

    bGeoTransformValid = FALSE;
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;

    nGCPCount = 0;
    pasGCPList = NULL;

    osProfile = "GDALGeoTIFF";

    papszCreationOptions = NULL;

    nTempWriteBufferSize = 0;
    pabyTempWriteBuffer = NULL;

    poMaskDS = NULL;
    poBaseDS = NULL;

    bFillEmptyTiles = FALSE;
    bLoadingOtherBands = FALSE;
    nLastLineRead = -1;
    nLastBandRead = -1;
    bTreatAsSplit = FALSE;
    bTreatAsSplitBitmap = FALSE;
    bClipWarn = FALSE;
    bHasWarnedDisableAggressiveBandCaching = FALSE;
    bDontReloadFirstBlock = FALSE;

    nZLevel = -1;
    nLZMAPreset = -1;
    nJpegQuality = -1;
    nJpegTablesMode = -1;

    bPromoteTo8Bits = FALSE;

    bDebugDontWriteBlocks = CPLTestBool(CPLGetConfigOption("GTIFF_DONT_WRITE_BLOCKS", "NO"));

    bIsFinalized = FALSE;
    bIgnoreReadErrors = CPLTestBool(CPLGetConfigOption("GTIFF_IGNORE_READ_ERRORS", "NO"));

    bEXIFMetadataLoaded = FALSE;
    bICCMetadataLoaded = FALSE;

    bScanDeferred = TRUE;

    bDirectIO = CPLTestBool(CPLGetConfigOption("GTIFF_DIRECT_IO", "NO"));
    const char* pszVirtualMemIO = CPLGetConfigOption("GTIFF_VIRTUAL_MEM_IO", "NO");
    if( EQUAL(pszVirtualMemIO, "IF_ENOUGH_RAM") )
        eVirtualMemIOUsage = VIRTUAL_MEM_IO_IF_ENOUGH_RAM;
    else if( CPLTestBool(pszVirtualMemIO) )
        eVirtualMemIOUsage = VIRTUAL_MEM_IO_YES;
    else
        eVirtualMemIOUsage = VIRTUAL_MEM_IO_NO;
    psVirtualMemIOMapping = NULL;

    eGeoTIFFKeysFlavor = GEOTIFF_KEYS_STANDARD;

    pBaseMapping = NULL;
    nRefBaseMapping = 0;

    bHasDiscardedLsb = FALSE;

    bIMDRPCMetadataLoaded = FALSE;
    papszMetadataFiles = NULL;
    poCompressThreadPool = NULL;
    hCompressThreadPoolMutex = NULL;

    m_pTempBufferForCommonDirectIO = NULL;
    m_nTempBufferForCommonDirectIOSize = 0;

    m_bReadGeoTransform = false;
    m_bLoadPam = false;
    m_bHasGotSiblingFiles = false;
}

/************************************************************************/
/*                           ~GTiffDataset()                            */
/************************************************************************/

GTiffDataset::~GTiffDataset()

{
    Finalize();
}

/************************************************************************/
/*                             Finalize()                               */
/************************************************************************/

int GTiffDataset::Finalize()
{
    if (bIsFinalized)
        return FALSE;

    bool bHasDroppedRef = false;

    Crystalize();

    if ( bColorProfileMetadataChanged )
    {
        SaveICCProfile(this, NULL, NULL, 0);
        bColorProfileMetadataChanged = FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Handle forcing xml:ESRI data to be written to PAM.              */
/* -------------------------------------------------------------------- */
    if( CPLTestBool(CPLGetConfigOption( "ESRI_XML_PAM", "NO" )) )
    {
        char **papszESRIMD = GetMetadata("xml:ESRI");
        if( papszESRIMD )
        {
            GDALPamDataset::SetMetadata( papszESRIMD, "xml:ESRI");
        }
    }

    if( psVirtualMemIOMapping )
        CPLVirtualMemFree( psVirtualMemIOMapping );
    psVirtualMemIOMapping = NULL;

/* -------------------------------------------------------------------- */
/*      Fill in missing blocks with empty data.                         */
/* -------------------------------------------------------------------- */
    if( bFillEmptyTiles )
    {
/* -------------------------------------------------------------------- */
/*  Ensure any blocks write cached by GDAL gets pushed through libtiff. */
/* -------------------------------------------------------------------- */
        FlushCacheInternal( false /* do not call FlushDirectory */ );

        FillEmptyTiles();
        bFillEmptyTiles = false;
    }

/* -------------------------------------------------------------------- */
/*      Force a complete flush, including either rewriting(moving)      */
/*      of writing in place the current directory.                      */
/* -------------------------------------------------------------------- */
    FlushCacheInternal( true );

    // Destroy compression pool
    if( poCompressThreadPool )
    {
        delete poCompressThreadPool;

        for( int i = 0; i < static_cast<int>(asCompressionJobs.size()); ++i )
        {
            CPLFree(asCompressionJobs[i].pabyBuffer);
            if( asCompressionJobs[i].pszTmpFilename )
            {
                VSIUnlink(asCompressionJobs[i].pszTmpFilename);
                CPLFree(asCompressionJobs[i].pszTmpFilename);
            }
        }
        CPLDestroyMutex(hCompressThreadPoolMutex);
    }

/* -------------------------------------------------------------------- */
/*      If there is still changed metadata, then presumably we want     */
/*      to push it into PAM.                                            */
/* -------------------------------------------------------------------- */
    if( bMetadataChanged )
    {
        PushMetadataToPam();
        bMetadataChanged = FALSE;
        GDALPamDataset::FlushCache();
    }

/* -------------------------------------------------------------------- */
/*      Cleanup overviews.                                              */
/* -------------------------------------------------------------------- */
    if( bBase )
    {
        for( int i = 0; i < nOverviewCount; i++ )
        {
            delete papoOverviewDS[i];
            bHasDroppedRef = true;
        }
        nOverviewCount = 0;

        for( int i = 0; i < nJPEGOverviewCountOri; i++ )
        {
            delete papoJPEGOverviewDS[i];
            bHasDroppedRef = true;
        }
        nJPEGOverviewCount = 0;
        nJPEGOverviewCountOri = 0;
        CPLFree( papoJPEGOverviewDS );
        papoJPEGOverviewDS = NULL;
    }

    /* If we are a mask dataset, we can have overviews, but we don't */
    /* own them. We can only free the array, not the overviews themselves */
    CPLFree( papoOverviewDS );
    papoOverviewDS = NULL;

    /* poMaskDS is owned by the main image and the overviews */
    /* so because of the latter case, we can delete it even if */
    /* we are not the base image */
    if (poMaskDS)
    {
        delete poMaskDS;
        poMaskDS = NULL;
        bHasDroppedRef = true;
    }

    if( poColorTable != NULL )
        delete poColorTable;
    poColorTable = NULL;

    if( bBase || bCloseTIFFHandle )
    {
        XTIFFClose( hTIFF );
        hTIFF = NULL;
        if( fpL != NULL )
        {
            if( VSIFCloseL( fpL ) != 0 )
            {
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
            }
            fpL = NULL;
        }
    }

    if( fpToWrite != NULL )
    {
        if( VSIFCloseL( fpToWrite ) != 0 )
        {
            CPLError(CE_Failure, CPLE_FileIO, "I/O error");
        }
        fpToWrite = NULL;
    }

    if( nGCPCount > 0 )
    {
        GDALDeinitGCPs( nGCPCount, pasGCPList );
        CPLFree( pasGCPList );
        pasGCPList = NULL;
        nGCPCount = 0;
    }

    CPLFree( pszProjection );
    pszProjection = NULL;

    CSLDestroy( papszCreationOptions );
    papszCreationOptions = NULL;

    CPLFree(pabyTempWriteBuffer);
    pabyTempWriteBuffer = NULL;

    if( ppoActiveDSRef != NULL && *ppoActiveDSRef == this )
        *ppoActiveDSRef = NULL;
    ppoActiveDSRef = NULL;

    bIMDRPCMetadataLoaded = FALSE;
    CSLDestroy(papszMetadataFiles);
    papszMetadataFiles = NULL;

    VSIFree(m_pTempBufferForCommonDirectIO);
    m_pTempBufferForCommonDirectIO = NULL;

    bIsFinalized = TRUE;

    return bHasDroppedRef;
}

/************************************************************************/
/*                        CloseDependentDatasets()                      */
/************************************************************************/

int GTiffDataset::CloseDependentDatasets()
{
    if (!bBase)
        return FALSE;

    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();

    bHasDroppedRef |= Finalize();

    return bHasDroppedRef;
}

/************************************************************************/
/*                        GetJPEGOverviewCount()                        */
/************************************************************************/

int GTiffDataset::GetJPEGOverviewCount()
{
    if( nJPEGOverviewCount >= 0 )
        return nJPEGOverviewCount;

    nJPEGOverviewCount = 0;
    if( !bBase || eAccess != GA_ReadOnly || nCompression != COMPRESSION_JPEG ||
        (nRasterXSize < 256 && nRasterYSize < 256) ||
        !CPLTestBool(CPLGetConfigOption("GTIFF_IMPLICIT_JPEG_OVR", "YES")) ||
        GDALGetDriverByName("JPEG") == NULL )
    {
        return 0;
    }
    const char* pszSourceColorSpace = oGTiffMDMD.GetMetadataItem( "SOURCE_COLOR_SPACE", "IMAGE_STRUCTURE");
    if( pszSourceColorSpace != NULL && EQUAL(pszSourceColorSpace, "CMYK") )
    {
        // We cannot handle implicit overviews on JPEG CMYK datasets converted to RGBA
        // This would imply doing the conversion in GTiffJPEGOverviewBand
       return 0;
    }

    /* libjpeg-6b only supports 2, 4 and 8 scale denominators */
    /* TODO: Later versions support more */
    for(int i = 2; i >= 0; i--)
    {
        if( nRasterXSize >= (256 << i) || nRasterYSize >= (256 << i) )
        {
            nJPEGOverviewCount = i + 1;
            break;
        }
    }
    if( nJPEGOverviewCount == 0 )
        return 0;

    if( !SetDirectory() )
        return 0;

    /* Get JPEG tables */
    uint32 nJPEGTableSize = 0;
    void* pJPEGTable = NULL;
    GByte abyFFD8[] = { 0xFF, 0xD8 };
    if( TIFFGetField(hTIFF, TIFFTAG_JPEGTABLES, &nJPEGTableSize, &pJPEGTable) )
    {
        if( pJPEGTable == NULL || (int)nJPEGTableSize <= 0 ||
            ((GByte*)pJPEGTable)[nJPEGTableSize-1] != 0xD9 )
        {
            return 0;
        }
        nJPEGTableSize --; /* remove final 0xD9 */
    }
    else
    {
        pJPEGTable = abyFFD8;
        nJPEGTableSize = 2;
    }

    papoJPEGOverviewDS = (GTiffJPEGOverviewDS**) CPLMalloc(
                        sizeof(GTiffJPEGOverviewDS*) * nJPEGOverviewCount );
    for(int i = 0; i < nJPEGOverviewCount; i++)
    {
        papoJPEGOverviewDS[i] = new GTiffJPEGOverviewDS(this, i+1,
                                            pJPEGTable, (int)nJPEGTableSize);
    }

    nJPEGOverviewCountOri = nJPEGOverviewCount;

    return nJPEGOverviewCount;
}

/************************************************************************/
/*                           FillEmptyTiles()                           */
/************************************************************************/

void GTiffDataset::FillEmptyTiles()

{
    if (!SetDirectory())
        return;

/* -------------------------------------------------------------------- */
/*      How many blocks are there in this file?                         */
/* -------------------------------------------------------------------- */
    int nBlockCount;

    if( nPlanarConfig == PLANARCONFIG_SEPARATE )
        nBlockCount = nBlocksPerBand * nBands;
    else
        nBlockCount = nBlocksPerBand;

/* -------------------------------------------------------------------- */
/*      Fetch block maps.                                               */
/* -------------------------------------------------------------------- */
    toff_t *panByteCounts = NULL;

    if( TIFFIsTiled( hTIFF ) )
        TIFFGetField( hTIFF, TIFFTAG_TILEBYTECOUNTS, &panByteCounts );
    else
        TIFFGetField( hTIFF, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts );

    if (panByteCounts == NULL)
    {
        /* Got here with libtiff 3.9.3 and tiff_write_8 test */
        CPLError(CE_Failure, CPLE_AppDefined, "FillEmptyTiles() failed because panByteCounts == NULL");
        return;
    }

/* -------------------------------------------------------------------- */
/*      Prepare a blank data buffer to write for uninitialized blocks.  */
/* -------------------------------------------------------------------- */
    int nBlockBytes;

    if( TIFFIsTiled( hTIFF ) )
        nBlockBytes = static_cast<int>(TIFFTileSize(hTIFF));
    else
        nBlockBytes = static_cast<int>(TIFFStripSize(hTIFF));

    GByte *pabyData = (GByte *) VSI_CALLOC_VERBOSE(nBlockBytes,1);
    if (pabyData == NULL)
    {
        return;
    }

/* -------------------------------------------------------------------- */
/*      Check all blocks, writing out data for uninitialized blocks.    */
/* -------------------------------------------------------------------- */
    for( int iBlock = 0; iBlock < nBlockCount; iBlock++ )
    {
        if( panByteCounts[iBlock] == 0 )
        {
            if( WriteEncodedTileOrStrip( iBlock, pabyData, FALSE ) != CE_None )
                break;
        }
    }

    CPLFree( pabyData );
}

/************************************************************************/
/*                        WriteEncodedTile()                            */
/************************************************************************/

bool GTiffDataset::WriteEncodedTile(uint32 tile, GByte *pabyData,
                                    int bPreserveDataBuffer)
{
    int cc = static_cast<int>(TIFFTileSize( hTIFF ));
    bool bNeedTileFill = false;
    int iRow=0, iColumn=0;
    int nBlocksPerRow=1, nBlocksPerColumn=1;

    /*
    ** Do we need to spread edge values right or down for a partial
    ** JPEG encoded tile?  We do this to avoid edge artifacts.
    */
    if( nCompression == COMPRESSION_JPEG )
    {
        nBlocksPerRow = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
        nBlocksPerColumn = DIV_ROUND_UP(nRasterYSize, nBlockYSize);

        iColumn = (tile % nBlocksPerBand) % nBlocksPerRow;
        iRow = (tile % nBlocksPerBand) / nBlocksPerRow;

        // Is this a partial right edge tile?
        if( iRow == nBlocksPerRow - 1
            && nRasterXSize % nBlockXSize != 0 )
            bNeedTileFill = true;

        // Is this a partial bottom edge tile?
        if( iColumn == nBlocksPerColumn - 1
            && nRasterYSize % nBlockYSize != 0 )
            bNeedTileFill = true;
    }

    /*
    ** If we need to fill out the tile, or if we want to prevent
    ** TIFFWriteEncodedTile from altering the buffer as part of
    ** byte swapping the data on write then we will need a temporary
    ** working buffer.  If not, we can just do a direct write.
    */
    if (bPreserveDataBuffer
        && (TIFFIsByteSwapped(hTIFF) || bNeedTileFill || bHasDiscardedLsb) )
    {
        if (cc != nTempWriteBufferSize)
        {
            pabyTempWriteBuffer = CPLRealloc(pabyTempWriteBuffer, cc);
            nTempWriteBufferSize = cc;
        }
        memcpy(pabyTempWriteBuffer, pabyData, cc);

        pabyData = (GByte *) pabyTempWriteBuffer;
    }

    /*
    ** Perform tile fill if needed.
    */
    // TODO: we should also handle the case of nBitsPerSample == 12
    // but this is more involved...
    if( bNeedTileFill && nBitsPerSample == 8 )
    {
        int nRightPixelsToFill = 0;
        int nBottomPixelsToFill = 0;
        unsigned int iX, iY, iSrcX, iSrcY;
        int nComponents = 1;
        if( nPlanarConfig == PLANARCONFIG_CONTIG )
            nComponents = nBands;

        CPLDebug( "GTiff", "Filling out jpeg edge tile on write." );

        if( iColumn == nBlocksPerRow - 1 )
            nRightPixelsToFill = nBlockXSize * (iColumn+1) - nRasterXSize;
        if( iRow == nBlocksPerColumn - 1 )
            nBottomPixelsToFill = nBlockYSize * (iRow+1) - nRasterYSize;

        // Fill out to the right.
        iSrcX = nBlockXSize - nRightPixelsToFill - 1;

        for( iX = iSrcX+1; iX < nBlockXSize; iX++ )
        {
            for( iY = 0; iY < nBlockYSize; iY++ )
            {
                memcpy( pabyData + (nBlockXSize * iY + iX) * nComponents,
                        pabyData + (nBlockXSize * iY + iSrcX) * nComponents,
                        nComponents );
            }
        }

        // now fill out the bottom.
        iSrcY = nBlockYSize - nBottomPixelsToFill - 1;
        for( iY = iSrcY+1; iY < nBlockYSize; iY++ )
        {
            memcpy( pabyData + nBlockXSize * nComponents * iY,
                    pabyData + nBlockXSize * nComponents * iSrcY,
                    nBlockXSize * nComponents );
        }
    }

    if( bHasDiscardedLsb != 0 )
    {
        int iBand = (nPlanarConfig == PLANARCONFIG_SEPARATE ) ? (int)tile / nBlocksPerBand : -1;
        DiscardLsb(pabyData, cc, iBand);
    }

    if( bStreamingOut )
    {
        if( tile != (uint32)(nLastWrittenBlockId + 1) )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Attempt to write block %d whereas %d was expected",
                     tile,  nLastWrittenBlockId + 1);
            return false;
        }
        if( (int)VSIFWriteL(pabyData, 1, cc, fpToWrite) != cc )
        {
            CPLError(CE_Failure, CPLE_FileIO, "Could not write %d bytes",
                     cc);
            return false;
        }
        nLastWrittenBlockId = tile;
        return true;
    }

/* -------------------------------------------------------------------- */
/*      Should we do compression in a worker thread ?                   */
/* -------------------------------------------------------------------- */
    if( SubmitCompressionJob(tile, pabyData, cc, nBlockYSize) )
        return true;

    // libtiff 4.0.6 or older do not always properly report write errors
#if !defined(INTERNAL_LIBTIFF) && (!defined(TIFFLIB_VERSION) || (TIFFLIB_VERSION <= 20150912))
    CPLErr eBefore = CPLGetLastErrorType();
#endif
    bool bRet = static_cast<int>(TIFFWriteEncodedTile(hTIFF, tile, pabyData, cc)) == cc;
#if !defined(INTERNAL_LIBTIFF) && (!defined(TIFFLIB_VERSION) || (TIFFLIB_VERSION <= 20150912))
    if( eBefore == CE_None && CPLGetLastErrorType() == CE_Failure )
        bRet = FALSE;
#endif
    return bRet;
}

/************************************************************************/
/*                        WriteEncodedStrip()                           */
/************************************************************************/

bool GTiffDataset::WriteEncodedStrip(uint32 strip, GByte* pabyData,
                                     int bPreserveDataBuffer)
{
    int cc = static_cast<int>(TIFFStripSize( hTIFF ));

/* -------------------------------------------------------------------- */
/*      If this is the last strip in the image, and is partial, then    */
/*      we need to trim the number of scanlines written to the          */
/*      amount of valid data we have. (#2748)                           */
/* -------------------------------------------------------------------- */
    int nStripWithinBand = strip % nBlocksPerBand;
    int nStripHeight = nRowsPerStrip;

    if( (int) ((nStripWithinBand+1) * nRowsPerStrip) > GetRasterYSize() )
    {
        nStripHeight = GetRasterYSize() - nStripWithinBand * nRowsPerStrip;
        cc = (cc / nRowsPerStrip) * nStripHeight;
        CPLDebug( "GTiff", "Adjusted bytes to write from %d to %d.",
                  (int) TIFFStripSize(hTIFF), cc );
    }

/* -------------------------------------------------------------------- */
/*      TIFFWriteEncodedStrip can alter the passed buffer if            */
/*      byte-swapping is necessary so we use a temporary buffer         */
/*      before calling it.                                              */
/* -------------------------------------------------------------------- */
    if (bPreserveDataBuffer && (TIFFIsByteSwapped(hTIFF) || bHasDiscardedLsb))
    {
        if (cc != nTempWriteBufferSize)
        {
            pabyTempWriteBuffer = CPLRealloc(pabyTempWriteBuffer, cc);
            nTempWriteBufferSize = cc;
        }
        memcpy(pabyTempWriteBuffer, pabyData, cc);
        pabyData = (GByte *) pabyTempWriteBuffer;
    }

    if( bHasDiscardedLsb != 0 )
    {
        int iBand = (nPlanarConfig == PLANARCONFIG_SEPARATE ) ? (int)strip / nBlocksPerBand : -1;
        DiscardLsb(pabyData, cc, iBand);
    }

    if( bStreamingOut )
    {
        if( strip != (uint32)(nLastWrittenBlockId + 1) )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Attempt to write block %d whereas %d was expected",
                     strip,  nLastWrittenBlockId + 1);
            return false;
        }
        if( (int)VSIFWriteL(pabyData, 1, cc, fpToWrite) != cc )
        {
            CPLError(CE_Failure, CPLE_FileIO, "Could not write %d bytes",
                     cc);
            return false;
        }
        nLastWrittenBlockId = strip;
        return true;
    }

/* -------------------------------------------------------------------- */
/*      Should we do compression in a worker thread ?                   */
/* -------------------------------------------------------------------- */
    if( SubmitCompressionJob(strip, pabyData, cc, nStripHeight) )
        return true;

    // libtiff 4.0.6 or older do not always properly report write errors
#if !defined(INTERNAL_LIBTIFF) && (!defined(TIFFLIB_VERSION) || (TIFFLIB_VERSION <= 20150912))
    CPLErr eBefore = CPLGetLastErrorType();
#endif
    bool bRet = static_cast<int>(TIFFWriteEncodedStrip(hTIFF, strip, pabyData, cc)) == cc;
#if !defined(INTERNAL_LIBTIFF) && (!defined(TIFFLIB_VERSION) || (TIFFLIB_VERSION <= 20150912))
    if( eBefore == CE_None && CPLGetLastErrorType() == CE_Failure )
        bRet = FALSE;
#endif
    return bRet;
}

/************************************************************************/
/*                        InitCompressionThreads()                      */
/************************************************************************/

void GTiffDataset::InitCompressionThreads(char** papszOptions)
{
    const char* pszValue = CSLFetchNameValue( papszOptions, "NUM_THREADS" );
    if (pszValue == NULL)
        pszValue = CPLGetConfigOption("GDAL_NUM_THREADS", NULL);
    if( pszValue )
    {
        int nThreads;
        if (EQUAL(pszValue, "ALL_CPUS"))
            nThreads = CPLGetNumCPUs();
        else
            nThreads = atoi(pszValue);
        if( nThreads > 1 )
        {
            if( nCompression == COMPRESSION_NONE ||
                nCompression == COMPRESSION_JPEG )
            {
                CPLDebug("GTiff", "NUM_THREADS ignored with uncompressed or JPEG");
            }
            else
            {
                CPLDebug("GTiff", "Using %d threads for compression", nThreads);
                poCompressThreadPool = new CPLWorkerThreadPool();
                if( !poCompressThreadPool->Setup(nThreads, NULL, NULL) )
                {
                    delete poCompressThreadPool;
                    poCompressThreadPool = NULL;
                }
                else
                {
                    // Add a margin of an extra job w.r.t thread number
                    // so as to optimize compression time (enables the main
                    // thread to do boring I/O while all CPUs are working)
                    asCompressionJobs.resize(nThreads + 1);
                    memset(&asCompressionJobs[0], 0,
                           asCompressionJobs.size() * sizeof(GTiffCompressionJob));
                    for(int i=0;i<(int)asCompressionJobs.size();i++)
                    {
                        asCompressionJobs[i].pszTmpFilename =
                            CPLStrdup(CPLSPrintf("/vsimem/gtiff/thread/job/%p",
                                                 &asCompressionJobs[i]));
                        asCompressionJobs[i].nStripOrTile = -1;
                    }
                    hCompressThreadPoolMutex = CPLCreateMutex();
                    CPLReleaseMutex(hCompressThreadPoolMutex);

                    // This is kind of a hack, but basically using
                    // TIFFWriteRawStrip/Tile and then TIFFReadEncodedStrip/Tile
                    // does not work on a newly created file, because TIFF_MYBUFFER
                    // is not set in tif_flags
                    // (if using TIFFWriteEncodedStrip/Tile first, TIFFWriteBufferSetup()
                    // is automatically called)
                    // This should likely rather fixed in libtiff itself...
                    TIFFWriteBufferSetup(hTIFF, NULL, -1);
                }
            }
        }
        else if (nThreads < 0 || (!EQUAL(pszValue, "0") && !EQUAL(pszValue, "1") && !EQUAL(pszValue, "ALL_CPUS")) )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Invalid value for NUM_THREADS: %s", pszValue);
        }
    }
}

/************************************************************************/
/*                       GetGTIFFKeysFlavor()                           */
/************************************************************************/

static GTIFFKeysFlavorEnum GetGTIFFKeysFlavor(char** papszOptions)
{
    const char* pszGeoTIFFKeysFlavor = CSLFetchNameValueDef(papszOptions,
                                                            "GEOTIFF_KEYS_FLAVOR",
                                                            "STANDARD");
    if( EQUAL(pszGeoTIFFKeysFlavor, "ESRI_PE") )
        return GEOTIFF_KEYS_ESRI_PE;
    return GEOTIFF_KEYS_STANDARD;
}

/************************************************************************/
/*                      InitCreationOrOpenOptions()                     */
/************************************************************************/

void GTiffDataset::InitCreationOrOpenOptions(char** papszOptions)
{
    InitCompressionThreads(papszOptions);

    eGeoTIFFKeysFlavor = GetGTIFFKeysFlavor(papszOptions);
}

/************************************************************************/
/*                      ThreadCompressionFunc()                         */
/************************************************************************/

void GTiffDataset::ThreadCompressionFunc(void* pData)
{
    GTiffCompressionJob* psJob = (GTiffCompressionJob*)pData;
    GTiffDataset* poDS = psJob->poDS;

    VSILFILE* fpTmp = VSIFOpenL(psJob->pszTmpFilename, "wb+");
    TIFF* hTIFFTmp = VSI_TIFFOpen(psJob->pszTmpFilename,
        (psJob->bTIFFIsBigEndian) ? "wb+" : "wl+", fpTmp);
    CPLAssert( hTIFFTmp != NULL );
    int nBlockXSize, nBlockYSize;
    poDS->GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    TIFFSetField(hTIFFTmp, TIFFTAG_IMAGEWIDTH, nBlockXSize);
    TIFFSetField(hTIFFTmp, TIFFTAG_IMAGELENGTH, psJob->nHeight);
    TIFFSetField(hTIFFTmp, TIFFTAG_BITSPERSAMPLE, poDS->nBitsPerSample);
    TIFFSetField(hTIFFTmp, TIFFTAG_COMPRESSION, poDS->nCompression);
    if( psJob->nPredictor != PREDICTOR_NONE )
        TIFFSetField(hTIFFTmp, TIFFTAG_PREDICTOR, psJob->nPredictor);
    if( poDS->nZLevel >= 0 )
        TIFFSetField(hTIFFTmp, TIFFTAG_ZIPQUALITY, poDS->nZLevel);
    if( poDS->nLZMAPreset > 0 && poDS->nCompression == COMPRESSION_LZMA)
        TIFFSetField(hTIFFTmp, TIFFTAG_LZMAPRESET, poDS->nLZMAPreset);
    TIFFSetField(hTIFFTmp, TIFFTAG_PHOTOMETRIC, poDS->nPhotometric);
    TIFFSetField(hTIFFTmp, TIFFTAG_SAMPLEFORMAT, poDS->nSampleFormat);
    TIFFSetField(hTIFFTmp, TIFFTAG_SAMPLESPERPIXEL, poDS->nSamplesPerPixel);
    TIFFSetField(hTIFFTmp, TIFFTAG_ROWSPERSTRIP, poDS->nBlockYSize);
    TIFFSetField(hTIFFTmp, TIFFTAG_PLANARCONFIG, poDS->nPlanarConfig);

    bool bOK
        = (TIFFWriteEncodedStrip(hTIFFTmp, 0, psJob->pabyBuffer,
                                 psJob->nBufferSize) == psJob->nBufferSize);

    int nOffset = 0;
    if( bOK )
    {
        toff_t* panOffsets = NULL;
        toff_t* panByteCounts = NULL;
        TIFFGetField(hTIFFTmp, TIFFTAG_STRIPOFFSETS, &panOffsets);
        TIFFGetField(hTIFFTmp, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts);

        nOffset = (int) panOffsets[0];
        psJob->nCompressedBufferSize = (int) panByteCounts[0];
    }
    else
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Error when compressing strip/tile %d",
                 psJob->nStripOrTile);
    }

    XTIFFClose(hTIFFTmp);
    if( VSIFCloseL(fpTmp) != 0 )
    {
        if( bOK )
        {
            bOK = false;
            CPLError(CE_Failure, CPLE_AppDefined, "Error when compressing strip/tile %d",
                    psJob->nStripOrTile);
        }
    }

    if( bOK )
    {
        vsi_l_offset nFileSize = 0;
        GByte* pabyCompressedBuffer = VSIGetMemFileBuffer(psJob->pszTmpFilename,
                                                                &nFileSize,
                                                                FALSE);
        CPLAssert(nOffset + psJob->nCompressedBufferSize <= (int)nFileSize);
        psJob->pabyCompressedBuffer = pabyCompressedBuffer + nOffset;
    }
    else
    {
        psJob->pabyCompressedBuffer = NULL;
        psJob->nCompressedBufferSize = 0;
    }

    CPLAcquireMutex(poDS->hCompressThreadPoolMutex, 1000.0);
    psJob->bReady = TRUE;
    CPLReleaseMutex(poDS->hCompressThreadPoolMutex);
}

/************************************************************************/
/*                        WriteRawStripOrTile()                         */
/************************************************************************/

void GTiffDataset::WriteRawStripOrTile(int nStripOrTile,
                                       GByte* pabyCompressedBuffer,
                                       int nCompressedBufferSize)
{
    //CPLDebug("GTIFF", "Writing raw strip/tile %d, size %d",
    //         nStripOrTile, nCompressedBufferSize);
    toff_t *panOffsets = NULL;
    if( TIFFGetField( hTIFF,
        TIFFIsTiled( hTIFF ) ? TIFFTAG_TILEOFFSETS : TIFFTAG_STRIPOFFSETS, &panOffsets ) &&
        panOffsets[nStripOrTile] != 0 )
    {
        // Make sure that if the tile/strip already exists, we write at end of file
        TIFFSetWriteOffset(hTIFF, 0);
    }
    if( TIFFIsTiled( hTIFF ) )
        TIFFWriteRawTile(hTIFF, nStripOrTile, pabyCompressedBuffer, nCompressedBufferSize);
    else
        TIFFWriteRawStrip(hTIFF, nStripOrTile, pabyCompressedBuffer, nCompressedBufferSize);
}

/************************************************************************/
/*                        WaitCompletionForBlock()                      */
/************************************************************************/

void GTiffDataset::WaitCompletionForBlock(int nBlockId)
{
    if( poCompressThreadPool != NULL )
    {
        for(int i=0;i<(int)asCompressionJobs.size();i++)
        {
            if( asCompressionJobs[i].nStripOrTile == nBlockId )
            {
                CPLDebug("GTIFF",
                         "Waiting for worker job to finish handling block %d",
                         nBlockId);

                CPLAcquireMutex(hCompressThreadPoolMutex, 1000.0);
                int bReady = asCompressionJobs[i].bReady;
                CPLReleaseMutex(hCompressThreadPoolMutex);
                if( !bReady )
                {
                    poCompressThreadPool->WaitCompletion(0);
                    CPLAssert( asCompressionJobs[i].bReady == TRUE );
                }

                if( asCompressionJobs[i].nCompressedBufferSize )
                {
                    WriteRawStripOrTile(asCompressionJobs[i].nStripOrTile,
                                  asCompressionJobs[i].pabyCompressedBuffer,
                                  asCompressionJobs[i].nCompressedBufferSize);
                }
                asCompressionJobs[i].pabyCompressedBuffer = NULL;
                asCompressionJobs[i].nBufferSize = 0;
                asCompressionJobs[i].bReady = FALSE;
                asCompressionJobs[i].nStripOrTile = -1;
                return;
            }
        }
    }
}

/************************************************************************/
/*                      SubmitCompressionJob()                          */
/************************************************************************/

int GTiffDataset::SubmitCompressionJob(int nStripOrTile, GByte* pabyData,
                                       int cc, int nHeight)
{
/* -------------------------------------------------------------------- */
/*      Should we do compression in a worker thread ?                   */
/* -------------------------------------------------------------------- */
    if( !( poCompressThreadPool != NULL &&
           (nCompression == COMPRESSION_ADOBE_DEFLATE ||
            nCompression == COMPRESSION_LZW ||
            nCompression == COMPRESSION_PACKBITS ||
            nCompression == COMPRESSION_LZMA) ) )
        return FALSE;

    int nNextCompressionJobAvail = -1;
    // Wait that at least one job is finished
    poCompressThreadPool->WaitCompletion(static_cast<int>(asCompressionJobs.size() - 1));
    for(int i=0;i<(int)asCompressionJobs.size();i++)
    {
        CPLAcquireMutex(hCompressThreadPoolMutex, 1000.0);
        int bReady = asCompressionJobs[i].bReady;
        CPLReleaseMutex(hCompressThreadPoolMutex);
        if( bReady )
        {
            if( asCompressionJobs[i].nCompressedBufferSize )
            {
                WriteRawStripOrTile( asCompressionJobs[i].nStripOrTile,
                                asCompressionJobs[i].pabyCompressedBuffer,
                                asCompressionJobs[i].nCompressedBufferSize );
            }
            asCompressionJobs[i].pabyCompressedBuffer = NULL;
            asCompressionJobs[i].nBufferSize = 0;
            asCompressionJobs[i].bReady = FALSE;
            asCompressionJobs[i].nStripOrTile = -1;
        }
        if( asCompressionJobs[i].nBufferSize == 0 )
        {
            if( nNextCompressionJobAvail < 0 )
                nNextCompressionJobAvail = i;
        }
    }
    CPLAssert(nNextCompressionJobAvail >= 0);

    GTiffCompressionJob* psJob = &asCompressionJobs[nNextCompressionJobAvail];
    psJob->poDS = this;
    psJob->bTIFFIsBigEndian = TIFFIsBigEndian(hTIFF);
    psJob->pabyBuffer = (GByte*)CPLRealloc(psJob->pabyBuffer, cc);
    memcpy(psJob->pabyBuffer, pabyData, cc);
    psJob->nBufferSize = cc;
    psJob->nHeight = nHeight;
    psJob->nStripOrTile = nStripOrTile;
    psJob->nPredictor = PREDICTOR_NONE;
    if ( nCompression == COMPRESSION_LZW ||
         nCompression == COMPRESSION_ADOBE_DEFLATE )
    {
        TIFFGetField( hTIFF, TIFFTAG_PREDICTOR, &psJob->nPredictor );
    }

    poCompressThreadPool->SubmitJob(ThreadCompressionFunc, psJob);
    return TRUE;
}

/************************************************************************/
/*                          DiscardLsb()                               */
/************************************************************************/

void GTiffDataset::DiscardLsb(GByte* pabyBuffer, int nBytes, int iBand)
{
    if( nBitsPerSample == 8 )
    {
        if( nPlanarConfig == PLANARCONFIG_SEPARATE )
        {
            const int nMask = anMaskLsb[iBand];
            const int nOffset = anOffsetLsb[iBand];
            for( int i = 0; i < nBytes; i ++ )
            {
                if( pabyBuffer[i] != 255 ) /* we want to keep 255 in case it is alpha */
                    pabyBuffer[i] = static_cast<GByte>((pabyBuffer[i] & nMask) | nOffset);
            }
        }
        else
        {
            for( int i = 0; i < nBytes; i += nBands )
            {
                for( int j = 0; j < nBands; j ++ )
                {
                    if( pabyBuffer[i + j] != 255 ) /* we want to keep 255 in case it is alpha */
                        pabyBuffer[i + j] = static_cast<GByte>((pabyBuffer[i + j] &
                                    anMaskLsb[j]) | anOffsetLsb[j]);
                }
            }
        }
    }
    else if( nBitsPerSample == 16 )
    {
        if( nPlanarConfig == PLANARCONFIG_SEPARATE )
        {
            const int nMask = anMaskLsb[iBand];
            const int nOffset = anOffsetLsb[iBand];
            for( int i = 0; i < nBytes/2; i ++ )
            {
                ((GUInt16*)pabyBuffer)[i] = static_cast<GUInt16>((((GUInt16*)pabyBuffer)[i] & nMask) | nOffset);
            }
        }
        else
        {
            for( int i = 0; i < nBytes/2; i += nBands )
            {
                for( int j = 0; j < nBands; j ++ )
                {
                    ((GUInt16*)pabyBuffer)[i + j] = static_cast<GUInt16>((((GUInt16*)pabyBuffer)[i + j] &
                                    anMaskLsb[j]) | anOffsetLsb[j]);
                }
            }
        }
    }
    else if( nBitsPerSample == 32 )
    {
        if( nPlanarConfig == PLANARCONFIG_SEPARATE )
        {
            const int nMask = anMaskLsb[iBand];
            const int nOffset = anOffsetLsb[iBand];
            for( int i = 0; i < nBytes/4; i ++ )
            {
                ((GUInt32*)pabyBuffer)[i] = (((GUInt32*)pabyBuffer)[i] & nMask) | nOffset;
            }
        }
        else
        {
            for( int i = 0; i < nBytes/4; i += nBands )
            {
                for( int j = 0; j < nBands; j ++ )
                {
                    ((GUInt32*)pabyBuffer)[i + j] = (((GUInt32*)pabyBuffer)[i + j] &
                                    anMaskLsb[j]) | anOffsetLsb[j];
                }
            }
        }
    }
}

/************************************************************************/
/*                  WriteEncodedTileOrStrip()                           */
/************************************************************************/

CPLErr  GTiffDataset::WriteEncodedTileOrStrip(uint32 tile_or_strip, void* data,
                                              int bPreserveDataBuffer)
{
    CPLErr eErr = CE_None;

    if( TIFFIsTiled( hTIFF ) )
    {
        if( !(WriteEncodedTile(tile_or_strip, (GByte*) data,
                               bPreserveDataBuffer)) )
        {
            eErr = CE_Failure;
        }
    }
    else
    {
        if( !(WriteEncodedStrip(tile_or_strip, (GByte *) data,
                                bPreserveDataBuffer)) )
        {
            eErr = CE_Failure;
        }
    }

    return eErr;
}

/************************************************************************/
/*                           FlushBlockBuf()                            */
/************************************************************************/

CPLErr GTiffDataset::FlushBlockBuf()

{
    if( nLoadedBlock < 0 || !bLoadedBlockDirty )
        return CE_None;

    bLoadedBlockDirty = FALSE;

    if (!SetDirectory())
        return CE_Failure;

    CPLErr eErr = WriteEncodedTileOrStrip(nLoadedBlock, pabyBlockBuf, TRUE);
    if (eErr != CE_None)
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                    "WriteEncodedTile/Strip() failed." );
        bWriteErrorInFlushBlockBuf = TRUE;
    }

    return eErr;
}

/************************************************************************/
/*                            LoadBlockBuf()                            */
/*                                                                      */
/*      Load working block buffer with request block (tile/strip).      */
/************************************************************************/

CPLErr GTiffDataset::LoadBlockBuf( int nBlockId, int bReadFromDisk )

{
    if( nLoadedBlock == nBlockId )
        return CE_None;

/* -------------------------------------------------------------------- */
/*      If we have a dirty loaded block, flush it out first.            */
/* -------------------------------------------------------------------- */
    CPLErr eErr = CE_None;
    if( nLoadedBlock != -1 && bLoadedBlockDirty )
    {
        eErr = FlushBlockBuf();
        if( eErr != CE_None )
            return eErr;
    }

/* -------------------------------------------------------------------- */
/*      Get block size.                                                 */
/* -------------------------------------------------------------------- */
    int nBlockBufSize;
    if( TIFFIsTiled(hTIFF) )
        nBlockBufSize = static_cast<int>(TIFFTileSize( hTIFF ));
    else
        nBlockBufSize = static_cast<int>(TIFFStripSize( hTIFF ));

    if ( !nBlockBufSize )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Bogus block size; unable to allocate a buffer.");
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Allocate a temporary buffer for this strip.                     */
/* -------------------------------------------------------------------- */
    if( pabyBlockBuf == NULL )
    {
        pabyBlockBuf = (GByte *) VSI_CALLOC_VERBOSE( 1, nBlockBufSize );
        if( pabyBlockBuf == NULL )
        {
            return( CE_Failure );
        }
    }

/* -------------------------------------------------------------------- */
/*  When called from ::IWriteBlock in separate cases (or in single band */
/*  geotiffs), the ::IWriteBlock will override the content of the buffer*/
/*  with pImage, so we don't need to read data from disk                */
/* -------------------------------------------------------------------- */
    if( !bReadFromDisk || bStreamingOut )
    {
        nLoadedBlock = nBlockId;
        return CE_None;
    }

    /* libtiff 3.X doesn't like mixing read&write of JPEG compressed blocks */
    /* The below hack is necessary due to another hack that consist in */
    /* writing zero block to force creation of JPEG tables */
    if( nBlockId == 0 && bDontReloadFirstBlock )
    {
        bDontReloadFirstBlock = FALSE;
        memset( pabyBlockBuf, 0, nBlockBufSize );
        nLoadedBlock = nBlockId;
        return CE_None;
    }

/* -------------------------------------------------------------------- */
/*      The bottom most partial tiles and strips are sometimes only     */
/*      partially encoded.  This code reduces the requested data so     */
/*      an error won't be reported in this case. (#1179)                */
/* -------------------------------------------------------------------- */
    int nBlockReqSize = nBlockBufSize;
    int nBlocksPerRow = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
    int nBlockYOff = (nBlockId % nBlocksPerBand) / nBlocksPerRow;

    if( (int)((nBlockYOff+1) * nBlockYSize) > nRasterYSize )
    {
        nBlockReqSize = (nBlockBufSize / nBlockYSize)
            * (nBlockYSize - (((nBlockYOff+1) * nBlockYSize) % nRasterYSize));
        memset( pabyBlockBuf, 0, nBlockBufSize );
    }

    WaitCompletionForBlock(nBlockId);

/* -------------------------------------------------------------------- */
/*      If we don't have this block already loaded, and we know it      */
/*      doesn't yet exist on disk, just zero the memory buffer and      */
/*      pretend we loaded it.                                           */
/* -------------------------------------------------------------------- */
    if( !IsBlockAvailable( nBlockId ) )
    {
        memset( pabyBlockBuf, 0, nBlockBufSize );
        nLoadedBlock = nBlockId;
        return CE_None;
    }

/* -------------------------------------------------------------------- */
/*      Load the block, if it isn't our current block.                  */
/* -------------------------------------------------------------------- */
    if( TIFFIsTiled( hTIFF ) )
    {
        if( TIFFReadEncodedTile(hTIFF, nBlockId, pabyBlockBuf,
                                nBlockReqSize) == -1
            && !bIgnoreReadErrors )
        {
            /* Once TIFFError() is properly hooked, this can go away */
            CPLError( CE_Failure, CPLE_AppDefined,
                      "TIFFReadEncodedTile() failed." );

            memset( pabyBlockBuf, 0, nBlockBufSize );

            eErr = CE_Failure;
        }
    }
    else
    {
        if( TIFFReadEncodedStrip(hTIFF, nBlockId, pabyBlockBuf,
                                 nBlockReqSize) == -1
            && !bIgnoreReadErrors )
        {
            /* Once TIFFError() is properly hooked, this can go away */
            CPLError( CE_Failure, CPLE_AppDefined,
                      "TIFFReadEncodedStrip() failed." );

            memset( pabyBlockBuf, 0, nBlockBufSize );

            eErr = CE_Failure;
        }
    }

    if( eErr == CE_None )
    {
        nLoadedBlock = nBlockId;
    }
    else
    {
        nLoadedBlock = -1;
    }
    bLoadedBlockDirty = FALSE;

    return eErr;
}

/************************************************************************/
/*                   GTiffFillStreamableOffsetAndCount()                */
/************************************************************************/

static void GTiffFillStreamableOffsetAndCount(TIFF* hTIFF, int nSize)
{
    uint32  nXSize, nYSize;
    TIFFGetField( hTIFF, TIFFTAG_IMAGEWIDTH, &nXSize );
    TIFFGetField( hTIFF, TIFFTAG_IMAGELENGTH, &nYSize );
    toff_t* panOffset = NULL, *panSize = NULL;
    const bool bIsTiled = CPL_TO_BOOL( TIFFIsTiled(hTIFF) );
    int nBlockCount = ( bIsTiled ) ? TIFFNumberOfTiles(hTIFF) : TIFFNumberOfStrips(hTIFF);
    TIFFGetField( hTIFF, bIsTiled ? TIFFTAG_TILEOFFSETS : TIFFTAG_STRIPOFFSETS, &panOffset );
    TIFFGetField( hTIFF, bIsTiled ? TIFFTAG_TILEBYTECOUNTS : TIFFTAG_STRIPBYTECOUNTS, &panSize );
    toff_t nOffset = nSize;
    int nBlocksPerBand = 1; /* trick to avoid clang static analyzer raising false positive about divide by zero later */
    uint32 nRowsPerStrip = 0;
    if( !bIsTiled  )
    {
        TIFFGetField( hTIFF, TIFFTAG_ROWSPERSTRIP, &nRowsPerStrip);
        if( nRowsPerStrip > (uint32)nYSize )
            nRowsPerStrip = nYSize;
        nBlocksPerBand = DIV_ROUND_UP(nYSize, nRowsPerStrip);
    }
    for(int i=0;i<nBlockCount;i++)
    {
        int cc = bIsTiled ? static_cast<int>(TIFFTileSize(hTIFF)) :
                            static_cast<int>(TIFFStripSize(hTIFF));
        if( !bIsTiled  )
        {
/* -------------------------------------------------------------------- */
/*      If this is the last strip in the image, and is partial, then    */
/*      we need to trim the number of scanlines written to the          */
/*      amount of valid data we have. (#2748)                           */
/* -------------------------------------------------------------------- */
            int nStripWithinBand = i % nBlocksPerBand;
            if( (int) ((nStripWithinBand+1) * nRowsPerStrip) > (int) nYSize )
            {
                cc = (cc / nRowsPerStrip)
                    * (nYSize - nStripWithinBand * nRowsPerStrip);
            }
        }
        panOffset[i] = nOffset;
        panSize[i] = cc;
        nOffset += cc;
    }
}

/************************************************************************/
/*                             Crystalize()                             */
/*                                                                      */
/*      Make sure that the directory information is written out for     */
/*      a new file, require before writing any imagery data.            */
/************************************************************************/

void GTiffDataset::Crystalize()

{
    if( !bCrystalized )
    {
        if( bCheckIfColorInterpMustGoToPamAtCrystalization )
        {
            bool bColorInterpToPam = false;
            if( nPhotometric == PHOTOMETRIC_MINISBLACK )
            {
                for( int i=0;i<nBands;i++)
                {
                    GDALColorInterp eInterp = GetRasterBand(i+1)->GetColorInterpretation();
                    if( !(eInterp == GCI_GrayIndex || eInterp == GCI_Undefined ||
                          (i > 0 && eInterp == GCI_AlphaBand)) )
                    {
                        bColorInterpToPam = true;
                        break;
                    }
                }
            }
            else if( nPhotometric == PHOTOMETRIC_RGB )
            {
                for( int i=0;i<nBands;i++)
                {
                    GDALColorInterp eInterp = GetRasterBand(i+1)->GetColorInterpretation();
                    if( !((i == 0 && eInterp == GCI_RedBand) ||
                          (i == 1 && eInterp == GCI_GreenBand) ||
                          (i == 2 && eInterp == GCI_BlueBand) ||
                          (i >= 3 && (eInterp == GCI_Undefined || eInterp == GCI_AlphaBand))) )
                    {
                        bColorInterpToPam = true;
                        break;
                    }
                }
            }
            else
            {
                bColorInterpToPam = true;
            }
            if( bColorInterpToPam )
            {
                CPLDebug("GTiff", "Color interpretations have to go to PAM");
                for( int i=0;i<nBands;i++)
                {
                    static_cast<GDALPamRasterBand*>(GetRasterBand(i+1))->
                        GDALPamRasterBand::SetColorInterpretation(
                            GetRasterBand(i+1)->GetColorInterpretation() );
                }
            }
            bCheckIfColorInterpMustGoToPamAtCrystalization = false;
        }

        // FIXME? libtiff writes extended tags in the order they are specified
        // and not in increasing order
        WriteMetadata( this, hTIFF, TRUE, osProfile, osFilename,
                       papszCreationOptions );
        WriteGeoTIFFInfo();
        if( bNoDataSet )
            WriteNoDataValue( hTIFF, dfNoDataValue );

        bMetadataChanged = FALSE;
        bGeoTIFFInfoChanged = FALSE;
        bNoDataChanged = FALSE;
        bNeedsRewrite = FALSE;

        bCrystalized = TRUE;

        TIFFWriteCheck( hTIFF, TIFFIsTiled(hTIFF), "GTiffDataset::Crystalize");

        // Keep zip and tiff quality, and jpegcolormode which get reset when we call
        // TIFFWriteDirectory
        int jquality = -1, zquality = -1, nColorMode = -1, nJpegTablesModeIn = -1;
        TIFFGetField(hTIFF, TIFFTAG_JPEGQUALITY, &jquality);
        TIFFGetField(hTIFF, TIFFTAG_ZIPQUALITY, &zquality);
        TIFFGetField( hTIFF, TIFFTAG_JPEGCOLORMODE, &nColorMode );
        TIFFGetField( hTIFF, TIFFTAG_JPEGTABLESMODE, &nJpegTablesModeIn );

        TIFFWriteDirectory( hTIFF );
        if( bStreamingOut )
        {
            /* We need to write twice the directory to be sure that custom */
            /* TIFF tags are correctly sorted and that padding bytes have been */
            /* added */
            TIFFSetDirectory( hTIFF, 0 );
            TIFFWriteDirectory( hTIFF );

            if( VSIFSeekL( fpL, 0, SEEK_END ) != 0 )
            {
                CPLError(CE_Failure, CPLE_FileIO, "Could not seek");
            }
            int nSize = (int) VSIFTellL(fpL);

            TIFFSetDirectory( hTIFF, 0 );
            GTiffFillStreamableOffsetAndCount( hTIFF, nSize );
            TIFFWriteDirectory( hTIFF );

            vsi_l_offset nDataLength;
            void* pabyBuffer = VSIGetMemFileBuffer( osTmpFilename, &nDataLength, FALSE);
            if( (int)VSIFWriteL( pabyBuffer, 1, (int)nDataLength, fpToWrite ) != (int)nDataLength )
            {
                CPLError(CE_Failure, CPLE_FileIO, "Could not write %d bytes",
                        (int)nDataLength);
            }
            /* In case of single strip file, there's a libtiff check that would */
            /* issue a warning since the file hasn't the required size */
            CPLPushErrorHandler(CPLQuietErrorHandler);
            TIFFSetDirectory( hTIFF, 0 );
            CPLPopErrorHandler();
        }
        else
            TIFFSetDirectory( hTIFF, 0 );


        // Now, reset zip and tiff quality and jpegcolormode.
        if(jquality > 0)
            TIFFSetField(hTIFF, TIFFTAG_JPEGQUALITY, jquality);
        if(zquality > 0)
            TIFFSetField(hTIFF, TIFFTAG_ZIPQUALITY, zquality);
        if (nColorMode >= 0)
            TIFFSetField(hTIFF, TIFFTAG_JPEGCOLORMODE, nColorMode);
        if (nJpegTablesModeIn >= 0 )
            TIFFSetField(hTIFF, TIFFTAG_JPEGTABLESMODE, nJpegTablesModeIn);

        nDirOffset = TIFFCurrentDirOffset( hTIFF );
    }
}

#ifdef INTERNAL_LIBTIFF

#define IO_CACHE_PAGE_SIZE      4096

static
void GTiffCacheOffsetOrCount(VSILFILE* fp,
                             vsi_l_offset nBaseOffset,
                             int nBlockId,
                             uint32 nstrips,
                             uint64* panVals,
                             size_t sizeofval)
{
    int i, iStartBefore;
    vsi_l_offset nOffset, nOffsetStartPage, nOffsetEndPage;
    GByte buffer[2 * IO_CACHE_PAGE_SIZE];

    nOffset = nBaseOffset + sizeofval * nBlockId;
    nOffsetStartPage = (nOffset / IO_CACHE_PAGE_SIZE) * IO_CACHE_PAGE_SIZE;
    nOffsetEndPage = nOffsetStartPage + IO_CACHE_PAGE_SIZE;

    if( nOffset + sizeofval > nOffsetEndPage )
        nOffsetEndPage += IO_CACHE_PAGE_SIZE;
    vsi_l_offset nLastStripOffset = nBaseOffset + nstrips * sizeofval;
    if( nLastStripOffset < nOffsetEndPage )
        nOffsetEndPage  = nLastStripOffset;
    if( nOffsetStartPage >= nOffsetEndPage )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read offset/size for strile %d", nBlockId);
        panVals[nBlockId] = 0;
        return;
    }
    if( VSIFSeekL(fp, nOffsetStartPage, SEEK_SET) != 0 )
    {
        panVals[nBlockId] = 0;
        return;
    }
    size_t nToRead = (size_t)(nOffsetEndPage - nOffsetStartPage);
    size_t nRead = VSIFReadL(buffer, 1, nToRead, fp);
    if( nRead < nToRead )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read offset/size for strile around ~%d", nBlockId);
        memset(buffer + nRead, 0, nToRead - nRead);
    }
    iStartBefore = - (int)((nOffset - nOffsetStartPage) / sizeofval);
    if( nBlockId + iStartBefore < 0 )
        iStartBefore = -nBlockId;
    for(i=iStartBefore; (uint32)(nBlockId + i) < nstrips &&
        (GIntBig)nOffset + (i+1) * (int)sizeofval <= (GIntBig)nOffsetEndPage; i++)
    {
        if( sizeofval == 4 )
        {
            uint32 val;
            memcpy(&val,
                   buffer + (nOffset - nOffsetStartPage) + i * sizeof(val),
                   sizeof(val));
            panVals[nBlockId + i] = val;
        }
        else
        {
            uint64 val;
            memcpy(&val,
                   buffer + (nOffset - nOffsetStartPage) + i * sizeof(val),
                   sizeof(val));
            panVals[nBlockId + i] = val;
        }
    }
}

#endif /* INTERNAL_LIBTIFF */

/************************************************************************/
/*                          IsBlockAvailable()                          */
/*                                                                      */
/*      Return TRUE if the indicated strip/tile is available.  We       */
/*      establish this by testing if the stripbytecount is zero.  If    */
/*      zero then the block has never been committed to disk.           */
/************************************************************************/

int GTiffDataset::IsBlockAvailable( int nBlockId )

{
#ifdef INTERNAL_LIBTIFF
#ifdef DEFER_STRILE_LOAD
    /* Optimization to avoid fetching the whole Strip/TileCounts and Strip/TileOffsets arrays */
    if( eAccess == GA_ReadOnly &&
        !(hTIFF->tif_flags & TIFF_SWAB) &&
        hTIFF->tif_dir.td_nstrips > 2 &&
        (hTIFF->tif_dir.td_stripoffset_entry.tdir_type == TIFF_LONG ||
         hTIFF->tif_dir.td_stripoffset_entry.tdir_type == TIFF_LONG8) &&
        (hTIFF->tif_dir.td_stripbytecount_entry.tdir_type == TIFF_LONG ||
         hTIFF->tif_dir.td_stripbytecount_entry.tdir_type == TIFF_LONG8) &&
        !bStreamingIn )
    {
        if( hTIFF->tif_dir.td_stripoffset == NULL )
        {
            hTIFF->tif_dir.td_stripoffset =
                (uint64*) _TIFFmalloc( sizeof(uint64) * hTIFF->tif_dir.td_nstrips );
            hTIFF->tif_dir.td_stripbytecount =
                (uint64*) _TIFFmalloc( sizeof(uint64) * hTIFF->tif_dir.td_nstrips );
            if( hTIFF->tif_dir.td_stripoffset && hTIFF->tif_dir.td_stripbytecount )
            {
                memset(hTIFF->tif_dir.td_stripoffset, 0xFF,
                       sizeof(uint64) * hTIFF->tif_dir.td_nstrips );
                memset(hTIFF->tif_dir.td_stripbytecount, 0xFF,
                       sizeof(uint64) * hTIFF->tif_dir.td_nstrips );
            }
            else
            {
                _TIFFfree(hTIFF->tif_dir.td_stripoffset);
                hTIFF->tif_dir.td_stripoffset = NULL;
                _TIFFfree(hTIFF->tif_dir.td_stripbytecount);
                hTIFF->tif_dir.td_stripbytecount = NULL;
            }
        }
        if( hTIFF->tif_dir.td_stripbytecount == NULL )
            return FALSE;
        if( ~(hTIFF->tif_dir.td_stripoffset[nBlockId]) == 0 ||
            ~(hTIFF->tif_dir.td_stripbytecount[nBlockId]) == 0 )
        {
            VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( hTIFF ));
            vsi_l_offset nCurOffset = VSIFTellL(fp);
            if( ~(hTIFF->tif_dir.td_stripoffset[nBlockId]) == 0 )
            {
                vsi_l_offset l_nDirOffset;
                if( hTIFF->tif_flags&TIFF_BIGTIFF )
                    l_nDirOffset = hTIFF->tif_dir.td_stripoffset_entry.tdir_offset.toff_long8;
                else
                    l_nDirOffset = hTIFF->tif_dir.td_stripoffset_entry.tdir_offset.toff_long;

                if( hTIFF->tif_dir.td_stripoffset_entry.tdir_type == TIFF_LONG )
                {
                    GTiffCacheOffsetOrCount(fp,
                                            l_nDirOffset,
                                            nBlockId,
                                            hTIFF->tif_dir.td_nstrips,
                                            hTIFF->tif_dir.td_stripoffset,
                                            sizeof(uint32));
                }
                else
                {
                    GTiffCacheOffsetOrCount(fp,
                                            l_nDirOffset,
                                            nBlockId,
                                            hTIFF->tif_dir.td_nstrips,
                                            hTIFF->tif_dir.td_stripoffset,
                                            sizeof(uint64));
                }
            }

            if( ~(hTIFF->tif_dir.td_stripbytecount[nBlockId]) == 0 )
            {
                vsi_l_offset l_nDirOffset;
                if( hTIFF->tif_flags&TIFF_BIGTIFF )
                    l_nDirOffset = hTIFF->tif_dir.td_stripbytecount_entry.tdir_offset.toff_long8;
                else
                    l_nDirOffset = hTIFF->tif_dir.td_stripbytecount_entry.tdir_offset.toff_long;

                if( hTIFF->tif_dir.td_stripbytecount_entry.tdir_type == TIFF_LONG )
                {
                    GTiffCacheOffsetOrCount(fp,
                                            l_nDirOffset,
                                            nBlockId,
                                            hTIFF->tif_dir.td_nstrips,
                                            hTIFF->tif_dir.td_stripbytecount,
                                            sizeof(uint32));
                }
                else
                {
                    GTiffCacheOffsetOrCount(fp,
                                            l_nDirOffset,
                                            nBlockId,
                                            hTIFF->tif_dir.td_nstrips,
                                            hTIFF->tif_dir.td_stripbytecount,
                                            sizeof(uint64));
                }
            }
            if( VSIFSeekL(fp, nCurOffset, SEEK_SET) != 0 )
            {
                /* For some reason Coverity tells: */
                /* Value of non-local "this->hTIFF->tif_dir.td_stripoffset" that was verified to be "NULL" is not restored as it was along other paths. */
                /* coverity[end_of_path] */
                return FALSE;
            }
        }
        return hTIFF->tif_dir.td_stripbytecount[nBlockId] != 0;
    }
#endif /* DEFER_STRILE_LOAD */
#endif /* INTERNAL_LIBTIFF */
    toff_t *panByteCounts = NULL;

    if( ( TIFFIsTiled( hTIFF )
          && TIFFGetField( hTIFF, TIFFTAG_TILEBYTECOUNTS, &panByteCounts ) )
        || ( !TIFFIsTiled( hTIFF )
          && TIFFGetField( hTIFF, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts ) ) )
    {
        if( panByteCounts == NULL )
            return FALSE;
        else
            return panByteCounts[nBlockId] != 0;
    }
    else
        return FALSE;
}

/************************************************************************/
/*                             FlushCache()                             */
/*                                                                      */
/*      We override this so we can also flush out local tiff strip      */
/*      cache if need be.                                               */
/************************************************************************/


void GTiffDataset::FlushCache()

{
    FlushCacheInternal( true );
}

void GTiffDataset::FlushCacheInternal( bool bFlushDirectory )
{
    if (bIsFinalized || ppoActiveDSRef == NULL)
        return;

    GDALPamDataset::FlushCache();

    if( bLoadedBlockDirty && nLoadedBlock != -1 )
        FlushBlockBuf();

    CPLFree( pabyBlockBuf );
    pabyBlockBuf = NULL;
    nLoadedBlock = -1;
    bLoadedBlockDirty = false;

    // Finish compression
    if( poCompressThreadPool )
    {
        poCompressThreadPool->WaitCompletion();

        // Flush remaining data
        for( int i = 0; i < static_cast<int>(asCompressionJobs.size()); ++i )
        {
            if( asCompressionJobs[i].bReady )
            {
                if( asCompressionJobs[i].nCompressedBufferSize )
                {
                    WriteRawStripOrTile( asCompressionJobs[i].nStripOrTile,
                                   asCompressionJobs[i].pabyCompressedBuffer,
                                   asCompressionJobs[i].nCompressedBufferSize );
                }
                asCompressionJobs[i].pabyCompressedBuffer = NULL;
                asCompressionJobs[i].nBufferSize = 0;
                asCompressionJobs[i].bReady = false;
                asCompressionJobs[i].nStripOrTile = -1;
            }
        }
    }

    if( bFlushDirectory )
    {
        if( !SetDirectory() )
            return;
        FlushDirectory();
    }
}

/************************************************************************/
/*                           FlushDirectory()                           */
/************************************************************************/

void GTiffDataset::FlushDirectory()

{
    if( GetAccess() == GA_Update )
    {
        if( bMetadataChanged )
        {
            if (!SetDirectory())
                return;
            bNeedsRewrite =
                WriteMetadata( this, hTIFF, TRUE, osProfile, osFilename,
                               papszCreationOptions );
            bMetadataChanged = FALSE;
        }

        if( bGeoTIFFInfoChanged )
        {
            if (!SetDirectory())
                return;
            WriteGeoTIFFInfo();
        }

        if( bNoDataChanged )
        {
            if (!SetDirectory())
                return;
            if( bNoDataSet )
            {
                WriteNoDataValue( hTIFF, dfNoDataValue );
            }
            else
            {
                UnsetNoDataValue( hTIFF );
            }
            bNeedsRewrite = TRUE;
            bNoDataChanged = FALSE;
        }

        if( bNeedsRewrite )
        {
#if defined(TIFFLIB_VERSION)
#if defined(HAVE_TIFFGETSIZEPROC)
            if (!SetDirectory())
                return;

            TIFFSizeProc pfnSizeProc = TIFFGetSizeProc( hTIFF );

            nDirOffset = pfnSizeProc( TIFFClientdata( hTIFF ) );
            if( (nDirOffset % 2) == 1 )
                nDirOffset++;

            TIFFRewriteDirectory( hTIFF );

            TIFFSetSubDirectory( hTIFF, nDirOffset );
#elif  TIFFLIB_VERSION > 20010925 && TIFFLIB_VERSION != 20011807
            if (!SetDirectory())
                return;

            TIFFRewriteDirectory( hTIFF );
#endif
#endif
            bNeedsRewrite = FALSE;
        }
    }

    // there are some circumstances in which we can reach this point
    // without having made this our directory (SetDirectory()) in which
    // case we should not risk a flush.
    if( GetAccess() == GA_Update && TIFFCurrentDirOffset(hTIFF) == nDirOffset )
    {
#if defined(BIGTIFF_SUPPORT)
        TIFFSizeProc pfnSizeProc = TIFFGetSizeProc( hTIFF );

        toff_t nNewDirOffset = pfnSizeProc( TIFFClientdata( hTIFF ) );
        if( (nNewDirOffset % 2) == 1 )
            nNewDirOffset++;

        TIFFFlush( hTIFF );

        if( nDirOffset != TIFFCurrentDirOffset( hTIFF ) )
        {
            nDirOffset = nNewDirOffset;
            CPLDebug( "GTiff",
                      "directory moved during flush in FlushDirectory()" );
        }
#else
        /* For libtiff 3.X, the above causes regressions and crashes in */
        /* tiff_write.py and tiff_ovr.py */
        TIFFFlush( hTIFF );
#endif
    }
}

/************************************************************************/
/*                           CleanOverviews()                           */
/************************************************************************/

CPLErr GTiffDataset::CleanOverviews()

{
    CPLAssert( bBase );

    ScanDirectories();

    FlushDirectory();
    *ppoActiveDSRef = NULL;

/* -------------------------------------------------------------------- */
/*      Cleanup overviews objects, and get offsets to all overview      */
/*      directories.                                                    */
/* -------------------------------------------------------------------- */
    std::vector<toff_t>  anOvDirOffsets;
    int i;

    for( i = 0; i < nOverviewCount; i++ )
    {
        anOvDirOffsets.push_back( papoOverviewDS[i]->nDirOffset );
        delete papoOverviewDS[i];
    }

/* -------------------------------------------------------------------- */
/*      Loop through all the directories, translating the offsets       */
/*      into indexes we can use with TIFFUnlinkDirectory().             */
/* -------------------------------------------------------------------- */
    std::vector<uint16> anOvDirIndexes;
    int iThisOffset = 1;

    TIFFSetDirectory( hTIFF, 0 );

    while( true )
    {
        for( i = 0; i < nOverviewCount; i++ )
        {
            if( anOvDirOffsets[i] == TIFFCurrentDirOffset( hTIFF ) )
            {
                CPLDebug( "GTiff", "%d -> %d",
                          (int) anOvDirOffsets[i], iThisOffset );
                anOvDirIndexes.push_back( (uint16) iThisOffset );
            }
        }

        if( TIFFLastDirectory( hTIFF ) )
            break;

        TIFFReadDirectory( hTIFF );
        iThisOffset++;
    }

/* -------------------------------------------------------------------- */
/*      Actually unlink the target directories.  Note that we do        */
/*      this from last to first so as to avoid renumbering any of       */
/*      the earlier directories we need to remove.                      */
/* -------------------------------------------------------------------- */
    while( !anOvDirIndexes.empty() )
    {
        TIFFUnlinkDirectory( hTIFF, anOvDirIndexes.back() );
        anOvDirIndexes.pop_back();
    }

    CPLFree( papoOverviewDS );

    nOverviewCount = 0;
    papoOverviewDS = NULL;

    if (!SetDirectory())
        return CE_Failure;

    return CE_None;
}


/************************************************************************/
/*                   RegisterNewOverviewDataset()                       */
/************************************************************************/

CPLErr GTiffDataset::RegisterNewOverviewDataset(toff_t nOverviewOffset)
{
    GTiffDataset* poODS = new GTiffDataset();
    poODS->nJpegQuality = nJpegQuality;
    poODS->nZLevel = nZLevel;
    poODS->nLZMAPreset = nLZMAPreset;

    if( nCompression == COMPRESSION_JPEG )
    {
        if ( CPLGetConfigOption( "JPEG_QUALITY_OVERVIEW", NULL ) != NULL )
        {
            poODS->nJpegQuality =  atoi(CPLGetConfigOption("JPEG_QUALITY_OVERVIEW","75"));
        }
        TIFFSetField( hTIFF, TIFFTAG_JPEGQUALITY,
                        poODS->nJpegQuality );
    }

    if( poODS->OpenOffset( hTIFF, ppoActiveDSRef, nOverviewOffset, FALSE,
                            GA_Update ) != CE_None )
    {
        delete poODS;
        return CE_Failure;
    }
    else
    {
        nOverviewCount++;
        papoOverviewDS = (GTiffDataset **)
            CPLRealloc(papoOverviewDS,
                        nOverviewCount * (sizeof(void*)));
        papoOverviewDS[nOverviewCount-1] = poODS;
        poODS->poBaseDS = this;
        return CE_None;
    }
}

/************************************************************************/
/*                  CreateOverviewsFromSrcOverviews()                   */
/************************************************************************/

CPLErr GTiffDataset::CreateOverviewsFromSrcOverviews(GDALDataset* poSrcDS)
{
    CPLAssert(poSrcDS->GetRasterCount() != 0);
    CPLAssert(nOverviewCount == 0);

    ScanDirectories();

/* -------------------------------------------------------------------- */
/*      Move to the directory for this dataset.                         */
/* -------------------------------------------------------------------- */
    if (!SetDirectory())
        return CE_Failure;
    FlushDirectory();

    int nOvBitsPerSample = nBitsPerSample;

/* -------------------------------------------------------------------- */
/*      Do we have a palette?  If so, create a TIFF compatible version. */
/* -------------------------------------------------------------------- */
    std::vector<unsigned short> anTRed, anTGreen, anTBlue;
    unsigned short      *panRed=NULL, *panGreen=NULL, *panBlue=NULL;

    if( nPhotometric == PHOTOMETRIC_PALETTE && poColorTable != NULL )
    {
        int nColors;

        if( nOvBitsPerSample == 8 )
            nColors = 256;
        else if( nOvBitsPerSample < 8 )
            nColors = 1 << nOvBitsPerSample;
        else
            nColors = 65536;

        anTRed.resize(nColors,0);
        anTGreen.resize(nColors,0);
        anTBlue.resize(nColors,0);

        for( int iColor = 0; iColor < nColors; iColor++ )
        {
            if( iColor < poColorTable->GetColorEntryCount() )
            {
                GDALColorEntry  sRGB;

                poColorTable->GetColorEntryAsRGB( iColor, &sRGB );

                anTRed[iColor] = (unsigned short) (256 * sRGB.c1);
                anTGreen[iColor] = (unsigned short) (256 * sRGB.c2);
                anTBlue[iColor] = (unsigned short) (256 * sRGB.c3);
            }
            else
            {
                anTRed[iColor] = anTGreen[iColor] = anTBlue[iColor] = 0;
            }
        }

        panRed = &(anTRed[0]);
        panGreen = &(anTGreen[0]);
        panBlue = &(anTBlue[0]);
    }

/* -------------------------------------------------------------------- */
/*      Do we need some metadata for the overviews?                     */
/* -------------------------------------------------------------------- */
    CPLString osMetadata;

    GTIFFBuildOverviewMetadata( "NONE", this, osMetadata );

/* -------------------------------------------------------------------- */
/*      Fetch extra sample tag                                          */
/* -------------------------------------------------------------------- */
    uint16 *panExtraSampleValues = NULL;
    uint16 nExtraSamples = 0;

    if( TIFFGetField( hTIFF, TIFFTAG_EXTRASAMPLES, &nExtraSamples, &panExtraSampleValues) )
    {
        uint16* panExtraSampleValuesNew = (uint16*) CPLMalloc(nExtraSamples * sizeof(uint16));
        memcpy(panExtraSampleValuesNew, panExtraSampleValues, nExtraSamples * sizeof(uint16));
        panExtraSampleValues = panExtraSampleValuesNew;
    }
    else
    {
        panExtraSampleValues = NULL;
        nExtraSamples = 0;
    }

/* -------------------------------------------------------------------- */
/*      Fetch predictor tag                                             */
/* -------------------------------------------------------------------- */
    uint16 nPredictor = PREDICTOR_NONE;
    if ( nCompression == COMPRESSION_LZW ||
         nCompression == COMPRESSION_ADOBE_DEFLATE )
        TIFFGetField( hTIFF, TIFFTAG_PREDICTOR, &nPredictor );
    int nOvrBlockXSize, nOvrBlockYSize;
    GTIFFGetOverviewBlockSize(&nOvrBlockXSize, &nOvrBlockYSize);

    int nSrcOverviews = poSrcDS->GetRasterBand(1)->GetOverviewCount();
    int i;
    CPLErr eErr = CE_None;

    for(i=0;i<nSrcOverviews && eErr == CE_None;i++)
    {
        GDALRasterBand* poOvrBand = poSrcDS->GetRasterBand(1)->GetOverview(i);

        int         nOXSize = poOvrBand->GetXSize(), nOYSize = poOvrBand->GetYSize();

        toff_t nOverviewOffset =
                GTIFFWriteDirectory(hTIFF, FILETYPE_REDUCEDIMAGE,
                                    nOXSize, nOYSize,
                                    nOvBitsPerSample, nPlanarConfig,
                                    nSamplesPerPixel, nOvrBlockXSize, nOvrBlockYSize, TRUE,
                                    nCompression, nPhotometric, nSampleFormat,
                                    nPredictor,
                                    panRed, panGreen, panBlue,
                                    nExtraSamples, panExtraSampleValues,
                                    osMetadata );

        if( nOverviewOffset == 0 )
            eErr = CE_Failure;
        else
            eErr = RegisterNewOverviewDataset(nOverviewOffset);
    }

    CPLFree(panExtraSampleValues);
    panExtraSampleValues = NULL;

/* -------------------------------------------------------------------- */
/*      Create overviews for the mask.                                  */
/* -------------------------------------------------------------------- */
    if (eErr == CE_None)
        eErr = CreateInternalMaskOverviews(nOvrBlockXSize, nOvrBlockYSize);

    return eErr;
}


/************************************************************************/
/*                       CreateInternalMaskOverviews()                  */
/************************************************************************/

CPLErr GTiffDataset::CreateInternalMaskOverviews(int nOvrBlockXSize,
                                                 int nOvrBlockYSize)
{
    GTiffDataset *poODS;

    ScanDirectories();

/* -------------------------------------------------------------------- */
/*      Create overviews for the mask.                                  */
/* -------------------------------------------------------------------- */
    CPLErr eErr = CE_None;

    const char* pszInternalMask = CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK", NULL);
    if (poMaskDS != NULL &&
        poMaskDS->GetRasterCount() == 1 &&
        (pszInternalMask == NULL || CPLTestBool(pszInternalMask)))
    {
        int nMaskOvrCompression;
        if( strstr(GDALGetMetadataItem(GDALGetDriverByName( "GTiff" ),
                                       GDAL_DMD_CREATIONOPTIONLIST, NULL ),
                   "<Value>DEFLATE</Value>") != NULL )
            nMaskOvrCompression = COMPRESSION_ADOBE_DEFLATE;
        else
            nMaskOvrCompression = COMPRESSION_PACKBITS;

        int i;
        for( i = 0; i < nOverviewCount; i++ )
        {
            if (papoOverviewDS[i]->poMaskDS == NULL)
            {
                toff_t  nOverviewOffset;

                nOverviewOffset =
                    GTIFFWriteDirectory(hTIFF, FILETYPE_REDUCEDIMAGE | FILETYPE_MASK,
                                        papoOverviewDS[i]->nRasterXSize, papoOverviewDS[i]->nRasterYSize,
                                        1, PLANARCONFIG_CONTIG,
                                        1, nOvrBlockXSize, nOvrBlockYSize, TRUE,
                                        nMaskOvrCompression, PHOTOMETRIC_MASK, SAMPLEFORMAT_UINT, PREDICTOR_NONE,
                                        NULL, NULL, NULL, 0, NULL,
                                        "" );

                if( nOverviewOffset == 0 )
                {
                    eErr = CE_Failure;
                    continue;
                }

                poODS = new GTiffDataset();
                if( poODS->OpenOffset( hTIFF, ppoActiveDSRef,
                                       nOverviewOffset, FALSE,
                                       GA_Update ) != CE_None )
                {
                    delete poODS;
                    eErr = CE_Failure;
                }
                else
                {
                    poODS->bPromoteTo8Bits = CPLTestBool(CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK_TO_8BIT", "YES"));
                    poODS->poBaseDS = this;
                    papoOverviewDS[i]->poMaskDS = poODS;
                    poMaskDS->nOverviewCount++;
                    poMaskDS->papoOverviewDS = (GTiffDataset **)
                    CPLRealloc(poMaskDS->papoOverviewDS,
                               poMaskDS->nOverviewCount * (sizeof(void*)));
                    poMaskDS->papoOverviewDS[poMaskDS->nOverviewCount-1] = poODS;
                }
            }
        }
    }

    return eErr;
}

/************************************************************************/
/*                          IBuildOverviews()                           */
/************************************************************************/

CPLErr GTiffDataset::IBuildOverviews(
    const char * pszResampling,
    int nOverviews, int * panOverviewList,
    int nBandsIn, int * panBandList,
    GDALProgressFunc pfnProgress, void * pProgressData )

{
    CPLErr       eErr = CE_None;
    int          i;
    GTiffDataset *poODS;
    int          bUseGenericHandling = FALSE;

    ScanDirectories();

    /* Make implicit JPEG overviews invisible, but do not destroy */
    /* them in case they are already used (not sure that the client */
    /* has the right to do that. behaviour undefined in GDAL API I think) */
    nJPEGOverviewCount = 0;

/* -------------------------------------------------------------------- */
/*      If RRD or external OVR overviews requested, then invoke         */
/*      generic handling.                                               */
/* -------------------------------------------------------------------- */
    if( CPLTestBool(CPLGetConfigOption( "USE_RRD", "NO" ))
        || CPLTestBool(CPLGetConfigOption( "TIFF_USE_OVR", "NO" )) )
    {
        bUseGenericHandling = TRUE;
    }

/* -------------------------------------------------------------------- */
/*      If we don't have read access, then create the overviews         */
/*      externally.                                                     */
/* -------------------------------------------------------------------- */
    if( GetAccess() != GA_Update )
    {
        CPLDebug( "GTiff",
                  "File open for read-only accessing, "
                  "creating overviews externally." );

        bUseGenericHandling = TRUE;
    }

    if( bUseGenericHandling )
    {
        if (nOverviewCount != 0)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Cannot add external overviews when there are already internal overviews");
            return CE_Failure;
        }

        return GDALDataset::IBuildOverviews(
            pszResampling, nOverviews, panOverviewList,
            nBandsIn, panBandList, pfnProgress, pProgressData );
    }

/* -------------------------------------------------------------------- */
/*      Our TIFF overview support currently only works safely if all    */
/*      bands are handled at the same time.                             */
/* -------------------------------------------------------------------- */
    if( nBandsIn != GetRasterCount() )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Generation of overviews in TIFF currently only"
                  " supported when operating on all bands.\n"
                  "Operation failed.\n" );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      If zero overviews were requested, we need to clear all          */
/*      existing overviews.                                             */
/* -------------------------------------------------------------------- */
    if( nOverviews == 0 )
    {
        if( nOverviewCount == 0 )
            return GDALDataset::IBuildOverviews(
                pszResampling, nOverviews, panOverviewList,
                nBandsIn, panBandList, pfnProgress, pProgressData );
        else
            return CleanOverviews();
    }

/* -------------------------------------------------------------------- */
/*      libtiff 3.X has issues when generating interleaved overviews.   */
/*      so generate them one after another one.                         */
/* -------------------------------------------------------------------- */
#ifndef BIGTIFF_SUPPORT
    if( nOverviews > 1 )
    {
        double* padfOvrRasterFactor = (double*) CPLMalloc(sizeof(double) * nOverviews);
        double dfTotal = 0;
        for( i = 0; i < nOverviews; i++ )
        {
            if( panOverviewList[i] <= 0 )
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Invalid overview factor : %d", panOverviewList[i]);
                eErr = CE_Failure;
                break;
            }
            padfOvrRasterFactor[i] = 1.0 / (panOverviewList[i] * panOverviewList[i]);
            dfTotal += padfOvrRasterFactor[i];
        }

        double dfAcc = 0.0;
        for( i = 0; i < nOverviews && eErr == CE_None; i++ )
        {
            void *pScaledProgressData;
            pScaledProgressData =
                GDALCreateScaledProgress( dfAcc / dfTotal,
                                          (dfAcc + padfOvrRasterFactor[i]) / dfTotal,
                                         pfnProgress, pProgressData );
            dfAcc += padfOvrRasterFactor[i];

            eErr = IBuildOverviews(
                    pszResampling, 1, &panOverviewList[i],
                    nBandsIn, panBandList, GDALScaledProgress, pScaledProgressData );

            GDALDestroyScaledProgress(pScaledProgressData);
        }

        CPLFree(padfOvrRasterFactor);

        return eErr;
    }
#endif

/* -------------------------------------------------------------------- */
/*      Initialize progress counter.                                    */
/* -------------------------------------------------------------------- */
    if( !pfnProgress( 0.0, NULL, pProgressData ) )
    {
        CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Move to the directory for this dataset.                         */
/* -------------------------------------------------------------------- */
    if (!SetDirectory())
        return CE_Failure;
    FlushDirectory();

/* -------------------------------------------------------------------- */
/*      If we are averaging bit data to grayscale we need to create     */
/*      8bit overviews.                                                 */
/* -------------------------------------------------------------------- */
    int nOvBitsPerSample = nBitsPerSample;

    if( STARTS_WITH_CI(pszResampling, "AVERAGE_BIT2") )
        nOvBitsPerSample = 8;

/* -------------------------------------------------------------------- */
/*      Do we have a palette?  If so, create a TIFF compatible version. */
/* -------------------------------------------------------------------- */
    std::vector<unsigned short> anTRed, anTGreen, anTBlue;
    unsigned short      *panRed=NULL, *panGreen=NULL, *panBlue=NULL;

    if( nPhotometric == PHOTOMETRIC_PALETTE && poColorTable != NULL )
    {
        int nColors;

        if( nOvBitsPerSample == 8 )
            nColors = 256;
        else if( nOvBitsPerSample < 8 )
            nColors = 1 << nOvBitsPerSample;
        else
            nColors = 65536;

        anTRed.resize(nColors,0);
        anTGreen.resize(nColors,0);
        anTBlue.resize(nColors,0);

        for( int iColor = 0; iColor < nColors; iColor++ )
        {
            if( iColor < poColorTable->GetColorEntryCount() )
            {
                GDALColorEntry  sRGB;

                poColorTable->GetColorEntryAsRGB( iColor, &sRGB );

                anTRed[iColor] = (unsigned short) (256 * sRGB.c1);
                anTGreen[iColor] = (unsigned short) (256 * sRGB.c2);
                anTBlue[iColor] = (unsigned short) (256 * sRGB.c3);
            }
            else
            {
                anTRed[iColor] = anTGreen[iColor] = anTBlue[iColor] = 0;
            }
        }

        panRed = &(anTRed[0]);
        panGreen = &(anTGreen[0]);
        panBlue = &(anTBlue[0]);
    }

/* -------------------------------------------------------------------- */
/*      Do we need some metadata for the overviews?                     */
/* -------------------------------------------------------------------- */
    CPLString osMetadata;

    GTIFFBuildOverviewMetadata( pszResampling, this, osMetadata );

/* -------------------------------------------------------------------- */
/*      Fetch extra sample tag                                          */
/* -------------------------------------------------------------------- */
    uint16 *panExtraSampleValues = NULL;
    uint16 nExtraSamples = 0;

    if( TIFFGetField( hTIFF, TIFFTAG_EXTRASAMPLES, &nExtraSamples, &panExtraSampleValues) )
    {
        uint16* panExtraSampleValuesNew = (uint16*) CPLMalloc(nExtraSamples * sizeof(uint16));
        memcpy(panExtraSampleValuesNew, panExtraSampleValues, nExtraSamples * sizeof(uint16));
        panExtraSampleValues = panExtraSampleValuesNew;
    }
    else
    {
        panExtraSampleValues = NULL;
        nExtraSamples = 0;
    }

/* -------------------------------------------------------------------- */
/*      Fetch predictor tag                                             */
/* -------------------------------------------------------------------- */
    uint16 nPredictor = PREDICTOR_NONE;
    if ( nCompression == COMPRESSION_LZW ||
         nCompression == COMPRESSION_ADOBE_DEFLATE )
        TIFFGetField( hTIFF, TIFFTAG_PREDICTOR, &nPredictor );

/* -------------------------------------------------------------------- */
/*      Establish which of the overview levels we already have, and     */
/*      which are new.  We assume that band 1 of the file is            */
/*      representative.                                                 */
/* -------------------------------------------------------------------- */
    int nOvrBlockXSize, nOvrBlockYSize;
    GTIFFGetOverviewBlockSize(&nOvrBlockXSize, &nOvrBlockYSize);
    for( i = 0; i < nOverviews && eErr == CE_None; i++ )
    {
        int   j;

        for( j = 0; j < nOverviewCount && eErr == CE_None; j++ )
        {
            int    nOvFactor;

            poODS = papoOverviewDS[j];

            nOvFactor = GDALComputeOvFactor(poODS->GetRasterXSize(),
                                             GetRasterXSize(),
                                             poODS->GetRasterYSize(),
                                             GetRasterYSize());

            if( nOvFactor == panOverviewList[i]
                || nOvFactor == GDALOvLevelAdjust2( panOverviewList[i],
                                                    GetRasterXSize(),
                                                    GetRasterYSize() ) )
                panOverviewList[i] *= -1;
        }

        if( panOverviewList[i] > 0 )
        {
            toff_t nOverviewOffset;
            int         nOXSize, nOYSize;

            nOXSize = (GetRasterXSize() + panOverviewList[i] - 1)
                / panOverviewList[i];
            nOYSize = (GetRasterYSize() + panOverviewList[i] - 1)
                / panOverviewList[i];

            nOverviewOffset =
                GTIFFWriteDirectory(hTIFF, FILETYPE_REDUCEDIMAGE,
                                    nOXSize, nOYSize,
                                    nOvBitsPerSample, nPlanarConfig,
                                    nSamplesPerPixel, nOvrBlockXSize, nOvrBlockYSize, TRUE,
                                    nCompression, nPhotometric, nSampleFormat,
                                    nPredictor,
                                    panRed, panGreen, panBlue,
                                    nExtraSamples, panExtraSampleValues,
                                    osMetadata );


            if( nOverviewOffset == 0 )
                eErr = CE_Failure;
            else
                eErr = RegisterNewOverviewDataset(nOverviewOffset);
        }
        else
            panOverviewList[i] *= -1;
    }

    CPLFree(panExtraSampleValues);
    panExtraSampleValues = NULL;

/* -------------------------------------------------------------------- */
/*      Create overviews for the mask.                                  */
/* -------------------------------------------------------------------- */
    if (eErr == CE_None)
        eErr = CreateInternalMaskOverviews(nOvrBlockXSize, nOvrBlockYSize);
    else
        return eErr;

/* -------------------------------------------------------------------- */
/*      Refresh overviews for the mask                                  */
/* -------------------------------------------------------------------- */
    if (poMaskDS != NULL &&
        poMaskDS->GetRasterCount() == 1)
    {
        GDALRasterBand **papoOverviewBands;
        int nMaskOverviews = 0;

        papoOverviewBands = (GDALRasterBand **) CPLCalloc(sizeof(void*),nOverviewCount);
        for( i = 0; i < nOverviewCount; i++ )
        {
            if (papoOverviewDS[i]->poMaskDS != NULL)
            {
                papoOverviewBands[nMaskOverviews ++] =
                        papoOverviewDS[i]->poMaskDS->GetRasterBand(1);
            }
        }
        eErr = GDALRegenerateOverviews( (GDALRasterBandH)
                                        poMaskDS->GetRasterBand(1),
                                        nMaskOverviews,
                                        (GDALRasterBandH *) papoOverviewBands,
                                        pszResampling, GDALDummyProgress, NULL);
        CPLFree(papoOverviewBands);
    }


/* -------------------------------------------------------------------- */
/*      Refresh old overviews that were listed.                         */
/* -------------------------------------------------------------------- */
    if (nPlanarConfig == PLANARCONFIG_CONTIG &&
        GDALDataTypeIsComplex(GetRasterBand( panBandList[0] )->GetRasterDataType()) == FALSE &&
        GetRasterBand( panBandList[0] )->GetColorTable() == NULL &&
        (STARTS_WITH_CI(pszResampling, "NEAR") || EQUAL(pszResampling, "AVERAGE") ||
         EQUAL(pszResampling, "GAUSS") || EQUAL(pszResampling, "CUBIC") ||
         EQUAL(pszResampling, "CUBICSPLINE") || EQUAL(pszResampling, "LANCZOS") ||
         EQUAL(pszResampling, "BILINEAR")))
    {
        /* In the case of pixel interleaved compressed overviews, we want to generate */
        /* the overviews for all the bands block by block, and not band after band, */
        /* in order to write the block once and not loose space in the TIFF file */
        /* We also use that logic for uncompressed overviews, since GDALRegenerateOverviewsMultiBand() */
        /* will be able to trigger cascading overview regeneration even in the presence */
        /* of an alpha band. */

        GDALRasterBand ***papapoOverviewBands;
        GDALRasterBand  **papoBandList;

        int nNewOverviews = 0;
        int iBand;

        papapoOverviewBands = (GDALRasterBand ***) CPLCalloc(sizeof(void*),nBandsIn);
        papoBandList = (GDALRasterBand **) CPLCalloc(sizeof(void*),nBandsIn);
        for( iBand = 0; iBand < nBandsIn; iBand++ )
        {
            GDALRasterBand* poBand = GetRasterBand( panBandList[iBand] );

            papoBandList[iBand] = poBand;
            papapoOverviewBands[iBand] = (GDALRasterBand **) CPLCalloc(sizeof(void*), poBand->GetOverviewCount());

            int iCurOverview = 0;
            for( i = 0; i < nOverviews; i++ )
            {
                int   j;

                for( j = 0; j < poBand->GetOverviewCount(); j++ )
                {
                    int    nOvFactor;
                    GDALRasterBand * poOverview = poBand->GetOverview( j );

                    nOvFactor = GDALComputeOvFactor(poOverview->GetXSize(),
                                                     poBand->GetXSize(),
                                                     poOverview->GetYSize(),
                                                     poBand->GetYSize());

                    int bHasNoData;
                    double noDataValue = poBand->GetNoDataValue(&bHasNoData);

                    if (bHasNoData)
                        poOverview->SetNoDataValue(noDataValue);

                    if( nOvFactor == panOverviewList[i]
                        || nOvFactor == GDALOvLevelAdjust2( panOverviewList[i],
                                                            poBand->GetXSize(),
                                                            poBand->GetYSize() ) )
                    {
                        papapoOverviewBands[iBand][iCurOverview] = poOverview;
                        iCurOverview++ ;
                        break;
                    }
                }
            }

            if (nNewOverviews == 0)
                nNewOverviews = iCurOverview;
            else if (nNewOverviews != iCurOverview)
            {
                CPLAssert(0);
                return CE_Failure;
            }
        }

        GDALRegenerateOverviewsMultiBand(nBandsIn, papoBandList,
                                         nNewOverviews, papapoOverviewBands,
                                         pszResampling, pfnProgress, pProgressData );

        for( iBand = 0; iBand < nBandsIn; iBand++ )
        {
            CPLFree(papapoOverviewBands[iBand]);
        }
        CPLFree(papapoOverviewBands);
        CPLFree(papoBandList);
    }
    else
    {
        GDALRasterBand **papoOverviewBands;

        papoOverviewBands = (GDALRasterBand **)
            CPLCalloc(sizeof(void*),nOverviews);

        for( int iBand = 0; iBand < nBandsIn && eErr == CE_None; iBand++ )
        {
            GDALRasterBand *poBand;
            int            nNewOverviews;

            poBand = GetRasterBand( panBandList[iBand] );

            nNewOverviews = 0;
            for( i = 0; i < nOverviews && poBand != NULL; i++ )
            {
                int   j;

                for( j = 0; j < poBand->GetOverviewCount(); j++ )
                {
                    int    nOvFactor;
                    GDALRasterBand * poOverview = poBand->GetOverview( j );

                    int bHasNoData;
                    double noDataValue = poBand->GetNoDataValue(&bHasNoData);

                    if (bHasNoData)
                        poOverview->SetNoDataValue(noDataValue);

                    nOvFactor = GDALComputeOvFactor(poOverview->GetXSize(),
                                                     poBand->GetXSize(),
                                                     poOverview->GetYSize(),
                                                     poBand->GetYSize());

                    if( nOvFactor == panOverviewList[i]
                        || nOvFactor == GDALOvLevelAdjust2( panOverviewList[i],
                                                            poBand->GetXSize(),
                                                            poBand->GetYSize() ) )
                    {
                        papoOverviewBands[nNewOverviews++] = poOverview;
                        break;
                    }
                }
            }

            void         *pScaledProgressData;

            pScaledProgressData =
                GDALCreateScaledProgress( iBand / (double) nBandsIn,
                                        (iBand+1) / (double) nBandsIn,
                                        pfnProgress, pProgressData );

            eErr = GDALRegenerateOverviews( (GDALRasterBandH) poBand,
                                            nNewOverviews,
                                            (GDALRasterBandH *) papoOverviewBands,
                                            pszResampling,
                                            GDALScaledProgress,
                                            pScaledProgressData);

            GDALDestroyScaledProgress( pScaledProgressData );
        }

    /* -------------------------------------------------------------------- */
    /*      Cleanup                                                         */
    /* -------------------------------------------------------------------- */
        CPLFree( papoOverviewBands );
    }


    pfnProgress( 1.0, NULL, pProgressData );

    return eErr;
}

/************************************************************************/
/*                      GTiffWriteDummyGeokeyDirectory()                */
/************************************************************************/

static void GTiffWriteDummyGeokeyDirectory(TIFF* hTIFF)
{
    // If we have existing geokeys, try to wipe them
    // by writing a dummy geokey directory. (#2546)
    uint16 *panVI = NULL;
    uint16 nKeyCount;

    if( TIFFGetField( hTIFF, TIFFTAG_GEOKEYDIRECTORY,
                        &nKeyCount, &panVI ) )
    {
        GUInt16 anGKVersionInfo[4] = { 1, 1, 0, 0 };
        double  adfDummyDoubleParams[1] = { 0.0 };
        TIFFSetField( hTIFF, TIFFTAG_GEOKEYDIRECTORY,
                        4, anGKVersionInfo );
        TIFFSetField( hTIFF, TIFFTAG_GEODOUBLEPARAMS,
                        1, adfDummyDoubleParams );
        TIFFSetField( hTIFF, TIFFTAG_GEOASCIIPARAMS, "" );
    }
}

/************************************************************************/
/*                          WriteGeoTIFFInfo()                          */
/************************************************************************/

void GTiffDataset::WriteGeoTIFFInfo()

{
    bool bPixelIsPoint = false;
    bool bPointGeoIgnore = false;

    if( GetMetadataItem( GDALMD_AREA_OR_POINT )
        && EQUAL(GetMetadataItem(GDALMD_AREA_OR_POINT),
                 GDALMD_AOP_POINT) )
    {
        bPixelIsPoint = true;
        bPointGeoIgnore =
            CPLTestBool( CPLGetConfigOption( "GTIFF_POINT_GEO_IGNORE",
                                             "FALSE") );
    }

    if( bForceUnsetGTOrGCPs )
    {
        bNeedsRewrite = TRUE;
        bForceUnsetGTOrGCPs = FALSE;

#ifdef HAVE_UNSETFIELD
        TIFFUnsetField( hTIFF, TIFFTAG_GEOPIXELSCALE );
        TIFFUnsetField( hTIFF, TIFFTAG_GEOTIEPOINTS );
        TIFFUnsetField( hTIFF, TIFFTAG_GEOTRANSMATRIX );
#endif
    }

    if( bForceUnsetProjection )
    {
        bNeedsRewrite = TRUE;
        bForceUnsetProjection = FALSE;

#ifdef HAVE_UNSETFIELD
        TIFFUnsetField( hTIFF, TIFFTAG_GEOKEYDIRECTORY );
        TIFFUnsetField( hTIFF, TIFFTAG_GEODOUBLEPARAMS );
        TIFFUnsetField( hTIFF, TIFFTAG_GEOASCIIPARAMS );
#else
        GTiffWriteDummyGeokeyDirectory(hTIFF);
#endif
    }

/* -------------------------------------------------------------------- */
/*      If the geotransform is the default, don't bother writing it.    */
/* -------------------------------------------------------------------- */
    if( adfGeoTransform[0] != 0.0 || adfGeoTransform[1] != 1.0
        || adfGeoTransform[2] != 0.0 || adfGeoTransform[3] != 0.0
        || adfGeoTransform[4] != 0.0 || ABS(adfGeoTransform[5]) != 1.0 )
    {
        bNeedsRewrite = TRUE;

/* -------------------------------------------------------------------- */
/*      Clear old tags to ensure we don't end up with conflicting       */
/*      information. (#2625)                                            */
/* -------------------------------------------------------------------- */
#ifdef HAVE_UNSETFIELD
        TIFFUnsetField( hTIFF, TIFFTAG_GEOPIXELSCALE );
        TIFFUnsetField( hTIFF, TIFFTAG_GEOTIEPOINTS );
        TIFFUnsetField( hTIFF, TIFFTAG_GEOTRANSMATRIX );
#endif

/* -------------------------------------------------------------------- */
/*      Write the transform.  If we have a normal north-up image we     */
/*      use the tiepoint plus pixelscale otherwise we use a matrix.     */
/* -------------------------------------------------------------------- */
        if( adfGeoTransform[2] == 0.0 && adfGeoTransform[4] == 0.0
                && adfGeoTransform[5] < 0.0 )
        {
            double adfPixelScale[3], adfTiePoints[6];

            adfPixelScale[0] = adfGeoTransform[1];
            adfPixelScale[1] = fabs(adfGeoTransform[5]);
            adfPixelScale[2] = 0.0;

            if( !EQUAL(osProfile,"BASELINE") )
                TIFFSetField( hTIFF, TIFFTAG_GEOPIXELSCALE, 3, adfPixelScale );

            adfTiePoints[0] = 0.0;
            adfTiePoints[1] = 0.0;
            adfTiePoints[2] = 0.0;
            adfTiePoints[3] = adfGeoTransform[0];
            adfTiePoints[4] = adfGeoTransform[3];
            adfTiePoints[5] = 0.0;

            if( bPixelIsPoint && !bPointGeoIgnore )
            {
                adfTiePoints[3] += adfGeoTransform[1] * 0.5 + adfGeoTransform[2] * 0.5;
                adfTiePoints[4] += adfGeoTransform[4] * 0.5 + adfGeoTransform[5] * 0.5;
            }

            if( !EQUAL(osProfile,"BASELINE") )
                TIFFSetField( hTIFF, TIFFTAG_GEOTIEPOINTS, 6, adfTiePoints );
        }
        else
        {
            double adfMatrix[16];

            memset(adfMatrix,0,sizeof(double) * 16);

            adfMatrix[0] = adfGeoTransform[1];
            adfMatrix[1] = adfGeoTransform[2];
            adfMatrix[3] = adfGeoTransform[0];
            adfMatrix[4] = adfGeoTransform[4];
            adfMatrix[5] = adfGeoTransform[5];
            adfMatrix[7] = adfGeoTransform[3];
            adfMatrix[15] = 1.0;

            if( bPixelIsPoint && !bPointGeoIgnore )
            {
                adfMatrix[3] += adfGeoTransform[1] * 0.5 + adfGeoTransform[2] * 0.5;
                adfMatrix[7] += adfGeoTransform[4] * 0.5 + adfGeoTransform[5] * 0.5;
            }

            if( !EQUAL(osProfile,"BASELINE") )
                TIFFSetField( hTIFF, TIFFTAG_GEOTRANSMATRIX, 16, adfMatrix );
        }

        // Do we need a world file?
        if( CSLFetchBoolean( papszCreationOptions, "TFW", FALSE ) )
            GDALWriteWorldFile( osFilename, "tfw", adfGeoTransform );
        else if( CSLFetchBoolean( papszCreationOptions, "WORLDFILE", FALSE ) )
            GDALWriteWorldFile( osFilename, "wld", adfGeoTransform );
    }
    else if( GetGCPCount() > 0 )
    {
        double *padfTiePoints;
        int iGCP;

        bNeedsRewrite = TRUE;

        padfTiePoints = (double *)
            CPLMalloc( 6 * sizeof(double) * GetGCPCount() );

        for( iGCP = 0; iGCP < GetGCPCount(); iGCP++ )
        {

            padfTiePoints[iGCP*6+0] = pasGCPList[iGCP].dfGCPPixel;
            padfTiePoints[iGCP*6+1] = pasGCPList[iGCP].dfGCPLine;
            padfTiePoints[iGCP*6+2] = 0;
            padfTiePoints[iGCP*6+3] = pasGCPList[iGCP].dfGCPX;
            padfTiePoints[iGCP*6+4] = pasGCPList[iGCP].dfGCPY;
            padfTiePoints[iGCP*6+5] = pasGCPList[iGCP].dfGCPZ;

            if( bPixelIsPoint && !bPointGeoIgnore )
            {
                padfTiePoints[iGCP*6+0] += 0.5;
                padfTiePoints[iGCP*6+1] += 0.5;
            }
        }

        if( !EQUAL(osProfile,"BASELINE") )
            TIFFSetField( hTIFF, TIFFTAG_GEOTIEPOINTS,
                          6 * GetGCPCount(), padfTiePoints );
        CPLFree( padfTiePoints );
    }

/* -------------------------------------------------------------------- */
/*      Write out projection definition.                                */
/* -------------------------------------------------------------------- */
    const bool bHasProjection = (pszProjection != NULL && strlen(pszProjection) > 0);
    if( (bHasProjection || bPixelIsPoint)
        && !EQUAL(osProfile,"BASELINE") )
    {
        GTIF *psGTIF;

        bNeedsRewrite = TRUE;

        // If we have existing geokeys, try to wipe them
        // by writing a dummy geokey directory. (#2546)
        GTiffWriteDummyGeokeyDirectory(hTIFF);

        psGTIF = GTIFNew( hTIFF );

        // set according to coordinate system.
        if( bHasProjection )
        {
            GTIFSetFromOGISDefnEx( psGTIF, pszProjection, eGeoTIFFKeysFlavor );
        }

        if( bPixelIsPoint )
        {
            GTIFKeySet(psGTIF, GTRasterTypeGeoKey, TYPE_SHORT, 1,
                       RasterPixelIsPoint);
        }

        GTIFWriteKeys( psGTIF );
        GTIFFree( psGTIF );
    }
}

/************************************************************************/
/*                         AppendMetadataItem()                         */
/************************************************************************/

static void AppendMetadataItem( CPLXMLNode **ppsRoot, CPLXMLNode **ppsTail,
                                const char *pszKey, const char *pszValue,
                                int nBand, const char *pszRole,
                                const char *pszDomain )

{
    char szBandId[32];
    CPLXMLNode *psItem;

/* -------------------------------------------------------------------- */
/*      Create the Item element, and subcomponents.                     */
/* -------------------------------------------------------------------- */
    psItem = CPLCreateXMLNode( NULL, CXT_Element, "Item" );
    CPLCreateXMLNode( CPLCreateXMLNode( psItem, CXT_Attribute, "name"),
                      CXT_Text, pszKey );

    if( nBand > 0 )
    {
        snprintf( szBandId, sizeof(szBandId), "%d", nBand - 1 );
        CPLCreateXMLNode( CPLCreateXMLNode( psItem,CXT_Attribute,"sample"),
                          CXT_Text, szBandId );
    }

    if( pszRole != NULL )
        CPLCreateXMLNode( CPLCreateXMLNode( psItem,CXT_Attribute,"role"),
                          CXT_Text, pszRole );

    if( pszDomain != NULL && strlen(pszDomain) > 0 )
        CPLCreateXMLNode( CPLCreateXMLNode( psItem,CXT_Attribute,"domain"),
                          CXT_Text, pszDomain );

    char *pszEscapedItemValue = CPLEscapeString(pszValue,-1,CPLES_XML);
    CPLCreateXMLNode( psItem, CXT_Text, pszEscapedItemValue );
    CPLFree( pszEscapedItemValue );

/* -------------------------------------------------------------------- */
/*      Create root, if missing.                                        */
/* -------------------------------------------------------------------- */
    if( *ppsRoot == NULL )
        *ppsRoot = CPLCreateXMLNode( NULL, CXT_Element, "GDALMetadata" );

/* -------------------------------------------------------------------- */
/*      Append item to tail.  We keep track of the tail to avoid        */
/*      O(nsquared) time as the list gets longer.                       */
/* -------------------------------------------------------------------- */
    if( *ppsTail == NULL )
        CPLAddXMLChild( *ppsRoot, psItem );
    else
        CPLAddXMLSibling( *ppsTail, psItem );

    *ppsTail = psItem;
}

/************************************************************************/
/*                         WriteMDMetadata()                          */
/************************************************************************/

static void WriteMDMetadata( GDALMultiDomainMetadata *poMDMD, TIFF *hTIFF,
                             CPLXMLNode **ppsRoot, CPLXMLNode **ppsTail,
                             int nBand, const char *pszProfile )

{
    int iDomain;
    char **papszDomainList;

    (void) pszProfile;

/* ==================================================================== */
/*      Process each domain.                                            */
/* ==================================================================== */
    papszDomainList = poMDMD->GetDomainList();
    for( iDomain = 0; papszDomainList && papszDomainList[iDomain]; iDomain++ )
    {
        char **papszMD = poMDMD->GetMetadata( papszDomainList[iDomain] );
        int iItem;
        bool bIsXML = false;

        if( EQUAL(papszDomainList[iDomain], "IMAGE_STRUCTURE") )
            continue; // ignored
        if( EQUAL(papszDomainList[iDomain], "COLOR_PROFILE") )
            continue; // ignored
        if( EQUAL(papszDomainList[iDomain], MD_DOMAIN_RPC) )
            continue; // handled elsewhere
        if( EQUAL(papszDomainList[iDomain], "xml:ESRI")
            && CPLTestBool(CPLGetConfigOption( "ESRI_XML_PAM", "NO" )) )
            continue; // handled elsewhere

        if( STARTS_WITH_CI(papszDomainList[iDomain], "xml:") )
            bIsXML = TRUE;

/* -------------------------------------------------------------------- */
/*      Process each item in this domain.                               */
/* -------------------------------------------------------------------- */
        for( iItem = 0; papszMD && papszMD[iItem]; iItem++ )
        {
            const char *pszItemValue;
            char *pszItemName = NULL;

            if( bIsXML )
            {
                pszItemName = CPLStrdup("doc");
                pszItemValue = papszMD[iItem];
            }
            else
            {
                pszItemValue = CPLParseNameValue( papszMD[iItem], &pszItemName);
                if( pszItemName == NULL )
                {
                    CPLDebug("GTiff", "Invalid metadata item : %s", papszMD[iItem]);
                    continue;
                }
            }

/* -------------------------------------------------------------------- */
/*      Convert into XML item or handle as a special TIFF tag.          */
/* -------------------------------------------------------------------- */
            if( strlen(papszDomainList[iDomain]) == 0
                && nBand == 0 && STARTS_WITH_CI(pszItemName, "TIFFTAG_") )
            {
                if( EQUAL(pszItemName,"TIFFTAG_RESOLUTIONUNIT") ) {
                    /* ResolutionUnit can't be 0, which is the default if atoi() fails.
                       Set to 1=Unknown */
                    int v = atoi(pszItemValue);
                    if (!v) v = RESUNIT_NONE;
                    TIFFSetField( hTIFF, TIFFTAG_RESOLUTIONUNIT, v);
                }
                else
                {
                    int bFoundTag = FALSE;
                    size_t iTag;
                    for(iTag=0;iTag<sizeof(asTIFFTags)/sizeof(asTIFFTags[0]);iTag++)
                    {
                        if( EQUAL(pszItemName, asTIFFTags[iTag].pszTagName) )
                        {
                            bFoundTag = TRUE;
                            break;
                        }
                    }

                    if( bFoundTag && asTIFFTags[iTag].eType == GTIFFTAGTYPE_STRING )
                        TIFFSetField( hTIFF, asTIFFTags[iTag].nTagVal, pszItemValue );
                    else if( bFoundTag && asTIFFTags[iTag].eType == GTIFFTAGTYPE_FLOAT )
                        TIFFSetField( hTIFF, asTIFFTags[iTag].nTagVal, CPLAtof(pszItemValue) );
                    else if( bFoundTag && asTIFFTags[iTag].eType == GTIFFTAGTYPE_SHORT )
                        TIFFSetField( hTIFF, asTIFFTags[iTag].nTagVal, atoi(pszItemValue) );
                    else
                        CPLError(CE_Warning, CPLE_NotSupported,
                                "%s metadata item is unhandled and will not be written",
                                pszItemName);
                }
            }
            else if( nBand == 0 && EQUAL(pszItemName,GDALMD_AREA_OR_POINT) )
                /* do nothing, handled elsewhere */;
            else
                AppendMetadataItem( ppsRoot, ppsTail,
                                    pszItemName, pszItemValue,
                                    nBand, NULL, papszDomainList[iDomain] );

            CPLFree( pszItemName );
        }

/* -------------------------------------------------------------------- */
/*      Remove TIFFTAG_xxxxxx that are already set but no longer in     */
/*      the metadata list (#5619)                                       */
/* -------------------------------------------------------------------- */
        if( strlen(papszDomainList[iDomain]) == 0 && nBand == 0 )
        {
            size_t iTag;
            for(iTag=0;iTag<sizeof(asTIFFTags)/sizeof(asTIFFTags[0]);iTag++)
            {
                char* pszText = NULL;
                int16 nVal = 0;
                float fVal = 0.0f;
                const char* pszVal = CSLFetchNameValue(papszMD, asTIFFTags[iTag].pszTagName);
                if( pszVal == NULL &&
                    ((asTIFFTags[iTag].eType == GTIFFTAGTYPE_STRING && TIFFGetField( hTIFF, asTIFFTags[iTag].nTagVal, &pszText )) ||
                     (asTIFFTags[iTag].eType == GTIFFTAGTYPE_SHORT && TIFFGetField( hTIFF, asTIFFTags[iTag].nTagVal, &nVal )) ||
                     (asTIFFTags[iTag].eType == GTIFFTAGTYPE_FLOAT && TIFFGetField( hTIFF, asTIFFTags[iTag].nTagVal, &fVal ))) )
                {
#ifdef HAVE_UNSETFIELD
                    TIFFUnsetField( hTIFF, asTIFFTags[iTag].nTagVal );
#else
                    if( asTIFFTags[iTag].eType == GTIFFTAGTYPE_STRING )
                    {
                        TIFFSetField( hTIFF, asTIFFTags[iTag].nTagVal, "" );
                    }
#endif
                }
            }
        }

    }
}

/************************************************************************/
/*                           WriteRPC()                                 */
/************************************************************************/

void GTiffDataset::WriteRPC( GDALDataset *poSrcDS, TIFF *hTIFF,
                             int bSrcIsGeoTIFF,
                             const char *pszProfile,
                             const char *pszTIFFFilename,
                             char **papszCreationOptions,
                             int bWriteOnlyInPAMIfNeeded )
{

/* -------------------------------------------------------------------- */
/*      Handle RPC data written to an RPB file.                         */
/* -------------------------------------------------------------------- */
    char **papszRPCMD = poSrcDS->GetMetadata(MD_DOMAIN_RPC);
    if( papszRPCMD != NULL )
    {
        int bRPCSerializedOtherWay = FALSE;

        if( EQUAL(pszProfile,"GDALGeoTIFF") )
        {
            if( !bWriteOnlyInPAMIfNeeded )
                GTiffDatasetWriteRPCTag( hTIFF, papszRPCMD );
            bRPCSerializedOtherWay = TRUE;
        }

        /* Write RPB file if explicitly asked, or if a non GDAL specific */
        /* profile is selected and RPCTXT is not asked */
        int bRPBExplicitlyAsked = CSLFetchBoolean( papszCreationOptions, "RPB", FALSE );
        int bRPBExplicitlyDenied = !CSLFetchBoolean( papszCreationOptions, "RPB", TRUE );
        if( (!EQUAL(pszProfile,"GDALGeoTIFF") &&
             !CSLFetchBoolean( papszCreationOptions, "RPCTXT", FALSE ) &&
             !bRPBExplicitlyDenied )
            || bRPBExplicitlyAsked )
        {
            if( !bWriteOnlyInPAMIfNeeded )
                GDALWriteRPBFile( pszTIFFFilename, papszRPCMD );
            bRPCSerializedOtherWay = TRUE;
        }

        if( CSLFetchBoolean( papszCreationOptions, "RPCTXT", FALSE ) )
        {
            if( !bWriteOnlyInPAMIfNeeded )
                GDALWriteRPCTXTFile( pszTIFFFilename, papszRPCMD );
            bRPCSerializedOtherWay = TRUE;
        }

        if( !bRPCSerializedOtherWay && bWriteOnlyInPAMIfNeeded && bSrcIsGeoTIFF )
            ((GTiffDataset*)poSrcDS)->GDALPamDataset::SetMetadata(papszRPCMD, MD_DOMAIN_RPC);
    }
}

/************************************************************************/
/*                           WriteMetadata()                            */
/************************************************************************/

int  GTiffDataset::WriteMetadata( GDALDataset *poSrcDS, TIFF *hTIFF,
                                  int bSrcIsGeoTIFF,
                                  const char *pszProfile,
                                  const char *pszTIFFFilename,
                                  char **papszCreationOptions,
                                  int bExcludeRPBandIMGFileWriting)

{
/* -------------------------------------------------------------------- */
/*      Convert all the remaining metadata into a simple XML            */
/*      format.                                                         */
/* -------------------------------------------------------------------- */
    CPLXMLNode *psRoot = NULL, *psTail = NULL;

    if( bSrcIsGeoTIFF )
    {
        WriteMDMetadata( &(((GTiffDataset *)poSrcDS)->oGTiffMDMD),
                         hTIFF, &psRoot, &psTail, 0, pszProfile );
    }
    else
    {
        char **papszMD = poSrcDS->GetMetadata();

        if( CSLCount(papszMD) > 0 )
        {
            GDALMultiDomainMetadata oMDMD;
            oMDMD.SetMetadata( papszMD );

            WriteMDMetadata( &oMDMD, hTIFF, &psRoot, &psTail, 0, pszProfile );
        }
    }

    if( !bExcludeRPBandIMGFileWriting )
    {
        WriteRPC(poSrcDS, hTIFF, bSrcIsGeoTIFF,
                 pszProfile, pszTIFFFilename,
                 papszCreationOptions);

/* -------------------------------------------------------------------- */
/*      Handle metadata data written to an IMD file.                    */
/* -------------------------------------------------------------------- */
        char **papszIMDMD = poSrcDS->GetMetadata(MD_DOMAIN_IMD);
        if( papszIMDMD != NULL )
        {
            GDALWriteIMDFile( pszTIFFFilename, papszIMDMD );
        }
    }
/* -------------------------------------------------------------------- */
/*      We also need to address band specific metadata, and special     */
/*      "role" metadata.                                                */
/* -------------------------------------------------------------------- */
    int nBand;
    for( nBand = 1; nBand <= poSrcDS->GetRasterCount(); nBand++ )
    {
        GDALRasterBand *poBand = poSrcDS->GetRasterBand( nBand );

        if( bSrcIsGeoTIFF )
        {
            WriteMDMetadata( &(((GTiffRasterBand *)poBand)->oGTiffMDMD),
                             hTIFF, &psRoot, &psTail, nBand, pszProfile );
        }
        else
        {
            char **papszMD = poBand->GetMetadata();

            if( CSLCount(papszMD) > 0 )
            {
                GDALMultiDomainMetadata oMDMD;
                oMDMD.SetMetadata( papszMD );

                WriteMDMetadata( &oMDMD, hTIFF, &psRoot, &psTail, nBand,
                                 pszProfile );
            }
        }

        double dfOffset = poBand->GetOffset();
        double dfScale = poBand->GetScale();

        if( dfOffset != 0.0 || dfScale != 1.0 )
        {
            char szValue[128];

            CPLsnprintf( szValue, sizeof(szValue), "%.18g", dfOffset );
            AppendMetadataItem( &psRoot, &psTail, "OFFSET", szValue, nBand,
                                "offset", "" );
            CPLsnprintf( szValue, sizeof(szValue), "%.18g", dfScale );
            AppendMetadataItem( &psRoot, &psTail, "SCALE", szValue, nBand,
                                "scale", "" );
        }

        const char* pszUnitType = poBand->GetUnitType();
        if (pszUnitType != NULL && pszUnitType[0] != '\0')
            AppendMetadataItem( &psRoot, &psTail, "UNITTYPE", pszUnitType, nBand,
                                "unittype", "" );


        if (strlen(poBand->GetDescription()) > 0)
        {
            AppendMetadataItem( &psRoot, &psTail, "DESCRIPTION",
                                poBand->GetDescription(), nBand,
                                "description", "" );
        }
    }

/* -------------------------------------------------------------------- */
/*      Write out the generic XML metadata if there is any.             */
/* -------------------------------------------------------------------- */
    if( psRoot != NULL )
    {
        int bRet = TRUE;

        if( EQUAL(pszProfile,"GDALGeoTIFF") )
        {
            char *pszXML_MD = CPLSerializeXMLTree( psRoot );
            if( strlen(pszXML_MD) > 32000 )
            {
                if( bSrcIsGeoTIFF )
                {
                    if( ((GTiffDataset *) poSrcDS)->GetPamFlags() & GPF_DISABLED )
                    {
                        CPLError( CE_Warning, CPLE_AppDefined,
                            "Metadata exceeding 32000 bytes cannot be written into GeoTIFF." );
                    }
                    else
                    {
                        ((GTiffDataset *) poSrcDS)->PushMetadataToPam();
                        CPLError( CE_Warning, CPLE_AppDefined,
                            "Metadata exceeding 32000 bytes cannot be written into GeoTIFF. Transferred to PAM instead." );
                    }
                }
                else
                {
                    bRet = FALSE;
                }
            }
            else
            {
                TIFFSetField( hTIFF, TIFFTAG_GDAL_METADATA, pszXML_MD );
            }
            CPLFree( pszXML_MD );
        }
        else
        {
            if( bSrcIsGeoTIFF )
                ((GTiffDataset *) poSrcDS)->PushMetadataToPam();
            else
                bRet = FALSE;
        }

        CPLDestroyXMLNode( psRoot );

        return bRet;
    }
    else
    {
        /* If we have no more metadata but it existed before, remove the GDAL_METADATA tag */
        if( EQUAL(pszProfile,"GDALGeoTIFF") )
        {
            char* pszText = NULL;
            if( TIFFGetField( hTIFF, TIFFTAG_GDAL_METADATA, &pszText ) )
            {
#ifdef HAVE_UNSETFIELD
                TIFFUnsetField( hTIFF, TIFFTAG_GDAL_METADATA );
#else
                TIFFSetField( hTIFF, TIFFTAG_GDAL_METADATA, "" );
#endif
            }
        }
    }

    return TRUE;
}

/************************************************************************/
/*                         PushMetadataToPam()                          */
/*                                                                      */
/*      When producing a strict profile TIFF or if our aggregate        */
/*      metadata is too big for a single tiff tag we may end up         */
/*      needing to write it via the PAM mechanisms.  This method        */
/*      copies all the appropriate metadata into the PAM level          */
/*      metadata object but with special care to avoid copying          */
/*      metadata handled in other ways in TIFF format.                  */
/************************************************************************/

void GTiffDataset::PushMetadataToPam()

{
    int nBand;
    for( nBand = 0; nBand <= GetRasterCount(); nBand++ )
    {
        GDALMultiDomainMetadata *poSrcMDMD;
        GTiffRasterBand *poBand = NULL;

        if( nBand == 0 )
            poSrcMDMD = &(this->oGTiffMDMD);
        else
        {
            poBand = (GTiffRasterBand *) GetRasterBand(nBand);
            poSrcMDMD = &(poBand->oGTiffMDMD);
        }

/* -------------------------------------------------------------------- */
/*      Loop over the available domains.                                */
/* -------------------------------------------------------------------- */
        int iDomain, i;
        char **papszDomainList;

        papszDomainList = poSrcMDMD->GetDomainList();
        for( iDomain = 0;
             papszDomainList && papszDomainList[iDomain];
             iDomain++ )
        {
            char **papszMD = poSrcMDMD->GetMetadata( papszDomainList[iDomain] );

            if( EQUAL(papszDomainList[iDomain],MD_DOMAIN_RPC)
                || EQUAL(papszDomainList[iDomain],MD_DOMAIN_IMD)
                || EQUAL(papszDomainList[iDomain],"_temporary_")
                || EQUAL(papszDomainList[iDomain],"IMAGE_STRUCTURE")
                || EQUAL(papszDomainList[iDomain],"COLOR_PROFILE") )
                continue;

            papszMD = CSLDuplicate(papszMD);

            for( i = CSLCount(papszMD)-1; i >= 0; i-- )
            {
                if( STARTS_WITH_CI(papszMD[i], "TIFFTAG_")
                    || EQUALN(papszMD[i],GDALMD_AREA_OR_POINT,
                              strlen(GDALMD_AREA_OR_POINT)) )                    papszMD = CSLRemoveStrings( papszMD, i, 1, NULL );            }

            if( nBand == 0 )
                GDALPamDataset::SetMetadata( papszMD, papszDomainList[iDomain]);
            else
                poBand->GDALPamRasterBand::SetMetadata( papszMD, papszDomainList[iDomain]);

            CSLDestroy( papszMD );
        }

/* -------------------------------------------------------------------- */
/*      Handle some "special domain" stuff.                             */
/* -------------------------------------------------------------------- */
        if( poBand != NULL )
        {
            poBand->GDALPamRasterBand::SetOffset( poBand->GetOffset() );
            poBand->GDALPamRasterBand::SetScale( poBand->GetScale() );
            poBand->GDALPamRasterBand::SetUnitType( poBand->GetUnitType() );
            poBand->GDALPamRasterBand::SetDescription( poBand->GetDescription() );
        }
    }
}

/************************************************************************/
/*                     GTiffDatasetWriteRPCTag()                        */
/*                                                                      */
/*      Format a TAG according to:                                      */
/*                                                                      */
/*      http://geotiff.maptools.org/rpc_prop.html                       */
/************************************************************************/

void GTiffDatasetWriteRPCTag( TIFF *hTIFF, char **papszRPCMD )

{
    double adfRPCTag[92];
    GDALRPCInfo sRPC;

    if( !GDALExtractRPCInfo( papszRPCMD, &sRPC ) )
        return;

    adfRPCTag[0] = -1.0;  // Error Bias
    adfRPCTag[1] = -1.0;  // Error Random

    adfRPCTag[2] = sRPC.dfLINE_OFF;
    adfRPCTag[3] = sRPC.dfSAMP_OFF;
    adfRPCTag[4] = sRPC.dfLAT_OFF;
    adfRPCTag[5] = sRPC.dfLONG_OFF;
    adfRPCTag[6] = sRPC.dfHEIGHT_OFF;
    adfRPCTag[7] = sRPC.dfLINE_SCALE;
    adfRPCTag[8] = sRPC.dfSAMP_SCALE;
    adfRPCTag[9] = sRPC.dfLAT_SCALE;
    adfRPCTag[10] = sRPC.dfLONG_SCALE;
    adfRPCTag[11] = sRPC.dfHEIGHT_SCALE;

    memcpy( adfRPCTag + 12, sRPC.adfLINE_NUM_COEFF, sizeof(double) * 20 );
    memcpy( adfRPCTag + 32, sRPC.adfLINE_DEN_COEFF, sizeof(double) * 20 );
    memcpy( adfRPCTag + 52, sRPC.adfSAMP_NUM_COEFF, sizeof(double) * 20 );
    memcpy( adfRPCTag + 72, sRPC.adfSAMP_DEN_COEFF, sizeof(double) * 20 );

    TIFFSetField( hTIFF, TIFFTAG_RPCCOEFFICIENT, 92, adfRPCTag );
}

/************************************************************************/
/*                             ReadRPCTag()                             */
/*                                                                      */
/*      Format a TAG according to:                                      */
/*                                                                      */
/*      http://geotiff.maptools.org/rpc_prop.html                       */
/************************************************************************/

char** GTiffDatasetReadRPCTag(TIFF* hTIFF)

{
    double *padfRPCTag;
    CPLString osField;
    CPLString osMultiField;
    CPLStringList asMD;
    int i;
    uint16 nCount;

    if( !TIFFGetField( hTIFF, TIFFTAG_RPCCOEFFICIENT, &nCount, &padfRPCTag )
        || nCount != 92 )
        return NULL;

    asMD.SetNameValue(RPC_LINE_OFF, CPLOPrintf("%.15g", padfRPCTag[2]));
    asMD.SetNameValue(RPC_SAMP_OFF, CPLOPrintf("%.15g", padfRPCTag[3]));
    asMD.SetNameValue(RPC_LAT_OFF, CPLOPrintf("%.15g", padfRPCTag[4]));
    asMD.SetNameValue(RPC_LONG_OFF, CPLOPrintf("%.15g", padfRPCTag[5]));
    asMD.SetNameValue(RPC_HEIGHT_OFF, CPLOPrintf("%.15g", padfRPCTag[6]));
    asMD.SetNameValue(RPC_LINE_SCALE, CPLOPrintf("%.15g", padfRPCTag[7]));
    asMD.SetNameValue(RPC_SAMP_SCALE, CPLOPrintf("%.15g", padfRPCTag[8]));
    asMD.SetNameValue(RPC_LAT_SCALE, CPLOPrintf("%.15g", padfRPCTag[9]));
    asMD.SetNameValue(RPC_LONG_SCALE, CPLOPrintf("%.15g", padfRPCTag[10]));
    asMD.SetNameValue(RPC_HEIGHT_SCALE, CPLOPrintf("%.15g", padfRPCTag[11]));

    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", padfRPCTag[12+i] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    asMD.SetNameValue(RPC_LINE_NUM_COEFF, osMultiField );

    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", padfRPCTag[32+i] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    asMD.SetNameValue( RPC_LINE_DEN_COEFF, osMultiField );

    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", padfRPCTag[52+i] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    asMD.SetNameValue( RPC_SAMP_NUM_COEFF, osMultiField );

    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", padfRPCTag[72+i] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    asMD.SetNameValue( RPC_SAMP_DEN_COEFF, osMultiField );

    return asMD.StealList();
}

/************************************************************************/
/*                         WriteNoDataValue()                           */
/************************************************************************/

void GTiffDataset::WriteNoDataValue( TIFF *hTIFF, double dfNoData )

{
    char szVal[400];
    if (CPLIsNan(dfNoData))
        strcpy(szVal, "nan");
    else
        CPLsnprintf(szVal, sizeof(szVal), "%.18g", dfNoData);
    TIFFSetField( hTIFF, TIFFTAG_GDAL_NODATA, szVal );
}

/************************************************************************/
/*                         UnsetNoDataValue()                           */
/************************************************************************/

void GTiffDataset::UnsetNoDataValue( TIFF *hTIFF )

{
#ifdef HAVE_UNSETFIELD
    TIFFUnsetField( hTIFF, TIFFTAG_GDAL_NODATA );
#else
    TIFFSetField( hTIFF, TIFFTAG_GDAL_NODATA, "" );
#endif
}

/************************************************************************/
/*                            SetDirectory()                            */
/************************************************************************/

int GTiffDataset::SetDirectory( toff_t nNewOffset )

{
    Crystalize();

    if( nNewOffset == 0 )
        nNewOffset = nDirOffset;

    if( TIFFCurrentDirOffset(hTIFF) == nNewOffset )
    {
        CPLAssert( *ppoActiveDSRef == this || *ppoActiveDSRef == NULL );
        *ppoActiveDSRef = this;
        return TRUE;
    }

    if( GetAccess() == GA_Update )
    {
        if( *ppoActiveDSRef != NULL )
            (*ppoActiveDSRef)->FlushDirectory();
    }

    if( nNewOffset == 0)
        return TRUE;

    (*ppoActiveDSRef) = this;

    int nSetDirResult = TIFFSetSubDirectory( hTIFF, nNewOffset );
    if (!nSetDirResult)
        return nSetDirResult;

/* -------------------------------------------------------------------- */
/*      YCbCr JPEG compressed images should be translated on the fly    */
/*      to RGB by libtiff/libjpeg unless specifically requested         */
/*      otherwise.                                                      */
/* -------------------------------------------------------------------- */
    if( !TIFFGetField( hTIFF, TIFFTAG_COMPRESSION, &(nCompression) ) )
        nCompression = COMPRESSION_NONE;

    if( !TIFFGetField( hTIFF, TIFFTAG_PHOTOMETRIC, &(nPhotometric) ) )
        nPhotometric = PHOTOMETRIC_MINISBLACK;

    if( nCompression == COMPRESSION_JPEG
        && nPhotometric == PHOTOMETRIC_YCBCR
        && CPLTestBool( CPLGetConfigOption("CONVERT_YCBCR_TO_RGB",
                                              "YES") ) )
    {
        int nColorMode;

        TIFFGetField( hTIFF, TIFFTAG_JPEGCOLORMODE, &nColorMode );
        if( nColorMode != JPEGCOLORMODE_RGB )
            TIFFSetField(hTIFF, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
    }

/* -------------------------------------------------------------------- */
/*      Propagate any quality settings.                                 */
/* -------------------------------------------------------------------- */
    if( GetAccess() == GA_Update )
    {
        // Now, reset zip and jpeg quality.
        if(nJpegQuality > 0 && nCompression == COMPRESSION_JPEG)
        {
            CPLDebug( "GTiff", "Propagate JPEG_QUALITY(%d) in SetDirectory()",
                      nJpegQuality );
            TIFFSetField(hTIFF, TIFFTAG_JPEGQUALITY, nJpegQuality);
        }
        if(nJpegTablesMode >= 0 && nCompression == COMPRESSION_JPEG)
            TIFFSetField(hTIFF, TIFFTAG_JPEGTABLESMODE, nJpegTablesMode);
        if(nZLevel > 0 && nCompression == COMPRESSION_ADOBE_DEFLATE)
            TIFFSetField(hTIFF, TIFFTAG_ZIPQUALITY, nZLevel);
        if(nLZMAPreset > 0 && nCompression == COMPRESSION_LZMA)
            TIFFSetField(hTIFF, TIFFTAG_LZMAPRESET, nLZMAPreset);
    }

    return nSetDirResult;
}

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/

int GTiffDataset::Identify( GDALOpenInfo * poOpenInfo )

{
    const char  *pszFilename = poOpenInfo->pszFilename;
    if( STARTS_WITH_CI(pszFilename, "GTIFF_RAW:") )
    {
        pszFilename += strlen("GTIFF_RAW:");
        GDALOpenInfo oOpenInfo( pszFilename, poOpenInfo->eAccess );
        return Identify(&oOpenInfo);
    }

/* -------------------------------------------------------------------- */
/*      We have a special hook for handling opening a specific          */
/*      directory of a TIFF file.                                       */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszFilename, "GTIFF_DIR:") )
        return TRUE;

/* -------------------------------------------------------------------- */
/*      First we check to see if the file has the expected header       */
/*      bytes.                                                          */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->fpL == NULL || poOpenInfo->nHeaderBytes < 2 )
        return FALSE;

    if( (poOpenInfo->pabyHeader[0] != 'I' || poOpenInfo->pabyHeader[1] != 'I')
        && (poOpenInfo->pabyHeader[0] != 'M' || poOpenInfo->pabyHeader[1] != 'M'))
        return FALSE;

#ifndef BIGTIFF_SUPPORT
    if( (poOpenInfo->pabyHeader[2] == 0x2B && poOpenInfo->pabyHeader[3] == 0) ||
        (poOpenInfo->pabyHeader[2] == 0 && poOpenInfo->pabyHeader[3] == 0x2B) )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "This is a BigTIFF file.  BigTIFF is not supported by this\n"
                  "version of GDAL and libtiff." );
        return FALSE;
    }
#endif

    if( (poOpenInfo->pabyHeader[2] != 0x2A || poOpenInfo->pabyHeader[3] != 0)
        && (poOpenInfo->pabyHeader[3] != 0x2A || poOpenInfo->pabyHeader[2] != 0)
        && (poOpenInfo->pabyHeader[2] != 0x2B || poOpenInfo->pabyHeader[3] != 0)
        && (poOpenInfo->pabyHeader[3] != 0x2B || poOpenInfo->pabyHeader[2] != 0))
        return FALSE;

    return TRUE;
}

/************************************************************************/
/*                            GTIFFErrorHandler()                       */
/************************************************************************/

class GTIFFErrorStruct CPL_FINAL
{
public:
    CPLErr type;
    CPLErrorNum no;
    CPLString msg;

        GTIFFErrorStruct() : type(CE_None), no(CPLE_None) {}
        GTIFFErrorStruct(CPLErr eErrIn, CPLErrorNum noIn, const char* msgIn) :
            type(eErrIn), no(noIn), msg(msgIn) {}
};

static void CPL_STDCALL GTIFFErrorHandler(CPLErr eErr, CPLErrorNum no, const char* msg)
{
    std::vector<GTIFFErrorStruct>* paoErrors =
        (std::vector<GTIFFErrorStruct>*) CPLGetErrorHandlerUserData();
    paoErrors->push_back(GTIFFErrorStruct(eErr, no, msg));
}

/************************************************************************/
/*                          GTIFFExtendMemoryFile()                     */
/************************************************************************/

static int GTIFFExtendMemoryFile(const CPLString& osTmpFilename,
                                 VSILFILE* fpTemp,
                                 VSILFILE* fpL,
                                 int nNewLength,
                                 GByte*& pabyBuffer,
                                 vsi_l_offset& nDataLength)
{
    if( nNewLength <= (int)nDataLength )
        return TRUE;
    if( VSIFSeekL(fpTemp, nNewLength - 1, SEEK_SET) != 0 )
        return FALSE;
    char ch = 0;
    if( VSIFWriteL(&ch, 1, 1, fpTemp) != 1 )
        return FALSE;
    int nOldDataLength = static_cast<int>(nDataLength);
    pabyBuffer = (GByte*)VSIGetMemFileBuffer( osTmpFilename, &nDataLength, FALSE);
    int nToRead = nNewLength - nOldDataLength;
    int nRead = (int)VSIFReadL( pabyBuffer + nOldDataLength, 1, nToRead, fpL);
    if( nRead != nToRead )
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Needed to read %d bytes. Only %d got", nToRead, nRead);
        return FALSE;
    }
    return TRUE;
}

/************************************************************************/
/*                         GTIFFMakeBufferedStream()                    */
/************************************************************************/

static int GTIFFMakeBufferedStream(GDALOpenInfo* poOpenInfo)
{
    CPLString osTmpFilename;
    static int nCounter = 0;
    osTmpFilename.Printf("/vsimem/stream_%d.tif", ++nCounter);
    VSILFILE* fpTemp = VSIFOpenL(osTmpFilename, "wb+");
    if( fpTemp == NULL )
        return FALSE;
    /* The seek is needed for /vsistdin/ that has some rewind capabilities */
    if( VSIFSeekL(poOpenInfo->fpL, poOpenInfo->nHeaderBytes, SEEK_SET) != 0 )
    {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
        return FALSE;
    }
    CPLAssert( (int)VSIFTellL(poOpenInfo->fpL) == poOpenInfo->nHeaderBytes );
    if( VSIFWriteL(poOpenInfo->pabyHeader, poOpenInfo->nHeaderBytes, 1, fpTemp) != 1 )
    {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
        return FALSE;
    }
    vsi_l_offset nDataLength;
    GByte* pabyBuffer = (GByte*)VSIGetMemFileBuffer( osTmpFilename, &nDataLength, FALSE);
    int bLittleEndian = (pabyBuffer[0] == 'I');
    int bSwap = (bLittleEndian && !CPL_IS_LSB) || (!bLittleEndian && CPL_IS_LSB);
    int bBigTIFF = ( pabyBuffer[2] == 43 || pabyBuffer[3] == 43 );
    vsi_l_offset nMaxOffset = 0;
    if( bBigTIFF )
    {
        GUIntBig nTmp;
        memcpy(&nTmp, pabyBuffer + 8, 8);
        if( bSwap ) CPL_SWAP64PTR(&nTmp);
        if( nTmp != 16 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "IFD start should be at offset 16 for a streamed BigTIFF");
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
            VSIUnlink(osTmpFilename);
            return FALSE;
        }
        memcpy(&nTmp, pabyBuffer + 16, 8);
        if( bSwap ) CPL_SWAP64PTR(&nTmp);
        if( nTmp > 1024 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Too many tags : " CPL_FRMT_GIB, nTmp);
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
            VSIUnlink(osTmpFilename);
            return FALSE;
        }
        int nTags = static_cast<int>(nTmp);
        int nSpaceForTags = nTags * 20;
        if( !GTIFFExtendMemoryFile(osTmpFilename, fpTemp, poOpenInfo->fpL,
                                    24 + nSpaceForTags,
                                    pabyBuffer, nDataLength) )
        {
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
            VSIUnlink(osTmpFilename);
            return FALSE;
        }
        nMaxOffset = 24 + nSpaceForTags + 8;
        for(int i=0;i<nTags;i++)
        {
            GUInt16 nTmp16;
            memcpy(&nTmp16, pabyBuffer + 24 + i * 20, 2);
            if( bSwap ) CPL_SWAP16PTR(&nTmp16);
            int nTag = nTmp16;
            memcpy(&nTmp16, pabyBuffer + 24 + i * 20 + 2, 2);
            if( bSwap ) CPL_SWAP16PTR(&nTmp16);
            int nDataType = nTmp16;
            memcpy(&nTmp, pabyBuffer + 24 + i * 20 + 4, 8);
            if( bSwap ) CPL_SWAP64PTR(&nTmp);
            if( nTmp >= 16 * 1024 * 1024 )
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "Too many elements for tag %d : " CPL_FRMT_GUIB, nTag, nTmp);
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
                VSIUnlink(osTmpFilename);
                return FALSE;
            }
            GUInt32 nCount = (GUInt32)nTmp;
            GUInt32 nTagSize = TIFFDataWidth((TIFFDataType)nDataType) * nCount;
            if( nTagSize > 8 )
            {
                memcpy(&nTmp, pabyBuffer + 24 + i * 20 + 12, 8);
                if( bSwap ) CPL_SWAP64PTR(&nTmp);
                if( nTmp > (GUIntBig)((((GIntBig)INT_MAX) << 32) - nTagSize) )
                {
                    CPLError(CE_Failure, CPLE_NotSupported,
                             "Overflow with tag %d", nTag);
                    CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
                    VSIUnlink(osTmpFilename);
                    return FALSE;
                }
                if( (vsi_l_offset)(nTmp + nTagSize) > nMaxOffset )
                    nMaxOffset = nTmp + nTagSize;
            }
        }
    }
    else
    {
        GUInt32 nTmp;
        memcpy(&nTmp, pabyBuffer + 4, 4);
        if( bSwap ) CPL_SWAP32PTR(&nTmp);
        if( nTmp != 8 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "IFD start should be at offset 8 for a streamed TIFF");
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
            VSIUnlink(osTmpFilename);
            return FALSE;
        }
        GUInt16 nTmp16;
        memcpy(&nTmp16, pabyBuffer + 8, 2);
        if( bSwap ) CPL_SWAP16PTR(&nTmp16);
        if( nTmp16 > 1024 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Too many tags : %d", nTmp16);
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
            VSIUnlink(osTmpFilename);
            return FALSE;
        }
        int nTags = nTmp16;
        int nSpaceForTags = nTags * 12;
        if( !GTIFFExtendMemoryFile(osTmpFilename, fpTemp, poOpenInfo->fpL,
                                    10 + nSpaceForTags,
                                    pabyBuffer, nDataLength) )
        {
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
            VSIUnlink(osTmpFilename);
            return FALSE;
        }
        nMaxOffset = 10 + nSpaceForTags + 4;
        for(int i=0;i<nTags;i++)
        {
            memcpy(&nTmp16, pabyBuffer + 10 + i * 12, 2);
            if( bSwap ) CPL_SWAP16PTR(&nTmp16);
            int nTag = nTmp16;
            memcpy(&nTmp16, pabyBuffer + 10 + i * 12 + 2, 2);
            if( bSwap ) CPL_SWAP16PTR(&nTmp16);
            int nDataType = nTmp16;
            memcpy(&nTmp, pabyBuffer + 10 + i * 12 + 4, 4);
            if( bSwap ) CPL_SWAP32PTR(&nTmp);
            if( nTmp >= 16 * 1024 * 1024 )
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "Too many elements for tag %d : %u", nTag, nTmp);
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
                VSIUnlink(osTmpFilename);
                return FALSE;
            }
            GUInt32 nCount = nTmp;
            GUInt32 nTagSize = TIFFDataWidth((TIFFDataType)nDataType) * nCount;
            if( nTagSize > 4 )
            {
                memcpy(&nTmp, pabyBuffer + 10 + i * 12 + 8, 4);
                if( bSwap ) CPL_SWAP32PTR(&nTmp);
                if( nTmp > (GUInt32)(0xFFFFFFFFU - nTagSize) )
                {
                    CPLError(CE_Failure, CPLE_NotSupported,
                             "Overflow with tag %d", nTag);
                    CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
                    VSIUnlink(osTmpFilename);
                    return FALSE;
                }
                if( nTmp + nTagSize > nMaxOffset )
                    nMaxOffset = nTmp + nTagSize;
            }
        }
    }
    if( nMaxOffset > 10 * 1024 * 1024 )
    {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
        VSIUnlink(osTmpFilename);
        return FALSE;
    }
    if( !GTIFFExtendMemoryFile(osTmpFilename, fpTemp, poOpenInfo->fpL,
                               static_cast<int>(nMaxOffset), pabyBuffer, nDataLength) )
    {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpTemp));
        VSIUnlink(osTmpFilename);
        return FALSE;
    }
    CPLAssert(nDataLength == VSIFTellL(poOpenInfo->fpL));
    poOpenInfo->fpL = (VSILFILE*)VSICreateBufferedReaderHandle(
        (VSIVirtualHandle*)poOpenInfo->fpL, pabyBuffer, ((vsi_l_offset)INT_MAX) << 32 );
    if( VSIFCloseL(fpTemp) != 0 )
        return FALSE;
    VSIUnlink(osTmpFilename);

    return TRUE;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *GTiffDataset::Open( GDALOpenInfo * poOpenInfo )

{
    TIFF *hTIFF;
    int          bAllowRGBAInterface = TRUE;
    const char  *pszFilename = poOpenInfo->pszFilename;

/* -------------------------------------------------------------------- */
/*      Check if it looks like a TIFF file.                             */
/* -------------------------------------------------------------------- */
    if (!Identify(poOpenInfo))
        return NULL;

    if( STARTS_WITH_CI(pszFilename, "GTIFF_RAW:") )
    {
        bAllowRGBAInterface = FALSE;
        pszFilename +=  strlen("GTIFF_RAW:");
    }

/* -------------------------------------------------------------------- */
/*      We have a special hook for handling opening a specific          */
/*      directory of a TIFF file.                                       */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszFilename, "GTIFF_DIR:") )
        return OpenDir( poOpenInfo );

    if (!GTiffOneTimeInit())
        return NULL;

/* -------------------------------------------------------------------- */
/*      Try opening the dataset.                                        */
/* -------------------------------------------------------------------- */

    /* Disable strip chop for now */
    int bStreaming = FALSE;
    const char* pszReadStreaming = CPLGetConfigOption("TIFF_READ_STREAMING", NULL);
    if( poOpenInfo->fpL == NULL )
    {
        poOpenInfo->fpL = VSIFOpenL( pszFilename, ( poOpenInfo->eAccess == GA_ReadOnly ) ? "rb" : "r+b" );
        if( poOpenInfo->fpL == NULL )
            return NULL;
    }
    else if( !(pszReadStreaming && !CPLTestBool(pszReadStreaming)) &&
             poOpenInfo->nHeaderBytes >= 24 &&
             ((int)VSIFTellL(poOpenInfo->fpL) == poOpenInfo->nHeaderBytes || /* A pipe has no seeking capability, so its position is 0 despite having read bytes */
              strcmp(pszFilename, "/vsistdin/") == 0 ||
              //STARTS_WITH(pszFilename, "/vsicurl_streaming/") ||
              (pszReadStreaming && CPLTestBool(pszReadStreaming))) )
    {
        bStreaming = TRUE;
        if( !GTIFFMakeBufferedStream(poOpenInfo) )
            return NULL;
    }

    /* Store errors/warnings and emit them later */
    std::vector<GTIFFErrorStruct> aoErrors;
    CPLPushErrorHandlerEx(GTIFFErrorHandler, &aoErrors);
    CPLSetCurrentErrorHandlerCatchDebug( FALSE );
    hTIFF = VSI_TIFFOpen( pszFilename, ( poOpenInfo->eAccess == GA_ReadOnly ) ? "rc" : "r+c",
                          poOpenInfo->fpL );
    CPLPopErrorHandler();
#if SIZEOF_VOIDP == 4
    if( hTIFF == NULL )
    {
        /* Case of one-strip file where the strip size is > 2GB (#5403) */
        if( bGlobalStripIntegerOverflow )
        {
            hTIFF = VSI_TIFFOpen( pszFilename, ( poOpenInfo->eAccess == GA_ReadOnly ) ? "r" : "r+",
                                  poOpenInfo->fpL );
            bGlobalStripIntegerOverflow = FALSE;
        }
    }
    else
    {
        bGlobalStripIntegerOverflow = FALSE;
    }
#endif

    /* Now emit errors and change their criticality if needed */
    /* We only emit failures if we didn't manage to open the file */
    /* Otherwise it make Python bindings unhappy (#5616) */
    for(size_t iError=0;iError<aoErrors.size();iError++)
    {
        CPLError( (hTIFF == NULL && aoErrors[iError].type == CE_Failure) ? CE_Failure : CE_Warning,
                  aoErrors[iError].no,
                  "%s",
                  aoErrors[iError].msg.c_str() );
    }
    aoErrors.resize(0);

    if( hTIFF == NULL )
        return( NULL );

    uint32  nXSize, nYSize;
    uint16  nPlanarConfig;
    uint32  nRowsPerStrip;
    uint16  nCompression;

    TIFFGetField( hTIFF, TIFFTAG_IMAGEWIDTH, &nXSize );
    TIFFGetField( hTIFF, TIFFTAG_IMAGELENGTH, &nYSize );

    if( nXSize > INT_MAX || nYSize > INT_MAX )
    {
        /* GDAL only supports signed 32bit dimensions */
        XTIFFClose( hTIFF );
        return( NULL );
    }

    if( !TIFFGetField( hTIFF, TIFFTAG_PLANARCONFIG, &(nPlanarConfig) ) )
        nPlanarConfig = PLANARCONFIG_CONTIG;

    if( !TIFFGetField( hTIFF, TIFFTAG_COMPRESSION, &(nCompression) ) )
        nCompression = COMPRESSION_NONE;

    if( !TIFFGetField( hTIFF, TIFFTAG_ROWSPERSTRIP, &(nRowsPerStrip) ) )
        nRowsPerStrip = nYSize;

    if (!TIFFIsTiled( hTIFF ) &&
        nCompression == COMPRESSION_NONE &&
        nRowsPerStrip >= nYSize &&
        nPlanarConfig == PLANARCONFIG_CONTIG)
    {
        int bReopenWithStripChop = TRUE;
        if ( nYSize > 128 * 1024 * 1024 )
        {
            uint16  nSamplesPerPixel;
            uint16  nBitsPerSample;

            if( !TIFFGetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, &nSamplesPerPixel ) )
                nSamplesPerPixel = 1;

            if( !TIFFGetField(hTIFF, TIFFTAG_BITSPERSAMPLE, &(nBitsPerSample)) )
                nBitsPerSample = 1;

            vsi_l_offset nLineSize = (nSamplesPerPixel * (vsi_l_offset)nXSize * nBitsPerSample + 7) / 8;
            int nDefaultStripHeight = (int)(8192 / nLineSize);
            if (nDefaultStripHeight == 0) nDefaultStripHeight = 1;
            vsi_l_offset nStrips = nYSize / nDefaultStripHeight;

            /* There is a risk of DoS due to huge amount of memory allocated in ChopUpSingleUncompressedStrip() */
            /* in libtiff */
            if (nStrips > 128 * 1024 * 1024 &&
                !CPLTestBool(CPLGetConfigOption("GTIFF_FORCE_STRIP_CHOP", "NO")))
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                        "Potential denial of service detected. Avoid using strip chop. "
                        "Set the GTIFF_FORCE_STRIP_CHOP configuration open to go over this test.");
                bReopenWithStripChop = FALSE;
            }
        }

        if (bReopenWithStripChop)
        {
            CPLDebug("GTiff", "Reopen with strip chop enabled");
            XTIFFClose(hTIFF);
            hTIFF = VSI_TIFFOpen( pszFilename, ( poOpenInfo->eAccess == GA_ReadOnly ) ? "r" : "r+",
                                  poOpenInfo->fpL );
            if( hTIFF == NULL )
                return( NULL );
        }
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    GTiffDataset *poDS;

    poDS = new GTiffDataset();
    poDS->SetDescription( pszFilename );
    poDS->osFilename = pszFilename;
    poDS->poActiveDS = poDS;
    poDS->fpL = poOpenInfo->fpL;
    poOpenInfo->fpL = NULL;
    poDS->bStreamingIn = bStreaming;
    poDS->nCompression = nCompression;

    if( poDS->OpenOffset( hTIFF, &(poDS->poActiveDS),
                          TIFFCurrentDirOffset(hTIFF), TRUE,
                          poOpenInfo->eAccess,
                          bAllowRGBAInterface, true) != CE_None )
    {
        delete poDS;
        return NULL;
    }

    if( poOpenInfo->eAccess == GA_Update )
    {
        poDS->InitCreationOrOpenOptions(poOpenInfo->papszOpenOptions);
    }

    if( nCompression == COMPRESSION_JPEG && poOpenInfo->eAccess == GA_Update )
    {
        int bHasQuantizationTable = FALSE, bHasHuffmanTable = FALSE;
        int nQuality = poDS->GuessJPEGQuality(bHasQuantizationTable,
                                              bHasHuffmanTable);
        if( nQuality > 0 )
        {
            CPLDebug("GTiff", "Guessed JPEG quality to be %d", nQuality);
            poDS->nJpegQuality = nQuality;
            TIFFSetField( hTIFF, TIFFTAG_JPEGQUALITY, nQuality );

            /* This means we will use the quantization tables from the JpegTables */
            /* tag */
            poDS->nJpegTablesMode = JPEGTABLESMODE_QUANT;
        }
        else
        {
            uint32 nJPEGTableSize = 0;
            void* pJPEGTable = NULL;
            if( !TIFFGetField(hTIFF, TIFFTAG_JPEGTABLES, &nJPEGTableSize, &pJPEGTable) )
            {
                int bFoundNonEmptyBlock = FALSE;
                toff_t *panByteCounts = NULL;
                int nBlockCount;
                if( poDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
                    nBlockCount = poDS->nBlocksPerBand * poDS->nBands;
                else
                    nBlockCount = poDS->nBlocksPerBand;
                if( TIFFIsTiled( hTIFF ) )
                    TIFFGetField( hTIFF, TIFFTAG_TILEBYTECOUNTS, &panByteCounts );
                else
                    TIFFGetField( hTIFF, TIFFTAG_STRIPBYTECOUNTS, &panByteCounts );
                if( panByteCounts != NULL )
                {
                    for( int iBlock = 0; iBlock < nBlockCount; iBlock++ )
                    {
                        if( panByteCounts[iBlock] != 0 )
                        {
                            bFoundNonEmptyBlock = TRUE;
                            break;
                        }
                    }
                }
                if( bFoundNonEmptyBlock )
                {
                    CPLDebug("GTiff", "Could not guess JPEG quality. "
                             "JPEG tables are missing, so going in "
                             "TIFFTAG_JPEGTABLESMODE = 0/2 mode");
                    /* Write quantization tables in each strile */
                    poDS->nJpegTablesMode = 0;
                }
            }
            else
            {
                if( bHasQuantizationTable )
                {
                    // FIXME in libtiff: this is likely going to cause issues since
                    // libtiff will reuse in each strile the number of the global
                    // quantization table, which is invalid.

                    CPLDebug("GTiff", "Could not guess JPEG quality although JPEG "
                            "quantization tables are present, so going in "
                            "TIFFTAG_JPEGTABLESMODE = 0/2 mode");
                }
                else
                {
                    CPLDebug("GTiff", "Could not guess JPEG quality since JPEG "
                            "quantization tables are not present, so going in "
                            "TIFFTAG_JPEGTABLESMODE = 0/2 mode");
                }

                /* Write quantization tables in each strile */
                poDS->nJpegTablesMode = 0;
            }
        }
        if( bHasHuffmanTable )
        {
            /* If there are Huffman tables in header use them, otherwise */
            /* if we use optimized tables, libtiff will currently reuse */
            /* the number of the Huffman tables of the header for the */
            /* optimized version of each strile, which is illegal */
            poDS->nJpegTablesMode |= JPEGTABLESMODE_HUFF;
        }
        if( poDS->nJpegTablesMode >= 0 )
            TIFFSetField( hTIFF, TIFFTAG_JPEGTABLESMODE, poDS->nJpegTablesMode);
    }

    poDS->m_bLoadPam = true;
    poDS->bColorProfileMetadataChanged = FALSE;
    poDS->bMetadataChanged = FALSE;
    poDS->bGeoTIFFInfoChanged = FALSE;
    poDS->bNoDataChanged = FALSE;
    poDS->bForceUnsetGTOrGCPs = FALSE;
    poDS->bForceUnsetProjection = FALSE;

/* -------------------------------------------------------------------- */
/*      Initialize info for external overviews.                         */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, pszFilename );
    if( poOpenInfo->AreSiblingFilesLoaded() )
        poDS->oOvManager.TransferSiblingFiles( poOpenInfo->StealSiblingFiles() );

    /* For backward compatibility, in case GTIFF_POINT_GEO_IGNORE is defined */
    /* load georeferencing right now */
    if( CPLGetConfigOption("GTIFF_POINT_GEO_IGNORE", NULL) != NULL )
    {
        poDS->LoadGeoreferencingAndPamIfNeeded();
    }

    return poDS;
}

/************************************************************************/
/*                      GTiffDatasetSetAreaOrPointMD()                  */
/************************************************************************/

static void GTiffDatasetSetAreaOrPointMD(GTIF* hGTIF,
                                         GDALMultiDomainMetadata& oGTiffMDMD)
{
    // Is this a pixel-is-point dataset?
    short nRasterType;

    if( GDALGTIFKeyGetSHORT(hGTIF, GTRasterTypeGeoKey, &nRasterType,
                    0, 1 ) == 1 )
    {
        if( nRasterType == (short) RasterPixelIsPoint )
            oGTiffMDMD.SetMetadataItem( GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT );
        else
            oGTiffMDMD.SetMetadataItem( GDALMD_AREA_OR_POINT, GDALMD_AOP_AREA );
    }
}

/************************************************************************/
/*                         LoadMDAreaOrPoint()                          */
/************************************************************************/

/* This is a light version of LookForProjection(), which saves the */
/* potential costly cost of GTIFGetOGISDefn(), since we just need to */
/* access to a raw GeoTIFF key, and not build the full projection object. */

void GTiffDataset::LoadMDAreaOrPoint()
{
    if( bLookedForProjection || bLookedForMDAreaOrPoint ||
        oGTiffMDMD.GetMetadataItem( GDALMD_AREA_OR_POINT ) != NULL )
        return;

    bLookedForMDAreaOrPoint = TRUE;

    if (!SetDirectory())
        return;

    GTIF* hGTIF = GTIFNew(hTIFF);

    if ( !hGTIF )
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "GeoTIFF tags apparently corrupt, they are being ignored." );
    }
    else
    {
        GTiffDatasetSetAreaOrPointMD( hGTIF, oGTiffMDMD );

        GTIFFree( hGTIF );
    }
}

/************************************************************************/
/*                         LookForProjection()                          */
/************************************************************************/

void GTiffDataset::LookForProjection()

{
    if( bLookedForProjection )
        return;

    bLookedForProjection = TRUE;
    if (!SetDirectory())
        return;

/* -------------------------------------------------------------------- */
/*      Capture the GeoTIFF projection, if available.                   */
/* -------------------------------------------------------------------- */
    GTIF *hGTIF;

    CPLFree( pszProjection );
    pszProjection = NULL;

    hGTIF = GTIFNew(hTIFF);

    if ( !hGTIF )
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "GeoTIFF tags apparently corrupt, they are being ignored." );
    }
    else
    {
        GTIFDefn      *psGTIFDefn;

#if LIBGEOTIFF_VERSION >= 1410
        psGTIFDefn = GTIFAllocDefn();
#else
        psGTIFDefn = (GTIFDefn *) CPLCalloc(1,sizeof(GTIFDefn));
#endif

        if( GTIFGetDefn( hGTIF, psGTIFDefn ) )
        {
            pszProjection = GTIFGetOGISDefn( hGTIF, psGTIFDefn );

            // Should we simplify away vertical CS stuff?
            if( STARTS_WITH_CI(pszProjection, "COMPD_CS")
                && !CPLTestBool( CPLGetConfigOption("GTIFF_REPORT_COMPD_CS",
                                                       "NO") ) )
            {
                OGRSpatialReference oSRS;

                CPLDebug( "GTiff", "Got COMPD_CS, but stripping it." );
                char *pszWKT = pszProjection;
                oSRS.importFromWkt( &pszWKT );
                CPLFree( pszProjection );

                oSRS.StripVertical();
                oSRS.exportToWkt( &pszProjection );
            }
        }

        // check the tif linear unit and the CS linear unit
#ifdef ESRI_BUILD
        AdjustLinearUnit(psGTIFDefn.UOMLength);
#endif

#if LIBGEOTIFF_VERSION >= 1410
        GTIFFreeDefn(psGTIFDefn);
#else
        CPLFree(psGTIFDefn);
#endif

        GTiffDatasetSetAreaOrPointMD( hGTIF, oGTiffMDMD );

        GTIFFree( hGTIF );
    }

    if( pszProjection == NULL )
    {
        pszProjection = CPLStrdup( "" );
    }

    bGeoTIFFInfoChanged = FALSE;
    bForceUnsetGTOrGCPs = FALSE;
    bForceUnsetProjection = FALSE;
}

/************************************************************************/
/*                          AdjustLinearUnit()                          */
/*                                                                      */
/*      The following code is only used in ESRI Builds and there is     */
/*      outstanding discussion on whether it is even appropriate        */
/*      then.                                                           */
/************************************************************************/
#ifdef ESRI_BUILD

void GTiffDataset::AdjustLinearUnit(short UOMLength)
{
    if (!pszProjection || strlen(pszProjection) == 0)
        return;
    if( UOMLength == 9001)
    {
        char* pstr = strstr(pszProjection, "PARAMETER");
        if (!pstr)
            return;
        pstr = strstr(pstr, "UNIT[");
        if (!pstr)
            return;
        pstr = strchr(pstr, ',') + 1;
        if (!pstr)
            return;
        char* pstr1 = strchr(pstr, ']');
        if (!pstr1 || pstr1 - pstr >= 128)
            return;
        char csUnitStr[128];
        strncpy(csUnitStr, pstr, pstr1-pstr);
        csUnitStr[pstr1-pstr] = '\0';
        double csUnit = CPLAtof(csUnitStr);
        if(fabs(csUnit - 1.0) > 0.000001)
        {
            for(long i=0; i<6; i++)
                adfGeoTransform[i] /= csUnit;
        }
    }
}

#endif /* def ESRI_BUILD */

/************************************************************************/
/*                            ApplyPamInfo()                            */
/*                                                                      */
/*      PAM Information, if available, overrides the GeoTIFF            */
/*      geotransform and projection definition.  Check for them         */
/*      now.                                                            */
/************************************************************************/

void GTiffDataset::ApplyPamInfo()

{
    double adfPamGeoTransform[6];

    if( GDALPamDataset::GetGeoTransform( adfPamGeoTransform ) == CE_None
        && (adfPamGeoTransform[0] != 0.0 || adfPamGeoTransform[1] != 1.0
            || adfPamGeoTransform[2] != 0.0 || adfPamGeoTransform[3] != 0.0
            || adfPamGeoTransform[4] != 0.0 || adfPamGeoTransform[5] != 1.0 ))
    {
        memcpy( adfGeoTransform, adfPamGeoTransform, sizeof(double)*6 );
        bGeoTransformValid = TRUE;
    }

    const char *pszPamSRS = GDALPamDataset::GetProjectionRef();

    if( pszPamSRS != NULL && strlen(pszPamSRS) > 0 )
    {
        CPLFree( pszProjection );
        pszProjection = CPLStrdup( pszPamSRS );
        bLookedForProjection = TRUE;
    }

    int nPamGCPCount = GDALPamDataset::GetGCPCount();
    if( nPamGCPCount > 0 )
    {
        if( nGCPCount > 0 )
        {
            GDALDeinitGCPs( nGCPCount, pasGCPList );
            CPLFree( pasGCPList );
            pasGCPList = NULL;
        }

        nGCPCount = nPamGCPCount;
        pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALPamDataset::GetGCPs());

        CPLFree( pszProjection );
        pszProjection = NULL;

        const char *pszPamGCPProjection = GDALPamDataset::GetGCPProjection();
        if( pszPamGCPProjection != NULL && strlen(pszPamGCPProjection) > 0 )
            pszProjection = CPLStrdup(pszPamGCPProjection);

        bLookedForProjection = TRUE;
    }

/* -------------------------------------------------------------------- */
/*      Copy any PAM metadata into our GeoTIFF context, and with        */
/*      the PAM info overriding the GeoTIFF context.                    */
/* -------------------------------------------------------------------- */
    char **papszPamDomains = oMDMD.GetDomainList();

    for( int iDomain = 0; papszPamDomains && papszPamDomains[iDomain] != NULL; iDomain++ )
    {
        const char *pszDomain = papszPamDomains[iDomain];
        char **papszGT_MD = CSLDuplicate(oGTiffMDMD.GetMetadata( pszDomain ));
        char **papszPAM_MD = oMDMD.GetMetadata( pszDomain );

        papszGT_MD = CSLMerge( papszGT_MD, papszPAM_MD );

        oGTiffMDMD.SetMetadata( papszGT_MD, pszDomain );
        CSLDestroy( papszGT_MD );
    }

    for( int i = 1; i <= GetRasterCount(); i++)
    {
        GTiffRasterBand* poBand = (GTiffRasterBand *)GetRasterBand(i);
        papszPamDomains = poBand->oMDMD.GetDomainList();

        for( int iDomain = 0; papszPamDomains && papszPamDomains[iDomain] != NULL; iDomain++ )
        {
            const char *pszDomain = papszPamDomains[iDomain];
            char **papszGT_MD = CSLDuplicate(poBand->oGTiffMDMD.GetMetadata( pszDomain ));
            char **papszPAM_MD = poBand->oMDMD.GetMetadata( pszDomain );

            papszGT_MD = CSLMerge( papszGT_MD, papszPAM_MD );

            poBand->oGTiffMDMD.SetMetadata( papszGT_MD, pszDomain );
            CSLDestroy( papszGT_MD );
        }
    }
}

/************************************************************************/
/*                              OpenDir()                               */
/*                                                                      */
/*      Open a specific directory as encoded into a filename.           */
/************************************************************************/

GDALDataset *GTiffDataset::OpenDir( GDALOpenInfo * poOpenInfo )

{
    int bAllowRGBAInterface = TRUE;
    const char* pszFilename = poOpenInfo->pszFilename;
    if( STARTS_WITH_CI(pszFilename, "GTIFF_RAW:") )
    {
        bAllowRGBAInterface = FALSE;
        pszFilename += strlen("GTIFF_RAW:");
    }

    if( !STARTS_WITH_CI(pszFilename, "GTIFF_DIR:") )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Split out filename, and dir#/offset.                            */
/* -------------------------------------------------------------------- */
    pszFilename += strlen("GTIFF_DIR:");
    int        bAbsolute = FALSE;
    toff_t     nOffset;

    if( STARTS_WITH_CI(pszFilename, "off:") )
    {
        bAbsolute = TRUE;
        pszFilename += 4;
    }

    nOffset = atol(pszFilename);
    pszFilename += 1;

    while( *pszFilename != '\0' && pszFilename[-1] != ':' )
        pszFilename++;

    if( *pszFilename == '\0' || nOffset == 0 )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Unable to extract offset or filename, should take the form\n"
                  "GTIFF_DIR:<dir>:filename or GTIFF_DIR:off:<dir_offset>:filename" );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Try opening the dataset.                                        */
/* -------------------------------------------------------------------- */
    TIFF *hTIFF;

    if (!GTiffOneTimeInit())
        return NULL;

    VSILFILE* fpL = VSIFOpenL(pszFilename, "r");
    if( fpL == NULL )
        return NULL;
    hTIFF = VSI_TIFFOpen( pszFilename, "r", fpL );
    if( hTIFF == NULL )
    {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
        return( NULL );
    }

/* -------------------------------------------------------------------- */
/*      If a directory was requested by index, advance to it now.       */
/* -------------------------------------------------------------------- */
    if( !bAbsolute )
    {
        toff_t nOffsetRequested = nOffset;
        while( nOffset > 1 )
        {
            if( TIFFReadDirectory( hTIFF ) == 0 )
            {
                XTIFFClose( hTIFF );
                CPLError( CE_Failure, CPLE_OpenFailed,
                          "Requested directory %lu not found.", (long unsigned int)nOffsetRequested );
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
                return NULL;
            }
            nOffset--;
        }

        nOffset = TIFFCurrentDirOffset( hTIFF );
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    GTiffDataset *poDS;

    poDS = new GTiffDataset();
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->osFilename = poOpenInfo->pszFilename;
    poDS->poActiveDS = poDS;
    poDS->fpL = fpL;

    if( !EQUAL(pszFilename,poOpenInfo->pszFilename)
        && !STARTS_WITH_CI(poOpenInfo->pszFilename, "GTIFF_RAW:") )
    {
        poDS->SetPhysicalFilename( pszFilename );
        poDS->SetSubdatasetName( poOpenInfo->pszFilename );
        poDS->osFilename = pszFilename;
    }

    if (poOpenInfo->eAccess == GA_Update)
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "Opening a specific TIFF directory is not supported in update mode. Switching to read-only" );
    }

    if( poOpenInfo->AreSiblingFilesLoaded() )
        poDS->oOvManager.TransferSiblingFiles( poOpenInfo->StealSiblingFiles() );

    if( poDS->OpenOffset( hTIFF, &(poDS->poActiveDS),
                          nOffset, FALSE, GA_ReadOnly,
                          bAllowRGBAInterface, true ) != CE_None )
    {
        delete poDS;
        return NULL;
    }
    else
    {
        poDS->bCloseTIFFHandle = TRUE;
        return poDS;
    }
}

/************************************************************************/
/*                   ConvertTransferFunctionToString()                  */
/*                                                                      */
/*      Convert a transfer function table into a string.                */
/*      Used by LoadICCProfile().                                       */
/************************************************************************/
static CPLString ConvertTransferFunctionToString( const uint16 *pTable, uint32 nTableEntries )
{
    CPLString sValue;

    for(uint32 i = 0; i < nTableEntries; i++)
    {
        if (i == 0)
            sValue = sValue.Printf("%d", (uint32)pTable[i]);
        else
        sValue = sValue.Printf("%s, %d", (const char*)sValue, (uint32)pTable[i]);
    }

    return sValue;
}

/************************************************************************/
/*                             LoadICCProfile()                         */
/*                                                                      */
/*      Load ICC Profile or colorimetric data into metadata             */
/************************************************************************/

void GTiffDataset::LoadICCProfile()
{
    uint32 nEmbedLen;
    uint8* pEmbedBuffer;
    float* pCHR;
    float* pWP;
    uint16 *pTFR, *pTFG, *pTFB;
    uint16 *pTransferRange = NULL;
    const int TIFFTAG_TRANSFERRANGE = 0x0156;

    if (bICCMetadataLoaded)
        return;
    bICCMetadataLoaded = TRUE;

    if (!SetDirectory())
        return;

    if (TIFFGetField(hTIFF, TIFFTAG_ICCPROFILE, &nEmbedLen, &pEmbedBuffer))
    {
        char *pszBase64Profile = CPLBase64Encode(nEmbedLen, (const GByte*)pEmbedBuffer);

        oGTiffMDMD.SetMetadataItem( "SOURCE_ICC_PROFILE", pszBase64Profile, "COLOR_PROFILE" );

        CPLFree(pszBase64Profile);

        return;
    }

    /* Check for colorimetric tiff */
    if (TIFFGetField(hTIFF, TIFFTAG_PRIMARYCHROMATICITIES, &pCHR))
    {
        if (TIFFGetField(hTIFF, TIFFTAG_WHITEPOINT, &pWP))
        {
            if (!TIFFGetFieldDefaulted(hTIFF, TIFFTAG_TRANSFERFUNCTION, &pTFR, &pTFG, &pTFB))
                return;

            TIFFGetFieldDefaulted(hTIFF, TIFFTAG_TRANSFERRANGE, &pTransferRange);

            // Set all the colorimetric metadata.
            oGTiffMDMD.SetMetadataItem( "SOURCE_PRIMARIES_RED",
                CPLString().Printf( "%.9f, %.9f, 1.0", (double)pCHR[0], (double)pCHR[1] ) , "COLOR_PROFILE" );
            oGTiffMDMD.SetMetadataItem( "SOURCE_PRIMARIES_GREEN",
                CPLString().Printf( "%.9f, %.9f, 1.0", (double)pCHR[2], (double)pCHR[3] ) , "COLOR_PROFILE" );
            oGTiffMDMD.SetMetadataItem( "SOURCE_PRIMARIES_BLUE",
                CPLString().Printf( "%.9f, %.9f, 1.0", (double)pCHR[4], (double)pCHR[5] ) , "COLOR_PROFILE" );

            oGTiffMDMD.SetMetadataItem( "SOURCE_WHITEPOINT",
                CPLString().Printf( "%.9f, %.9f, 1.0", (double)pWP[0], (double)pWP[1] ) , "COLOR_PROFILE" );

            /* Set transfer function metadata */

            /* Get length of table. */
            const uint32 nTransferFunctionLength = 1 << nBitsPerSample;

            oGTiffMDMD.SetMetadataItem( "TIFFTAG_TRANSFERFUNCTION_RED",
                ConvertTransferFunctionToString( pTFR, nTransferFunctionLength), "COLOR_PROFILE" );

            oGTiffMDMD.SetMetadataItem( "TIFFTAG_TRANSFERFUNCTION_GREEN",
                ConvertTransferFunctionToString( pTFG, nTransferFunctionLength), "COLOR_PROFILE" );

            oGTiffMDMD.SetMetadataItem( "TIFFTAG_TRANSFERFUNCTION_BLUE",
                ConvertTransferFunctionToString( pTFB, nTransferFunctionLength), "COLOR_PROFILE" );

            /* Set transfer range */
            if (pTransferRange)
            {
                oGTiffMDMD.SetMetadataItem( "TIFFTAG_TRANSFERRANGE_BLACK",
                    CPLString().Printf( "%d, %d, %d",
                        (int)pTransferRange[0], (int)pTransferRange[2], (int)pTransferRange[4]), "COLOR_PROFILE" );
                oGTiffMDMD.SetMetadataItem( "TIFFTAG_TRANSFERRANGE_WHITE",
                    CPLString().Printf( "%d, %d, %d",
                        (int)pTransferRange[1], (int)pTransferRange[3], (int)pTransferRange[5]), "COLOR_PROFILE" );
            }
        }
    }
}

/************************************************************************/
/*                             SaveICCProfile()                         */
/*                                                                      */
/*      Save ICC Profile or colorimetric data into file                 */
/* pDS:                                                                 */
/*      Dataset that contains the metadata with the ICC or colorimetric */
/*      data. If this argument is specified, all other arguments are    */
/*      ignored. Set them to NULL or 0.                                 */
/* hTIFF:                                                               */
/*      Pointer to TIFF handle. Only needed if pDS is NULL or           */
/*      pDS->hTIFF is NULL.                                             */
/* papszParmList:                                                       */
/*      Options containing the ICC profile or colorimetric metadata.    */
/*      Ignored if pDS is not NULL.                                     */
/* nBitsPerSample:                                                      */
/*      Bits per sample. Ignored if pDS is not NULL.                    */
/************************************************************************/

void GTiffDataset::SaveICCProfile(GTiffDataset *pDS, TIFF *hTIFF, char **papszParmList, uint32 nBitsPerSample)
{
    if ((pDS != NULL) && (pDS->eAccess != GA_Update))
        return;

    if (hTIFF == NULL)
    {
        if (pDS == NULL)
            return;

        hTIFF = pDS->hTIFF;
        if (hTIFF == NULL)
            return;
    }

    if ((papszParmList == NULL) && (pDS == NULL))
        return;

    const char *pszValue = NULL;
    if (pDS != NULL)
        pszValue = pDS->GetMetadataItem("SOURCE_ICC_PROFILE", "COLOR_PROFILE");
    else
        pszValue = CSLFetchNameValue(papszParmList, "SOURCE_ICC_PROFILE");
    if( pszValue != NULL )
    {
        int32 nEmbedLen;
        char *pEmbedBuffer = CPLStrdup(pszValue);
        nEmbedLen = CPLBase64DecodeInPlace((GByte*)pEmbedBuffer);

        TIFFSetField(hTIFF, TIFFTAG_ICCPROFILE, nEmbedLen, pEmbedBuffer);

        CPLFree(pEmbedBuffer);
    }
    else
    {
        /* Output colorimetric data. */
        const int TIFFTAG_TRANSFERRANGE = 0x0156;

        float pCHR[6]; // Primaries
        float pWP[2];  // Whitepoint
        uint16 pTXR[6]; // Transfer range
        const char* pszCHRNames[] = {
            "SOURCE_PRIMARIES_RED",
            "SOURCE_PRIMARIES_GREEN",
            "SOURCE_PRIMARIES_BLUE"
        };
        const char* pszTXRNames[] = {
            "TIFFTAG_TRANSFERRANGE_BLACK",
            "TIFFTAG_TRANSFERRANGE_WHITE"
        };

        /* Output chromacities */
        bool bOutputCHR = true;
        for(int i = 0; ((i < 3) && bOutputCHR); i++)
        {
            if (pDS != NULL)
                pszValue = pDS->GetMetadataItem(pszCHRNames[i], "COLOR_PROFILE");
            else
                pszValue = CSLFetchNameValue(papszParmList, pszCHRNames[i]);
            if (pszValue == NULL)
            {
                bOutputCHR = false;
                break;
            }

            char** papszTokens = CSLTokenizeString2( pszValue, ",",
                CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );

            if (CSLCount( papszTokens ) != 3)
            {
                bOutputCHR = false;
                CSLDestroy( papszTokens );
                break;
            }

            int j;
            for( j = 0; j < 3; j++ )
            {
                float v = (float)CPLAtof(papszTokens[j]);

                if (j == 2)
                {
                    /* Last term of xyY color must be 1.0 */
                    if (v != 1.0)
                    {
                        bOutputCHR = false;
                        break;
                    }
                }
                else
                {
                    pCHR[i * 2 + j] = v;
                }
            }

            CSLDestroy( papszTokens );
        }

        if (bOutputCHR)
        {
            TIFFSetField(hTIFF, TIFFTAG_PRIMARYCHROMATICITIES, pCHR);
        }

        /* Output whitepoint */
        bool bOutputWhitepoint = true;
        if (pDS != NULL)
            pszValue = pDS->GetMetadataItem("SOURCE_WHITEPOINT", "COLOR_PROFILE");
        else
            pszValue = CSLFetchNameValue(papszParmList, "SOURCE_WHITEPOINT");
        if (pszValue != NULL)
        {
            char** papszTokens = CSLTokenizeString2( pszValue, ",",
                CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );

            if (CSLCount( papszTokens ) != 3)
            {
                bOutputWhitepoint = false;
            }
            else
            {
                int j;
                for( j = 0; j < 3; j++ )
                {
                    float v = (float)CPLAtof(papszTokens[j]);

                    if (j == 2)
                    {
                        /* Last term of xyY color must be 1.0 */
                        if (v != 1.0)
                        {
                            bOutputWhitepoint = false;
                            break;
                        }
                    }
                    else
                    {
                        pWP[j] = v;
                    }
                }
            }
            CSLDestroy( papszTokens );

            if (bOutputWhitepoint)
            {
                TIFFSetField(hTIFF, TIFFTAG_WHITEPOINT, pWP);
            }
        }

        /* Set transfer function metadata */
        char const *pszTFRed = NULL;
        char const *pszTFGreen = NULL;
        char const *pszTFBlue = NULL;
        if (pDS != NULL)
            pszTFRed = pDS->GetMetadataItem("TIFFTAG_TRANSFERFUNCTION_RED", "COLOR_PROFILE");
        else
            pszTFRed = CSLFetchNameValue(papszParmList, "TIFFTAG_TRANSFERFUNCTION_RED");

        if (pDS != NULL)
            pszTFGreen = pDS->GetMetadataItem("TIFFTAG_TRANSFERFUNCTION_GREEN", "COLOR_PROFILE");
        else
            pszTFGreen = CSLFetchNameValue(papszParmList, "TIFFTAG_TRANSFERFUNCTION_GREEN");

        if (pDS != NULL)
            pszTFBlue = pDS->GetMetadataItem("TIFFTAG_TRANSFERFUNCTION_BLUE", "COLOR_PROFILE");
        else
            pszTFBlue = CSLFetchNameValue(papszParmList, "TIFFTAG_TRANSFERFUNCTION_BLUE");

        if ((pszTFRed != NULL) && (pszTFGreen != NULL) && (pszTFBlue != NULL))
        {
            /* Get length of table. */
            const int nTransferFunctionLength = 1 << ((pDS!=NULL)?pDS->nBitsPerSample:nBitsPerSample);

            char** papszTokensRed = CSLTokenizeString2( pszTFRed, ",",
                CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );
            char** papszTokensGreen = CSLTokenizeString2( pszTFGreen, ",",
                CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );
            char** papszTokensBlue = CSLTokenizeString2( pszTFBlue, ",",
                CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );

            if ((CSLCount( papszTokensRed ) == nTransferFunctionLength) &&
                (CSLCount( papszTokensGreen ) == nTransferFunctionLength) &&
                (CSLCount( papszTokensBlue ) == nTransferFunctionLength))
            {
                uint16 *pTransferFuncRed, *pTransferFuncGreen, *pTransferFuncBlue;
                pTransferFuncRed = (uint16*)CPLMalloc(sizeof(uint16) * nTransferFunctionLength);
                pTransferFuncGreen = (uint16*)CPLMalloc(sizeof(uint16) * nTransferFunctionLength);
                pTransferFuncBlue = (uint16*)CPLMalloc(sizeof(uint16) * nTransferFunctionLength);

                /* Convert our table in string format into int16 format. */
                for(int i = 0; i < nTransferFunctionLength; i++)
                {
                    pTransferFuncRed[i] = (uint16)atoi(papszTokensRed[i]);
                    pTransferFuncGreen[i] = (uint16)atoi(papszTokensGreen[i]);
                    pTransferFuncBlue[i] = (uint16)atoi(papszTokensBlue[i]);
                }

                TIFFSetField(hTIFF, TIFFTAG_TRANSFERFUNCTION,
                    pTransferFuncRed, pTransferFuncGreen, pTransferFuncBlue);

                CPLFree(pTransferFuncRed);
                CPLFree(pTransferFuncGreen);
                CPLFree(pTransferFuncBlue);
            }

            CSLDestroy( papszTokensRed );
            CSLDestroy( papszTokensGreen );
            CSLDestroy( papszTokensBlue );
        }

        /* Output transfer range */
        bool bOutputTransferRange = true;
        for(int i = 0; ((i < 2) && bOutputTransferRange); i++)
        {
            if (pDS != NULL)
                pszValue = pDS->GetMetadataItem(pszTXRNames[i], "COLOR_PROFILE");
            else
                pszValue = CSLFetchNameValue(papszParmList, pszTXRNames[i]);
            if (pszValue == NULL)
            {
                bOutputTransferRange = false;
                break;
            }

            char** papszTokens = CSLTokenizeString2( pszValue, ",",
                CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );

            if (CSLCount( papszTokens ) != 3)
            {
                bOutputTransferRange = false;
                CSLDestroy( papszTokens );
                break;
            }

            int j;
            for( j = 0; j < 3; j++ )
            {
                pTXR[i + j * 2] = (uint16)atoi(papszTokens[j]);
            }

            CSLDestroy( papszTokens );
        }

        if (bOutputTransferRange)
        {
            TIFFSetField(hTIFF, TIFFTAG_TRANSFERRANGE, pTXR);
        }
    }
}

/************************************************************************/
/*                             OpenOffset()                             */
/*                                                                      */
/*      Initialize the GTiffDataset based on a passed in file           */
/*      handle, and directory offset to utilize.  This is called for    */
/*      full res, and overview pages.                                   */
/************************************************************************/

CPLErr GTiffDataset::OpenOffset( TIFF *hTIFFIn,
                                 GTiffDataset **ppoActiveDSRefIn,
                                 toff_t nDirOffsetIn,
                                 int bBaseIn, GDALAccess eAccessIn,
                                 int bAllowRGBAInterface,
                                 bool bReadGeoTransform )

{
    bool bTreatAsBitmap = false;
    bool bTreatAsOdd = false;

    eAccess = eAccessIn;

    hTIFF = hTIFFIn;
    ppoActiveDSRef = ppoActiveDSRefIn;

    nDirOffset = nDirOffsetIn;

    if (!SetDirectory( nDirOffsetIn ))
        return CE_Failure;

    bBase = bBaseIn;

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    uint32 nXSize, nYSize;
    TIFFGetField( hTIFF, TIFFTAG_IMAGEWIDTH, &nXSize );
    TIFFGetField( hTIFF, TIFFTAG_IMAGELENGTH, &nYSize );
    nRasterXSize = nXSize;
    nRasterYSize = nYSize;

    if( !TIFFGetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, &nSamplesPerPixel ) )
        nBands = 1;
    else
        nBands = nSamplesPerPixel;

    if( !TIFFGetField(hTIFF, TIFFTAG_BITSPERSAMPLE, &(nBitsPerSample)) )
        nBitsPerSample = 1;

    if( !TIFFGetField( hTIFF, TIFFTAG_PLANARCONFIG, &(nPlanarConfig) ) )
        nPlanarConfig = PLANARCONFIG_CONTIG;

    if( !TIFFGetField( hTIFF, TIFFTAG_PHOTOMETRIC, &(nPhotometric) ) )
        nPhotometric = PHOTOMETRIC_MINISBLACK;

    if( !TIFFGetField( hTIFF, TIFFTAG_SAMPLEFORMAT, &(nSampleFormat) ) )
        nSampleFormat = SAMPLEFORMAT_UINT;

    if( !TIFFGetField( hTIFF, TIFFTAG_COMPRESSION, &(nCompression) ) )
        nCompression = COMPRESSION_NONE;

#if defined(TIFFLIB_VERSION) && TIFFLIB_VERSION > 20031007 /* 3.6.0 */
    if (nCompression != COMPRESSION_NONE &&
        !TIFFIsCODECConfigured(nCompression))
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Cannot open TIFF file due to missing codec." );
        return CE_Failure;
    }
#endif

/* -------------------------------------------------------------------- */
/*      YCbCr JPEG compressed images should be translated on the fly    */
/*      to RGB by libtiff/libjpeg unless specifically requested         */
/*      otherwise.                                                      */
/* -------------------------------------------------------------------- */
    if( nCompression == COMPRESSION_JPEG
        && nPhotometric == PHOTOMETRIC_YCBCR
        && CPLTestBool( CPLGetConfigOption("CONVERT_YCBCR_TO_RGB",
                                              "YES") ) )
    {
        int nColorMode;

        oGTiffMDMD.SetMetadataItem( "SOURCE_COLOR_SPACE", "YCbCr", "IMAGE_STRUCTURE" );
        if ( !TIFFGetField( hTIFF, TIFFTAG_JPEGCOLORMODE, &nColorMode ) ||
              nColorMode != JPEGCOLORMODE_RGB )
            TIFFSetField(hTIFF, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
    }

/* -------------------------------------------------------------------- */
/*      Get strip/tile layout.                                          */
/* -------------------------------------------------------------------- */
    if( TIFFIsTiled(hTIFF) )
    {
        TIFFGetField( hTIFF, TIFFTAG_TILEWIDTH, &(nBlockXSize) );
        TIFFGetField( hTIFF, TIFFTAG_TILELENGTH, &(nBlockYSize) );
    }
    else
    {
        if( !TIFFGetField( hTIFF, TIFFTAG_ROWSPERSTRIP,
                           &(nRowsPerStrip) ) )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                      "RowsPerStrip not defined ... assuming all one strip." );
            nRowsPerStrip = nYSize; /* dummy value */
        }

        // If the rows per strip is larger than the file we will get
        // confused.  libtiff internally will treat the rowsperstrip as
        // the image height and it is best if we do too. (#4468)
        if (nRowsPerStrip > (uint32)nRasterYSize)
            nRowsPerStrip = nRasterYSize;

        nBlockXSize = nRasterXSize;
        nBlockYSize = nRowsPerStrip;
    }

    nBlocksPerBand =
        DIV_ROUND_UP(nYSize, nBlockYSize) * DIV_ROUND_UP(nXSize, nBlockXSize);

/* -------------------------------------------------------------------- */
/*      Should we handle this using the GTiffBitmapBand?                */
/* -------------------------------------------------------------------- */
    if( nBitsPerSample == 1 && nBands == 1 )
    {
        bTreatAsBitmap = true;

        // Lets treat large "one row" bitmaps using the scanline api.
        if( !TIFFIsTiled(hTIFF)
            && nBlockYSize == nYSize
            && nYSize > 2000
            /* libtiff does not support reading JBIG files with TIFFReadScanline() */
            && nCompression != COMPRESSION_JBIG )
        {
            bTreatAsSplitBitmap = TRUE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Should we treat this via the RGBA interface?                    */
/* -------------------------------------------------------------------- */
    if(
#ifdef DEBUG
        CPLTestBool(CPLGetConfigOption("GTIFF_FORCE_RGBA", "NO")) ||
#endif
        (bAllowRGBAInterface &&
        !bTreatAsBitmap && !(nBitsPerSample > 8)
        && (nPhotometric == PHOTOMETRIC_CIELAB ||
            nPhotometric == PHOTOMETRIC_LOGL ||
            nPhotometric == PHOTOMETRIC_LOGLUV ||
            nPhotometric == PHOTOMETRIC_SEPARATED ||
            ( nPhotometric == PHOTOMETRIC_YCBCR
              && nCompression != COMPRESSION_JPEG ))) )
    {
        char szMessage[1024];

        if( TIFFRGBAImageOK( hTIFF, szMessage ) == 1 )
        {
            const char* pszSourceColorSpace = NULL;
            switch (nPhotometric)
            {
                case PHOTOMETRIC_CIELAB:
                    pszSourceColorSpace = "CIELAB";
                    break;
                case PHOTOMETRIC_LOGL:
                    pszSourceColorSpace = "LOGL";
                    break;
                case PHOTOMETRIC_LOGLUV:
                    pszSourceColorSpace = "LOGLUV";
                    break;
                case PHOTOMETRIC_SEPARATED:
                    pszSourceColorSpace = "CMYK";
                    break;
                case PHOTOMETRIC_YCBCR:
                    pszSourceColorSpace = "YCbCr";
                    break;
            }
            if (pszSourceColorSpace)
                oGTiffMDMD.SetMetadataItem( "SOURCE_COLOR_SPACE", pszSourceColorSpace, "IMAGE_STRUCTURE" );
            bTreatAsRGBA = TRUE;
            nBands = 4;
        }
        else
        {
            CPLDebug( "GTiff", "TIFFRGBAImageOK says:\n%s", szMessage );
        }
    }

    // libtiff has various issues with OJPEG compression and chunky-strip support
    // with the "classic" scanline/strip/tile interfaces, and that wouldn't
    // work either, so better bail out
    if( nCompression == COMPRESSION_OJPEG &&
        !bTreatAsRGBA )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Old-JPEG compression only supported through RGBA interface, "
                 "which cannot be used probably because the file is corrupted");
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Should we treat this via the split interface?                   */
/* -------------------------------------------------------------------- */
    if( !TIFFIsTiled(hTIFF)
        && nBitsPerSample == 8
        && nBlockYSize == nYSize
        && nYSize > 2000
        && !bTreatAsRGBA
        && CPLTestBool(CPLGetConfigOption("GDAL_ENABLE_TIFF_SPLIT", "YES")))
    {
        /* libtiff 3.9.2 (20091104) and older, libtiff 4.0.0beta5 (also 20091104) */
        /* and older will crash when trying to open a all-in-one-strip */
        /* YCbCr JPEG compressed TIFF (see #3259). */
#if (TIFFLIB_VERSION <= 20091104 && !defined(BIGTIFF_SUPPORT)) || \
    (TIFFLIB_VERSION <= 20091104 && defined(BIGTIFF_SUPPORT))
        if (nPhotometric == PHOTOMETRIC_YCBCR  &&
            nCompression == COMPRESSION_JPEG)
        {
            CPLDebug("GTiff", "Avoid using split band to open all-in-one-strip "
                              "YCbCr JPEG compressed TIFF because of older libtiff");
        }
        else
#endif
            bTreatAsSplit = TRUE;
    }

/* -------------------------------------------------------------------- */
/*      Should we treat this via the odd bits interface?                */
/* -------------------------------------------------------------------- */
    if ( nSampleFormat == SAMPLEFORMAT_IEEEFP )
    {
        if ( nBitsPerSample == 16 || nBitsPerSample == 24 )
            bTreatAsOdd = true;
    }
    else if ( !bTreatAsRGBA && !bTreatAsBitmap
              && nBitsPerSample != 8
              && nBitsPerSample != 16
              && nBitsPerSample != 32
              && nBitsPerSample != 64
              && nBitsPerSample != 128 )
        bTreatAsOdd = true;


/* -------------------------------------------------------------------- */
/*      We don't support 'chunks' bigger than 2GB although libtiff v4   */
/*      can.                                                            */
/* -------------------------------------------------------------------- */
#if defined(BIGTIFF_SUPPORT)
    tmsize_t nChunkSize;
    if( bTreatAsRGBA )
    {
        nChunkSize = 4 * static_cast<tmsize_t>(nBlockXSize) * nBlockYSize;
    }
    else if( bTreatAsSplit || bTreatAsSplitBitmap )
    {
        nChunkSize = TIFFScanlineSize( hTIFF );
    }
    else
    {
        if( TIFFIsTiled(hTIFF) )
            nChunkSize = TIFFTileSize( hTIFF );
        else
            nChunkSize = TIFFStripSize( hTIFF );
    }
    if( nChunkSize > INT_MAX )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                "Scanline/tile/strip size bigger than 2GB." );
        return CE_Failure;
    }
#endif

    bool bMinIsWhite = nPhotometric == PHOTOMETRIC_MINISWHITE;

/* -------------------------------------------------------------------- */
/*      Check for NODATA                                                */
/* -------------------------------------------------------------------- */
    char    *pszText;
    if( TIFFGetField( hTIFF, TIFFTAG_GDAL_NODATA, &pszText ) &&
        !EQUAL(pszText, "") )
    {
        bNoDataSet = TRUE;
        dfNoDataValue = CPLAtofM( pszText );
    }

/* -------------------------------------------------------------------- */
/*      Capture the color table if there is one.                        */
/* -------------------------------------------------------------------- */
    unsigned short *panRed, *panGreen, *panBlue;

    if( bTreatAsRGBA || nBitsPerSample > 16
        || TIFFGetField( hTIFF, TIFFTAG_COLORMAP,
                         &panRed, &panGreen, &panBlue) == 0 )
    {
        // Build inverted palette if we have inverted photometric.
        // Pixel values remains unchanged.  Avoid doing this for *deep*
            // data types (per #1882)
        if( nBitsPerSample <= 16 && nPhotometric == PHOTOMETRIC_MINISWHITE )
        {
            GDALColorEntry  oEntry;

            poColorTable = new GDALColorTable();
            const int nColorCount = 1 << nBitsPerSample;

            for ( int iColor = 0; iColor < nColorCount; iColor++ )
            {
            oEntry.c1 = oEntry.c2 = oEntry.c3 = (short)
                        ((255 * (nColorCount - 1 - iColor)) / (nColorCount-1));
            oEntry.c4 = 255;
            poColorTable->SetColorEntry( iColor, &oEntry );
            }

            nPhotometric = PHOTOMETRIC_PALETTE;
        }
        else
            poColorTable = NULL;
    }
    else
    {
        int nMaxColor = 0;
        GDALColorEntry oEntry;

        poColorTable = new GDALColorTable();

        const int nColorCount = 1 << nBitsPerSample;

        for( int iColor = nColorCount - 1; iColor >= 0; iColor-- )
        {
            oEntry.c1 = panRed[iColor] / 256;
            oEntry.c2 = panGreen[iColor] / 256;
            oEntry.c3 = panBlue[iColor] / 256;
            oEntry.c4 = (bNoDataSet && (int)dfNoDataValue == iColor) ? 0 : 255;

            poColorTable->SetColorEntry( iColor, &oEntry );

            nMaxColor = MAX(nMaxColor,panRed[iColor]);
            nMaxColor = MAX(nMaxColor,panGreen[iColor]);
            nMaxColor = MAX(nMaxColor,panBlue[iColor]);
        }

        // Bug 1384 - Some TIFF files are generated with color map entry
        // values in range 0-255 instead of 0-65535 - try to handle these
        // gracefully.
        if( nMaxColor > 0 && nMaxColor < 256 )
        {
            CPLDebug( "GTiff", "TIFF ColorTable seems to be improperly scaled, fixing up." );

            for( int iColor = nColorCount - 1; iColor >= 0; iColor-- )
            {
                oEntry.c1 = panRed[iColor];
                oEntry.c2 = panGreen[iColor];
                oEntry.c3 = panBlue[iColor];
                oEntry.c4 = (bNoDataSet && (int)dfNoDataValue == iColor) ? 0 : 255;

                poColorTable->SetColorEntry( iColor, &oEntry );
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    for( int iBand = 0; iBand < nBands; iBand++ )
    {
        if( bTreatAsRGBA )
            SetBand( iBand+1, new GTiffRGBABand( this, iBand+1 ) );
        else if( bTreatAsSplitBitmap )
            SetBand( iBand+1, new GTiffSplitBitmapBand( this, iBand+1 ) );
        else if( bTreatAsSplit )
            SetBand( iBand+1, new GTiffSplitBand( this, iBand+1 ) );
        else if( bTreatAsBitmap )
            SetBand( iBand+1, new GTiffBitmapBand( this, iBand+1 ) );
        else if( bTreatAsOdd )
            SetBand( iBand+1, new GTiffOddBitsBand( this, iBand+1 ) );
        else
            SetBand( iBand+1, new GTiffRasterBand( this, iBand+1 ) );
    }

    if( GetRasterBand(1)->GetRasterDataType() == GDT_Unknown )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Unsupported TIFF configuration." );
        return CE_Failure;
    }

    m_bReadGeoTransform = bReadGeoTransform;

/* -------------------------------------------------------------------- */
/*      Capture some other potentially interesting information.         */
/* -------------------------------------------------------------------- */
    char szWorkMDI[200];
    uint16  nShort;

    size_t iTag;
    for(iTag=0;iTag<sizeof(asTIFFTags)/sizeof(asTIFFTags[0]);iTag++)
    {
        if( asTIFFTags[iTag].eType == GTIFFTAGTYPE_STRING )
        {
            if( TIFFGetField( hTIFF, asTIFFTags[iTag].nTagVal, &pszText ) )
                oGTiffMDMD.SetMetadataItem( asTIFFTags[iTag].pszTagName,  pszText );
        }
        else if( asTIFFTags[iTag].eType == GTIFFTAGTYPE_FLOAT )
        {
            float   fVal;
            if( TIFFGetField( hTIFF, asTIFFTags[iTag].nTagVal, &fVal ) )
            {
                CPLsnprintf( szWorkMDI, sizeof(szWorkMDI), "%.8g", fVal );
                oGTiffMDMD.SetMetadataItem( asTIFFTags[iTag].pszTagName, szWorkMDI );
            }
        }
        else if( asTIFFTags[iTag].eType == GTIFFTAGTYPE_SHORT &&
                 asTIFFTags[iTag].nTagVal != TIFFTAG_RESOLUTIONUNIT )
        {
            if( TIFFGetField( hTIFF, asTIFFTags[iTag].nTagVal, &nShort ) )
            {
                snprintf( szWorkMDI, sizeof(szWorkMDI), "%d", nShort );
                oGTiffMDMD.SetMetadataItem( asTIFFTags[iTag].pszTagName, szWorkMDI );
            }
        }
    }

    if( TIFFGetField( hTIFF, TIFFTAG_RESOLUTIONUNIT, &nShort ) )
    {
        if( nShort == RESUNIT_NONE )
            snprintf( szWorkMDI, sizeof(szWorkMDI), "%d (unitless)", nShort );
        else if( nShort == RESUNIT_INCH )
            snprintf( szWorkMDI, sizeof(szWorkMDI), "%d (pixels/inch)", nShort );
        else if( nShort == RESUNIT_CENTIMETER )
            snprintf( szWorkMDI, sizeof(szWorkMDI), "%d (pixels/cm)", nShort );
        else
            snprintf( szWorkMDI, sizeof(szWorkMDI), "%d", nShort );
        oGTiffMDMD.SetMetadataItem( "TIFFTAG_RESOLUTIONUNIT", szWorkMDI );
    }

    int nTagSize;
    void* pData;
    if( TIFFGetField( hTIFF, TIFFTAG_XMLPACKET, &nTagSize, &pData ) )
    {
        char* pszXMP = (char*)VSI_MALLOC_VERBOSE(nTagSize + 1);
        if (pszXMP)
        {
            memcpy(pszXMP, pData, nTagSize);
            pszXMP[nTagSize] = '\0';

            char *apszMDList[2];
            apszMDList[0] = pszXMP;
            apszMDList[1] = NULL;
            oGTiffMDMD.SetMetadata(apszMDList, "xml:XMP");

            CPLFree(pszXMP);
        }
    }

    if( nCompression == COMPRESSION_NONE )
        /* no compression tag */;
    else if( nCompression == COMPRESSION_CCITTRLE )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "CCITTRLE", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_CCITTFAX3 )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "CCITTFAX3", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_CCITTFAX4 )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "CCITTFAX4", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_LZW )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "LZW", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_OJPEG )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "OJPEG", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_JPEG )
    {
        if ( nPhotometric == PHOTOMETRIC_YCBCR )
            oGTiffMDMD.SetMetadataItem( "COMPRESSION", "YCbCr JPEG", "IMAGE_STRUCTURE" );
        else
            oGTiffMDMD.SetMetadataItem( "COMPRESSION", "JPEG", "IMAGE_STRUCTURE" );
    }
    else if( nCompression == COMPRESSION_NEXT )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "NEXT", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_CCITTRLEW )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "CCITTRLEW", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_PACKBITS )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "PACKBITS", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_THUNDERSCAN )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "THUNDERSCAN", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_PIXARFILM )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "PIXARFILM", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_PIXARLOG )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "PIXARLOG", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_DEFLATE )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "DEFLATE", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_ADOBE_DEFLATE )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "DEFLATE", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_DCS )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "DCS", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_JBIG )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "JBIG", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_SGILOG )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "SGILOG", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_SGILOG24 )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "SGILOG24", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_JP2000 )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "JP2000", "IMAGE_STRUCTURE" );
    else if( nCompression == COMPRESSION_LZMA )
        oGTiffMDMD.SetMetadataItem( "COMPRESSION", "LZMA", "IMAGE_STRUCTURE" );

    else
    {
        CPLString oComp;
        oGTiffMDMD.SetMetadataItem( "COMPRESSION",
                         (const char *) oComp.Printf( "%d", nCompression));
    }

    if( nPlanarConfig == PLANARCONFIG_CONTIG && nBands != 1 )
        oGTiffMDMD.SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
    else
        oGTiffMDMD.SetMetadataItem( "INTERLEAVE", "BAND", "IMAGE_STRUCTURE" );

    if(  (GetRasterBand(1)->GetRasterDataType() == GDT_Byte   && nBitsPerSample != 8 ) ||
         (GetRasterBand(1)->GetRasterDataType() == GDT_UInt16 && nBitsPerSample != 16) ||
         (GetRasterBand(1)->GetRasterDataType() == GDT_UInt32 && nBitsPerSample != 32) )
    {
        for (int i = 0; i < nBands; ++i)
            static_cast<GTiffRasterBand*>(GetRasterBand(i+1))->oGTiffMDMD.SetMetadataItem( "NBITS",
                                                 CPLString().Printf( "%d", (int)nBitsPerSample ),
                                                 "IMAGE_STRUCTURE" );
    }

    if( bMinIsWhite )
        oGTiffMDMD.SetMetadataItem( "MINISWHITE", "YES", "IMAGE_STRUCTURE" );

    if( TIFFGetField( hTIFF, TIFFTAG_GDAL_METADATA, &pszText ) )
    {
        CPLXMLNode *psRoot = CPLParseXMLString( pszText );
        CPLXMLNode *psItem = NULL;

        if( psRoot != NULL && psRoot->eType == CXT_Element
            && EQUAL(psRoot->pszValue,"GDALMetadata") )
            psItem = psRoot->psChild;

        for( ; psItem != NULL; psItem = psItem->psNext )
        {

            if( psItem->eType != CXT_Element
                || !EQUAL(psItem->pszValue,"Item") )
                continue;

            const char *pszKey = CPLGetXMLValue( psItem, "name", NULL );
            const char *pszValue = CPLGetXMLValue( psItem, NULL, NULL );
            const int nBand = atoi(CPLGetXMLValue( psItem, "sample", "-1" )) + 1;
            const char *pszRole = CPLGetXMLValue( psItem, "role", "" );
            const char *pszDomain = CPLGetXMLValue( psItem, "domain", "" );

            if( pszKey == NULL || pszValue == NULL )
                continue;

            bool bIsXML = false;

            if( STARTS_WITH_CI(pszDomain, "xml:") )
                bIsXML = TRUE;

            char *pszUnescapedValue
                = CPLUnescapeString( pszValue, NULL, CPLES_XML );
            if( nBand == 0 )
            {
                if( bIsXML )
                {
                    char *apszMD[2] = { pszUnescapedValue, NULL };
                    oGTiffMDMD.SetMetadata( apszMD, pszDomain );
                }
                else
                    oGTiffMDMD.SetMetadataItem( pszKey, pszUnescapedValue, pszDomain );
            }
            else
            {
                GTiffRasterBand *poBand = static_cast<GTiffRasterBand*>(GetRasterBand(nBand));
                if( poBand != NULL )
                {
                    if( EQUAL(pszRole,"scale") )
                    {
                        poBand->bHaveOffsetScale = TRUE;
                        poBand->dfScale = CPLAtofM(pszUnescapedValue);
                    }
                    else if( EQUAL(pszRole,"offset") )
                    {
                        poBand->bHaveOffsetScale = TRUE;
                        poBand->dfOffset = CPLAtofM(pszUnescapedValue);
                    }
                    else if( EQUAL(pszRole,"unittype") )
                        poBand->osUnitType = pszUnescapedValue;
                    else if( EQUAL(pszRole,"description") )
                        poBand->osDescription = pszUnescapedValue;
                    else
                    {
                        if( bIsXML )
                        {
                            char *apszMD[2] = { pszUnescapedValue, NULL };
                            poBand->oGTiffMDMD.SetMetadata( apszMD, pszDomain );
                        }
                        else
                            poBand->oGTiffMDMD.SetMetadataItem(pszKey,pszUnescapedValue,
                                                    pszDomain );
                    }
                }
            }
            CPLFree( pszUnescapedValue );
        }

        CPLDestroyXMLNode( psRoot );
    }

    if( bStreamingIn )
    {
        toff_t* panOffsets = NULL;
        TIFFGetField( hTIFF, (TIFFIsTiled( hTIFF )) ? TIFFTAG_TILEOFFSETS : TIFFTAG_STRIPOFFSETS , &panOffsets );
        if( panOffsets )
        {
            int nBlockCount = ( TIFFIsTiled(hTIFF) ) ? TIFFNumberOfTiles(hTIFF) : TIFFNumberOfStrips(hTIFF);
            for(int i=1;i<nBlockCount;i++)
            {
                if( panOffsets[i] < panOffsets[i-1] )
                {
                    oGTiffMDMD.SetMetadataItem("UNORDERED_BLOCKS", "YES", "TIFF");
                    CPLDebug("GTIFF",
                             "Offset of block %d is lower than previous block. Reader must be careful",
                             i);
                    break;
                }
            }
        }
    }

    CPLAssert(m_bReadGeoTransform == bReadGeoTransform);
    CPLAssert(!bMetadataChanged);
    bMetadataChanged = FALSE;

    return( CE_None );
}

/************************************************************************/
/*                         GetSiblingFiles()                            */
/************************************************************************/

char** GTiffDataset::GetSiblingFiles()
{
    char** papszSiblingFiles;
    if( m_bHasGotSiblingFiles )
        papszSiblingFiles = oOvManager.GetSiblingFiles();
    else
    {
        m_bHasGotSiblingFiles = true;
        const int nMaxFiles = atoi(CPLGetConfigOption("GDAL_READDIR_LIMIT_ON_OPEN", "1000"));
        papszSiblingFiles = VSIReadDirEx(CPLGetDirname(osFilename), nMaxFiles);
        if( nMaxFiles > 0 && CSLCount(papszSiblingFiles) > nMaxFiles )
        {
            CPLDebug("GTiff", "GDAL_READDIR_LIMIT_ON_OPEN reached on %s",
                     CPLGetDirname(osFilename));
            CSLDestroy(papszSiblingFiles);
            papszSiblingFiles = NULL;
        }
        oOvManager.TransferSiblingFiles( papszSiblingFiles );
    }
    return papszSiblingFiles;
}

/************************************************************************/
/*                     LoadGeoreferencingAndPamIfNeeded()               */
/************************************************************************/

void GTiffDataset::LoadGeoreferencingAndPamIfNeeded()

{
/* -------------------------------------------------------------------- */
/*      Get the transform or gcps from the GeoTIFF file.                */
/* -------------------------------------------------------------------- */
    if( m_bReadGeoTransform )
    {
        char    *pszTabWKT = NULL;
        double  *padfTiePoints, *padfScale, *padfMatrix;
        uint16  nCount;
        bool    bPixelIsPoint = false;
        short nRasterType;
        bool bPointGeoIgnore = false;

        m_bReadGeoTransform = false;

        if( !SetDirectory() )
            return;

        GTIF    *psGTIF = GTIFNew( hTIFF ); // I wonder how expensive this is?

        if( psGTIF )
        {
            if( GDALGTIFKeyGetSHORT(psGTIF, GTRasterTypeGeoKey, &nRasterType,
                        0, 1 ) == 1
                && nRasterType == (short) RasterPixelIsPoint )
            {
                bPixelIsPoint = true;
                bPointGeoIgnore =
                    CPLTestBool( CPLGetConfigOption("GTIFF_POINT_GEO_IGNORE",
                                                    "FALSE") );
            }

            GTIFFree( psGTIF );
        }

        adfGeoTransform[0] = 0.0;
        adfGeoTransform[1] = 1.0;
        adfGeoTransform[2] = 0.0;
        adfGeoTransform[3] = 0.0;
        adfGeoTransform[4] = 0.0;
        adfGeoTransform[5] = 1.0;

        if( TIFFGetField(hTIFF,TIFFTAG_GEOPIXELSCALE,&nCount,&padfScale )
            && nCount >= 2
            && padfScale[0] != 0.0 && padfScale[1] != 0.0 )
        {
            adfGeoTransform[1] = padfScale[0];
            adfGeoTransform[5] = - ABS(padfScale[1]);

            if( TIFFGetField(hTIFF,TIFFTAG_GEOTIEPOINTS,&nCount,&padfTiePoints )
                && nCount >= 6 )
            {
                adfGeoTransform[0] =
                    padfTiePoints[3] - padfTiePoints[0] * adfGeoTransform[1];
                adfGeoTransform[3] =
                    padfTiePoints[4] - padfTiePoints[1] * adfGeoTransform[5];

                if( bPixelIsPoint && !bPointGeoIgnore )
                {
                    adfGeoTransform[0] -= (adfGeoTransform[1] * 0.5 + adfGeoTransform[2] * 0.5);
                    adfGeoTransform[3] -= (adfGeoTransform[4] * 0.5 + adfGeoTransform[5] * 0.5);
                }

                bGeoTransformValid = TRUE;
            }
        }

        else if( TIFFGetField(hTIFF,TIFFTAG_GEOTRANSMATRIX,&nCount,&padfMatrix )
                 && nCount == 16 )
        {
            adfGeoTransform[0] = padfMatrix[3];
            adfGeoTransform[1] = padfMatrix[0];
            adfGeoTransform[2] = padfMatrix[1];
            adfGeoTransform[3] = padfMatrix[7];
            adfGeoTransform[4] = padfMatrix[4];
            adfGeoTransform[5] = padfMatrix[5];

            if( bPixelIsPoint && !bPointGeoIgnore )
            {
                adfGeoTransform[0] -= (adfGeoTransform[1] * 0.5 + adfGeoTransform[2] * 0.5);
                adfGeoTransform[3] -= (adfGeoTransform[4] * 0.5 + adfGeoTransform[5] * 0.5);
            }

            bGeoTransformValid = TRUE;
        }

/* -------------------------------------------------------------------- */
/*      Otherwise try looking for a .tab, .tfw, .tifw or .wld file.     */
/* -------------------------------------------------------------------- */
        else
        {
            char* pszGeorefFilename = NULL;

            char** papszSiblingFiles = GetSiblingFiles();

            /* Begin with .tab since it can also have projection info */
            int bTabFileOK =
                GDALReadTabFile2( osFilename, adfGeoTransform,
                                    &pszTabWKT, &nGCPCount, &pasGCPList,
                                    papszSiblingFiles, &pszGeorefFilename );

            if( bTabFileOK )
            {
                if( nGCPCount == 0 )
                    bGeoTransformValid = TRUE;
            }
            else
            {
                if( !bGeoTransformValid )
                {
                    bGeoTransformValid =
                        GDALReadWorldFile2( osFilename, NULL, adfGeoTransform,
                                            papszSiblingFiles, &pszGeorefFilename);
                }

                if( !bGeoTransformValid )
                {
                    bGeoTransformValid =
                        GDALReadWorldFile2( osFilename, "wld", adfGeoTransform,
                                            papszSiblingFiles, &pszGeorefFilename);
                }
            }

            if (pszGeorefFilename)
            {
                osGeorefFilename = pszGeorefFilename;
                CPLFree(pszGeorefFilename);
            }
        }

/* -------------------------------------------------------------------- */
/*      Check for GCPs.  Note, we will allow there to be GCPs and a     */
/*      transform in some circumstances.                                */
/* -------------------------------------------------------------------- */
        if( TIFFGetField(hTIFF,TIFFTAG_GEOTIEPOINTS,&nCount,&padfTiePoints )
            && !bGeoTransformValid )
        {
            nGCPCount = nCount / 6;
            pasGCPList = (GDAL_GCP *) CPLCalloc(sizeof(GDAL_GCP),nGCPCount);

            for( int iGCP = 0; iGCP < nGCPCount; iGCP++ )
            {
                char    szID[32];

                snprintf( szID, sizeof(szID), "%d", iGCP+1 );
                pasGCPList[iGCP].pszId = CPLStrdup( szID );
                pasGCPList[iGCP].pszInfo = CPLStrdup("");
                pasGCPList[iGCP].dfGCPPixel = padfTiePoints[iGCP*6+0];
                pasGCPList[iGCP].dfGCPLine = padfTiePoints[iGCP*6+1];
                pasGCPList[iGCP].dfGCPX = padfTiePoints[iGCP*6+3];
                pasGCPList[iGCP].dfGCPY = padfTiePoints[iGCP*6+4];
                pasGCPList[iGCP].dfGCPZ = padfTiePoints[iGCP*6+5];

                if( bPixelIsPoint && !bPointGeoIgnore )
                {
                    pasGCPList[iGCP].dfGCPPixel -= 0.5;
                    pasGCPList[iGCP].dfGCPLine -= 0.5;
                }
            }
        }

/* -------------------------------------------------------------------- */
/*      Did we find a tab file?  If so we will use it's coordinate      */
/*      system and give it precedence.                                  */
/* -------------------------------------------------------------------- */
        if( pszTabWKT != NULL
            && (pszProjection == NULL || pszProjection[0] == '\0') )
        {
            CPLFree( pszProjection );
            pszProjection = pszTabWKT;
            pszTabWKT = NULL;
            bLookedForProjection = TRUE;
        }

        CPLFree( pszTabWKT );
    }



    if( m_bLoadPam )
    {
        m_bLoadPam = false;

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
        CPLAssert(!bColorProfileMetadataChanged);
        CPLAssert(!bMetadataChanged);
        CPLAssert(!bGeoTIFFInfoChanged);
        CPLAssert(!bNoDataChanged);

        TryLoadXML( GetSiblingFiles() );
        ApplyPamInfo();

        bColorProfileMetadataChanged = FALSE;
        bMetadataChanged = FALSE;
        bGeoTIFFInfoChanged = FALSE;
        bNoDataChanged = FALSE;

        int i;
        for(i=1;i<=nBands;i++)
        {
            GTiffRasterBand* poBand = (GTiffRasterBand*) GetRasterBand(i);

            /* Load scale, offset and unittype from PAM if available */
            if (!poBand->bHaveOffsetScale)
            {
                poBand->dfScale = poBand->GDALPamRasterBand::GetScale(&poBand->bHaveOffsetScale);
                poBand->dfOffset = poBand->GDALPamRasterBand::GetOffset();
            }
            if (poBand->osUnitType.size() == 0)
            {
                const char* pszUnitType = poBand->GDALPamRasterBand::GetUnitType();
                if (pszUnitType)
                    poBand->osUnitType = pszUnitType;
            }

            GDALColorInterp ePAMColorInterp = poBand->GDALPamRasterBand::GetColorInterpretation();
            if( ePAMColorInterp != GCI_Undefined )
                poBand->eBandInterp = ePAMColorInterp;
        }
    }
}

/************************************************************************/
/*                          ScanDirectories()                           */
/*                                                                      */
/*      Scan through all the directories finding overviews, masks       */
/*      and subdatasets.                                                */
/************************************************************************/

void GTiffDataset::ScanDirectories()

{
/* -------------------------------------------------------------------- */
/*      We only scan once.  We do not scan for non-base datasets.       */
/* -------------------------------------------------------------------- */
    if( !bScanDeferred )
        return;

    bScanDeferred = FALSE;

    if( !bBase )
        return;

    if( TIFFLastDirectory( hTIFF ) )
        return;

    CPLDebug( "GTiff", "ScanDirectories()" );

/* ==================================================================== */
/*      Scan all directories.                                           */
/* ==================================================================== */
    CPLStringList aosSubdatasets;
    int  iDirIndex = 0;

    FlushDirectory();
    while( !TIFFLastDirectory( hTIFF )
           && (iDirIndex == 0 || TIFFReadDirectory( hTIFF ) != 0) )
    {
        /* Only libtiff 4.0.4 can handle between 32768 and 65535 directories */
#if !defined(INTERNAL_LIBTIFF) && (!defined(TIFFLIB_VERSION) || (TIFFLIB_VERSION < 20120922))
        if( iDirIndex == 32768 )
            break;
#endif
        toff_t nThisDir = TIFFCurrentDirOffset(hTIFF);
        uint32 nSubType = 0;

        *ppoActiveDSRef = NULL; // our directory no longer matches this ds

        iDirIndex++;

        if( !TIFFGetField(hTIFF, TIFFTAG_SUBFILETYPE, &nSubType) )
            nSubType = 0;

        /* Embedded overview of the main image */
        if ((nSubType & FILETYPE_REDUCEDIMAGE) != 0 &&
            (nSubType & FILETYPE_MASK) == 0 &&
            iDirIndex != 1 &&
            nOverviewCount < 30 /* to avoid DoS */ )
        {
            GTiffDataset *poODS = new GTiffDataset();
            if( poODS->OpenOffset( hTIFF, ppoActiveDSRef, nThisDir, FALSE,
                                   eAccess ) != CE_None
                || poODS->GetRasterCount() != GetRasterCount() )
            {
                delete poODS;
            }
            else
            {
                CPLDebug( "GTiff", "Opened %dx%d overview.\n",
                          poODS->GetRasterXSize(), poODS->GetRasterYSize());
                nOverviewCount++;
                papoOverviewDS = (GTiffDataset **)
                    CPLRealloc(papoOverviewDS,
                               nOverviewCount * (sizeof(void*)));
                papoOverviewDS[nOverviewCount-1] = poODS;
                poODS->poBaseDS = this;
            }
        }

        /* Embedded mask of the main image */
        else if ((nSubType & FILETYPE_MASK) != 0 &&
                 (nSubType & FILETYPE_REDUCEDIMAGE) == 0 &&
                 iDirIndex != 1 &&
                 poMaskDS == NULL )
        {
            poMaskDS = new GTiffDataset();

            /* The TIFF6 specification - page 37 - only allows 1 SamplesPerPixel and 1 BitsPerSample
               Here we support either 1 or 8 bit per sample
               and we support either 1 sample per pixel or as many samples as in the main image
               We don't check the value of the PhotometricInterpretation tag, which should be
               set to "Transparency mask" (4) according to the specification (page 36)
               ... But the TIFF6 specification allows image masks to have a higher resolution than
               the main image, what we don't support here
            */

            if( poMaskDS->OpenOffset( hTIFF, ppoActiveDSRef, nThisDir,
                                      FALSE, eAccess ) != CE_None
                || poMaskDS->GetRasterCount() == 0
                || !(poMaskDS->GetRasterCount() == 1 || poMaskDS->GetRasterCount() == GetRasterCount())
                || poMaskDS->GetRasterXSize() != GetRasterXSize()
                || poMaskDS->GetRasterYSize() != GetRasterYSize()
                || poMaskDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
            {
                delete poMaskDS;
                poMaskDS = NULL;
            }
            else
            {
                CPLDebug( "GTiff", "Opened band mask.\n");
                poMaskDS->poBaseDS = this;

                poMaskDS->bPromoteTo8Bits = CPLTestBool(CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK_TO_8BIT", "YES"));
            }
        }

        /* Embedded mask of an overview */
        /* The TIFF6 specification allows the combination of the FILETYPE_xxxx masks */
        else if ((nSubType & FILETYPE_REDUCEDIMAGE) != 0 &&
                 (nSubType & FILETYPE_MASK) != 0 &&
                 iDirIndex != 1)
        {
            GTiffDataset* poDS = new GTiffDataset();
            if( poDS->OpenOffset( hTIFF, ppoActiveDSRef, nThisDir, FALSE,
                                  eAccess ) != CE_None
                || poDS->GetRasterCount() == 0
                || poDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
            {
                delete poDS;
            }
            else
            {
                int i;
                for(i=0;i<nOverviewCount;i++)
                {
                    if (((GTiffDataset*)papoOverviewDS[i])->poMaskDS == NULL &&
                        poDS->GetRasterXSize() == papoOverviewDS[i]->GetRasterXSize() &&
                        poDS->GetRasterYSize() == papoOverviewDS[i]->GetRasterYSize() &&
                        (poDS->GetRasterCount() == 1 || poDS->GetRasterCount() == GetRasterCount()))
                    {
                        CPLDebug( "GTiff", "Opened band mask for %dx%d overview.\n",
                                  poDS->GetRasterXSize(), poDS->GetRasterYSize());
                        ((GTiffDataset*)papoOverviewDS[i])->poMaskDS = poDS;
                        poDS->bPromoteTo8Bits = CPLTestBool(CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK_TO_8BIT", "YES"));
                        poDS->poBaseDS = this;
                        break;
                    }
                }
                if (i == nOverviewCount)
                {
                    delete poDS;
                }
            }
        }
        else if( nSubType == 0 || nSubType == FILETYPE_PAGE ) {
            uint32 nXSize, nYSize;
            uint16 nSPP;

            TIFFGetField( hTIFF, TIFFTAG_IMAGEWIDTH, &nXSize );
            TIFFGetField( hTIFF, TIFFTAG_IMAGELENGTH, &nYSize );
            if( !TIFFGetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, &nSPP ) )
                nSPP = 1;

            CPLString osName, osDesc;
            osName.Printf( "SUBDATASET_%d_NAME=GTIFF_DIR:%d:%s",
                           iDirIndex, iDirIndex, osFilename.c_str() );
            osDesc.Printf( "SUBDATASET_%d_DESC=Page %d (%dP x %dL x %dB)",
                           iDirIndex, iDirIndex,
                           (int)nXSize, (int)nYSize, nSPP );

            aosSubdatasets.AddString(osName);
            aosSubdatasets.AddString(osDesc);
        }

        // Make sure we are stepping from the expected directory regardless
        // of churn done processing the above.
        if( TIFFCurrentDirOffset(hTIFF) != nThisDir )
            TIFFSetSubDirectory( hTIFF, nThisDir );
        *ppoActiveDSRef = NULL;
    }

    /* If we have a mask for the main image, loop over the overviews, and if they */
    /* have a mask, let's set this mask as an overview of the main mask... */
    if (poMaskDS != NULL)
    {
        for(int i=0;i<nOverviewCount;i++)
        {
            if (((GTiffDataset*)papoOverviewDS[i])->poMaskDS != NULL)
            {
                poMaskDS->nOverviewCount++;
                poMaskDS->papoOverviewDS = (GTiffDataset **)
                    CPLRealloc(poMaskDS->papoOverviewDS,
                               poMaskDS->nOverviewCount * (sizeof(void*)));
                poMaskDS->papoOverviewDS[poMaskDS->nOverviewCount-1] =
                    ((GTiffDataset*)papoOverviewDS[i])->poMaskDS;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Only keep track of subdatasets if we have more than one         */
/*      subdataset (pair).                                              */
/* -------------------------------------------------------------------- */
    if( aosSubdatasets.size() > 2 )
    {
        oGTiffMDMD.SetMetadata( aosSubdatasets.List(), "SUBDATASETS" );
    }
}


static int GTiffGetLZMAPreset(char** papszOptions)
{
    int nLZMAPreset = -1;
    const char* pszValue = CSLFetchNameValue( papszOptions, "LZMA_PRESET" );
    if( pszValue  != NULL )
    {
        nLZMAPreset =  atoi( pszValue );
        if (!(nLZMAPreset >= 0 && nLZMAPreset <= 9))
        {
            CPLError( CE_Warning, CPLE_IllegalArg,
                    "LZMA_PRESET=%s value not recognised, ignoring.",
                    pszValue );
            nLZMAPreset = -1;
        }
    }
    return nLZMAPreset;
}


static int GTiffGetZLevel(char** papszOptions)
{
    int nZLevel = -1;
    const char* pszValue = CSLFetchNameValue( papszOptions, "ZLEVEL" );
    if( pszValue  != NULL )
    {
        nZLevel =  atoi( pszValue );
        if (!(nZLevel >= 1 && nZLevel <= 9))
        {
            CPLError( CE_Warning, CPLE_IllegalArg,
                    "ZLEVEL=%s value not recognised, ignoring.",
                    pszValue );
            nZLevel = -1;
        }
    }
    return nZLevel;
}

static int GTiffGetJpegQuality(char** papszOptions)
{
    int nJpegQuality = -1;
    const char* pszValue = CSLFetchNameValue( papszOptions, "JPEG_QUALITY" );
    if( pszValue  != NULL )
    {
        nJpegQuality = atoi( pszValue );
        if (!(nJpegQuality >= 1 && nJpegQuality <= 100))
        {
            CPLError( CE_Warning, CPLE_IllegalArg,
                    "JPEG_QUALITY=%s value not recognised, ignoring.",
                    pszValue );
            nJpegQuality = -1;
        }
    }
    return nJpegQuality;
}

static int GTiffGetJpegTablesMode(char** papszOptions)
{
    return atoi(CSLFetchNameValueDef( papszOptions, "JPEGTABLESMODE",
                                   "1" /* JPEGTABLESMODE_QUANT */));
}

/************************************************************************/
/*                        GetDiscardLsbOption()                         */
/************************************************************************/

void GTiffDataset::GetDiscardLsbOption(char** papszOptions)
{
    const char* pszBits = CSLFetchNameValue( papszOptions, "DISCARD_LSB" );
    if( pszBits == NULL)
        return;

    if( nPhotometric == PHOTOMETRIC_PALETTE )
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "DISCARD_LSB ignored on a paletted image");
        return;
    }
    if( !(nBitsPerSample == 8 || nBitsPerSample == 16 || nBitsPerSample == 32) )
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "DISCARD_LSB ignored on non 8, 16 or 32 bits integer images");
        return;
    }

    char** papszTokens = CSLTokenizeString2( pszBits, ",", 0 );
    if( CSLCount(papszTokens) == 1 )
    {
        bHasDiscardedLsb = TRUE;
        for(int i=0;i<nBands;i++)
        {
            int nBits = atoi(papszTokens[0]);
            anMaskLsb.push_back(~((1 << nBits)-1));
            if( nBits > 1 )
                anOffsetLsb.push_back(1 << (nBits-1));
            else
                anOffsetLsb.push_back(0);
        }
    }
    else if( CSLCount(papszTokens) == nBands )
    {
        bHasDiscardedLsb = TRUE;
        for(int i=0;i<nBands;i++)
        {
            int nBits = atoi(papszTokens[i]);
            anMaskLsb.push_back(~((1 << nBits)-1));
            if( nBits > 1 )
                anOffsetLsb.push_back(1 << (nBits-1));
            else
                anOffsetLsb.push_back(0);
        }
    }
    else
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "DISCARD_LSB ignored: wrong number of components");
    }
    CSLDestroy(papszTokens);
}

/************************************************************************/
/*                            GTiffCreate()                             */
/*                                                                      */
/*      Shared functionality between GTiffDataset::Create() and         */
/*      GTiffCreateCopy() for creating TIFF file based on a set of      */
/*      options and a configuration.                                    */
/************************************************************************/

TIFF *GTiffDataset::CreateLL( const char * pszFilename,
                              int nXSize, int nYSize, int nBands,
                              GDALDataType eType,
                              double dfExtraSpaceForOverviews,
                              char **papszParmList,
                              VSILFILE** pfpL,
                              CPLString& osTmpFilename )

{
    if (!GTiffOneTimeInit())
        return NULL;

    TIFF *hTIFF;
    int                 nBlockXSize = 0, nBlockYSize = 0;
    int                 bTiled = FALSE;
    int                 nCompression = COMPRESSION_NONE;
    int                 nPredictor = PREDICTOR_NONE;
    uint16              nSampleFormat;
    int nPlanar;
    const char          *pszValue;
    const char          *pszProfile;
    int                 bCreateBigTIFF = FALSE;

/* -------------------------------------------------------------------- */
/*      Blow on a few errors.                                           */
/* -------------------------------------------------------------------- */
    if( nXSize < 1 || nYSize < 1 || nBands < 1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Attempt to create %dx%dx%d TIFF file, but width, height and bands\n"
                  "must be positive.",
                  nXSize, nYSize, nBands );

        return NULL;
    }

    if (nBands > 65535)
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Attempt to create %dx%dx%d TIFF file, but bands\n"
                  "must be lesser or equal to 65535.",
                  nXSize, nYSize, nBands );

        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Setup values based on options.                                  */
/* -------------------------------------------------------------------- */
    pszProfile = CSLFetchNameValue(papszParmList,"PROFILE");
    if( pszProfile == NULL )
        pszProfile = "GDALGeoTIFF";

    if( CSLFetchBoolean( papszParmList, "TILED", FALSE ) )
        bTiled = TRUE;

    pszValue = CSLFetchNameValue(papszParmList,"BLOCKXSIZE");
    if( pszValue != NULL )
        nBlockXSize = atoi( pszValue );

    pszValue = CSLFetchNameValue(papszParmList,"BLOCKYSIZE");
    if( pszValue != NULL )
        nBlockYSize = atoi( pszValue );

    pszValue = CSLFetchNameValue(papszParmList,"INTERLEAVE");
    if( pszValue != NULL )
    {
        if( EQUAL( pszValue, "PIXEL" ) )
            nPlanar = PLANARCONFIG_CONTIG;
        else if( EQUAL( pszValue, "BAND" ) )
            nPlanar = PLANARCONFIG_SEPARATE;
        else
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "INTERLEAVE=%s unsupported, value must be PIXEL or BAND.",
                      pszValue );
            return NULL;
        }
    }
    else
    {
        nPlanar = PLANARCONFIG_CONTIG;
    }

    pszValue = CSLFetchNameValue( papszParmList, "COMPRESS" );
    if( pszValue  != NULL )
    {
        nCompression = GTIFFGetCompressionMethod(pszValue, "COMPRESS");
        if (nCompression < 0)
            return NULL;
    }

    pszValue = CSLFetchNameValue( papszParmList, "PREDICTOR" );
    if( pszValue  != NULL )
        nPredictor =  atoi( pszValue );

    int nZLevel = GTiffGetZLevel(papszParmList);
    int nLZMAPreset = GTiffGetLZMAPreset(papszParmList);
    int nJpegQuality = GTiffGetJpegQuality(papszParmList);
    int nJpegTablesMode = GTiffGetJpegTablesMode(papszParmList);

/* -------------------------------------------------------------------- */
/*      Streaming related code                                          */
/* -------------------------------------------------------------------- */
    int bStreaming = ( strcmp(pszFilename, "/vsistdout/") == 0 ||
                       CSLFetchBoolean(papszParmList, "STREAMABLE_OUTPUT", FALSE) );
#ifdef S_ISFIFO
    if( !bStreaming )
    {
        VSIStatBufL sStat;
        if( VSIStatExL(pszFilename, &sStat, VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
             S_ISFIFO(sStat.st_mode) )
        {
            bStreaming = TRUE;
        }
    }
#endif
    if( bStreaming &&
        !EQUAL("NONE", CSLFetchNameValueDef(papszParmList, "COMPRESS", "NONE")) )
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Streaming only supported to uncompressed TIFF");
        return NULL;
    }
    if( bStreaming &&
        CSLFetchBoolean(papszParmList, "SPARSE_OK", FALSE) )
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Streaming not supported with SPARSE_OK");
        return NULL;
    }
    if( bStreaming &&
        CSLFetchBoolean(papszParmList, "COPY_SRC_OVERVIEWS", FALSE) )
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Streaming not supported with COPY_SRC_OVERVIEWS");
        return NULL;
    }
    if( bStreaming )
    {
        static int nCounter = 0;
        osTmpFilename = CPLSPrintf("/vsimem/vsistdout_%d.tif",  ++nCounter);
        pszFilename = osTmpFilename.c_str();
    }

/* -------------------------------------------------------------------- */
/*      Compute the uncompressed size.                                  */
/* -------------------------------------------------------------------- */
    double  dfUncompressedImageSize
        = nXSize * ((double)nYSize) * nBands * (GDALGetDataTypeSize(eType)/8);
    dfUncompressedImageSize += dfExtraSpaceForOverviews;

    if( nCompression == COMPRESSION_NONE
        && dfUncompressedImageSize > 4200000000.0 )
    {
#ifndef BIGTIFF_SUPPORT
        CPLError( CE_Failure, CPLE_NotSupported,
                  "A %d pixels x %d lines x %d bands %s image would be larger than 4GB\n"
                  "but this is the largest size a TIFF can be, and BigTIFF is unavailable.\n"
                  "Creation failed.",
                  nXSize, nYSize, nBands, GDALGetDataTypeName(eType) );
        return NULL;
#endif
    }

/* -------------------------------------------------------------------- */
/*      Check free space (only for big, non sparse, uncompressed)       */
/* -------------------------------------------------------------------- */
    if( nCompression == COMPRESSION_NONE &&
        dfUncompressedImageSize >= 1e9 &&
        !CSLFetchBoolean(papszParmList, "SPARSE_OK", FALSE) &&
        CPLTestBool(CPLGetConfigOption("CHECK_DISK_FREE_SPACE", "TRUE")) )
    {
        GIntBig nFreeDiskSpace = VSIGetDiskFreeSpace(CPLGetDirname(pszFilename));
        if( nFreeDiskSpace >= 0 &&
            nFreeDiskSpace < dfUncompressedImageSize )
        {
            CPLError( CE_Failure, CPLE_FileIO,
                      "Free disk space available is " CPL_FRMT_GIB " bytes, "
                      "whereas " CPL_FRMT_GIB " are at least necessary.",
                      nFreeDiskSpace,
                      static_cast<GIntBig>(dfUncompressedImageSize) );
            return NULL;
        }
    }

/* -------------------------------------------------------------------- */
/*      Should the file be created as a bigtiff file?                   */
/* -------------------------------------------------------------------- */
    const char *pszBIGTIFF = CSLFetchNameValue(papszParmList, "BIGTIFF");

    if( pszBIGTIFF == NULL )
        pszBIGTIFF = "IF_NEEDED";

    if( EQUAL(pszBIGTIFF,"IF_NEEDED") )
    {
        if( nCompression == COMPRESSION_NONE
            && dfUncompressedImageSize > 4200000000.0 )
            bCreateBigTIFF = TRUE;
    }
    else if( EQUAL(pszBIGTIFF,"IF_SAFER") )
    {
        if( dfUncompressedImageSize > 2000000000.0 )
            bCreateBigTIFF = TRUE;
    }

    else
    {
        bCreateBigTIFF = CPLTestBool( pszBIGTIFF );
        if (!bCreateBigTIFF && nCompression == COMPRESSION_NONE &&
             dfUncompressedImageSize > 4200000000.0 )
        {
            CPLError( CE_Failure, CPLE_NotSupported,
                "The TIFF file will be larger than 4GB, so BigTIFF is necessary.\n"
                "Creation failed.");
            return NULL;
        }
    }

#ifndef BIGTIFF_SUPPORT
    if( bCreateBigTIFF )
    {
        CPLError( CE_Warning, CPLE_NotSupported,
                  "BigTIFF requested, but GDAL built without BigTIFF\n"
                  "enabled libtiff, request ignored." );
        bCreateBigTIFF = FALSE;
    }
#endif

    if( bCreateBigTIFF )
        CPLDebug( "GTiff", "File being created as a BigTIFF." );

/* -------------------------------------------------------------------- */
/*      Check if the user wishes a particular endianness                */
/* -------------------------------------------------------------------- */

    int eEndianness = ENDIANNESS_NATIVE;
    pszValue = CSLFetchNameValue(papszParmList, "ENDIANNESS");
    if ( pszValue == NULL )
        pszValue = CPLGetConfigOption( "GDAL_TIFF_ENDIANNESS", NULL );
    if ( pszValue != NULL )
    {
        if (EQUAL(pszValue, "LITTLE"))
            eEndianness = ENDIANNESS_LITTLE;
        else if (EQUAL(pszValue, "BIG"))
            eEndianness = ENDIANNESS_BIG;
        else if (EQUAL(pszValue, "INVERTED"))
        {
#ifdef CPL_LSB
            eEndianness = ENDIANNESS_BIG;
#else
            eEndianness = ENDIANNESS_LITTLE;
#endif
        }
        else if (!EQUAL(pszValue, "NATIVE"))
        {
            CPLError( CE_Warning, CPLE_NotSupported,
                      "ENDIANNESS=%s not supported. Defaulting to NATIVE", pszValue );
        }
    }

/* -------------------------------------------------------------------- */
/*      Try opening the dataset.                                        */
/* -------------------------------------------------------------------- */

    char szOpeningFlag[5];
    strcpy(szOpeningFlag, "w+");
    if (bCreateBigTIFF)
        strcat(szOpeningFlag, "8");
    if (eEndianness == ENDIANNESS_BIG)
        strcat(szOpeningFlag, "b");
    else if (eEndianness == ENDIANNESS_LITTLE)
        strcat(szOpeningFlag, "l");

    VSILFILE* fpL = VSIFOpenL( pszFilename, "w+b" );
    if( fpL == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Attempt to create new tiff file `%s' failed: %s",
                  pszFilename, VSIStrerror(errno) );
        return NULL;
    }
    hTIFF = VSI_TIFFOpen( pszFilename, szOpeningFlag, fpL );
    if( hTIFF == NULL )
    {
        if( CPLGetLastErrorNo() == 0 )
            CPLError( CE_Failure, CPLE_OpenFailed,
                      "Attempt to create new tiff file `%s'\n"
                      "failed in XTIFFOpen().\n",
                      pszFilename );
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      How many bits per sample?  We have a special case if NBITS      */
/*      specified for GDT_Byte, GDT_UInt16, GDT_UInt32.                 */
/* -------------------------------------------------------------------- */
    int nBitsPerSample = GDALGetDataTypeSize(eType);
    if (CSLFetchNameValue(papszParmList, "NBITS") != NULL)
    {
        int nMinBits = 0, nMaxBits = 0;
        nBitsPerSample = atoi(CSLFetchNameValue(papszParmList, "NBITS"));
        if( eType == GDT_Byte  )
        {
            nMinBits = 1;
            nMaxBits = 8;
        }
        else if( eType == GDT_UInt16 )
        {
            nMinBits = 9;
            nMaxBits = 16;
        }
        else if( eType == GDT_UInt32  )
        {
            nMinBits = 17;
            nMaxBits = 32;
        }
        else
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "NBITS is not supported for data type %s",
                     GDALGetDataTypeName(eType));
            nBitsPerSample = GDALGetDataTypeSize(eType);
        }

        if (nMinBits != 0)
        {
            if (nBitsPerSample < nMinBits)
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "NBITS=%d is invalid for data type %s. Using NBITS=%d",
                         nBitsPerSample, GDALGetDataTypeName(eType), nMinBits);
                nBitsPerSample = nMinBits;
            }
            else if (nBitsPerSample > nMaxBits)
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "NBITS=%d is invalid for data type %s. Using NBITS=%d",
                         nBitsPerSample, GDALGetDataTypeName(eType), nMaxBits);
                nBitsPerSample = nMaxBits;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Do we have a custom pixel type (just used for signed byte now). */
/* -------------------------------------------------------------------- */
    const char *pszPixelType = CSLFetchNameValue( papszParmList, "PIXELTYPE" );
    if( pszPixelType == NULL )
        pszPixelType = "";

/* -------------------------------------------------------------------- */
/*      Setup some standard flags.                                      */
/* -------------------------------------------------------------------- */
    TIFFSetField( hTIFF, TIFFTAG_IMAGEWIDTH, nXSize );
    TIFFSetField( hTIFF, TIFFTAG_IMAGELENGTH, nYSize );
    TIFFSetField( hTIFF, TIFFTAG_BITSPERSAMPLE, nBitsPerSample );

    if( (eType == GDT_Byte && EQUAL(pszPixelType,"SIGNEDBYTE"))
        || eType == GDT_Int16 || eType == GDT_Int32 )
        nSampleFormat = SAMPLEFORMAT_INT;
    else if( eType == GDT_CInt16 || eType == GDT_CInt32 )
        nSampleFormat = SAMPLEFORMAT_COMPLEXINT;
    else if( eType == GDT_Float32 || eType == GDT_Float64 )
        nSampleFormat = SAMPLEFORMAT_IEEEFP;
    else if( eType == GDT_CFloat32 || eType == GDT_CFloat64 )
        nSampleFormat = SAMPLEFORMAT_COMPLEXIEEEFP;
    else
        nSampleFormat = SAMPLEFORMAT_UINT;

    TIFFSetField( hTIFF, TIFFTAG_SAMPLEFORMAT, nSampleFormat );
    TIFFSetField( hTIFF, TIFFTAG_SAMPLESPERPIXEL, nBands );
    TIFFSetField( hTIFF, TIFFTAG_PLANARCONFIG, nPlanar );

/* -------------------------------------------------------------------- */
/*      Setup Photometric Interpretation. Take this value from the user */
/*      passed option or guess correct value otherwise.                 */
/* -------------------------------------------------------------------- */
    int nSamplesAccountedFor = 1;
    int bForceColorTable = FALSE;

    pszValue = CSLFetchNameValue(papszParmList,"PHOTOMETRIC");
    if( pszValue != NULL )
    {
        if( EQUAL( pszValue, "MINISBLACK" ) )
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK );
        else if( EQUAL( pszValue, "MINISWHITE" ) )
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE );
        else if( EQUAL( pszValue, "PALETTE" ))
        {
            if( eType == GDT_Byte || eType == GDT_UInt16 )
            {
                TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE );
                nSamplesAccountedFor = 1;
                bForceColorTable = TRUE;
            }
            else
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "PHOTOMETRIC=PALETTE only compatible with Byte or UInt16");
            }
        }
        else if( EQUAL( pszValue, "RGB" ))
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB );
            nSamplesAccountedFor = 3;
        }
        else if( EQUAL( pszValue, "CMYK" ))
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_SEPARATED );
            nSamplesAccountedFor = 4;
        }
        else if( EQUAL( pszValue, "YCBCR" ))
        {
            /* Because of subsampling, setting YCBCR without JPEG compression leads */
            /* to a crash currently. Would need to make GTiffRasterBand::IWriteBlock() */
            /* aware of subsampling so that it doesn't overrun buffer size returned */
            /* by libtiff */
            if ( nCompression != COMPRESSION_JPEG )
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "Currently, PHOTOMETRIC=YCBCR requires COMPRESS=JPEG");
                XTIFFClose(hTIFF);
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
                return NULL;
            }

            if ( nPlanar == PLANARCONFIG_SEPARATE )
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "PHOTOMETRIC=YCBCR requires INTERLEAVE=PIXEL");
                XTIFFClose(hTIFF);
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
                return NULL;
            }

            /* YCBCR strictly requires 3 bands. Not less, not more */
            /* Issue an explicit error message as libtiff one is a bit cryptic : */
            /* TIFFVStripSize64:Invalid td_samplesperpixel value */
            if ( nBands != 3 )
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "PHOTOMETRIC=YCBCR requires a source raster with only 3 bands (RGB)");
                XTIFFClose(hTIFF);
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
                return NULL;
            }

            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_YCBCR );
            nSamplesAccountedFor = 3;
        }
        else if( EQUAL( pszValue, "CIELAB" ))
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB );
            nSamplesAccountedFor = 3;
        }
        else if( EQUAL( pszValue, "ICCLAB" ))
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_ICCLAB );
            nSamplesAccountedFor = 3;
        }
        else if( EQUAL( pszValue, "ITULAB" ))
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_ITULAB );
            nSamplesAccountedFor = 3;
        }
        else
        {
            CPLError( CE_Warning, CPLE_IllegalArg,
                      "PHOTOMETRIC=%s value not recognised, ignoring.\n"
                      "Set the Photometric Interpretation as MINISBLACK.",
                      pszValue );
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK );
        }

        if ( nBands < nSamplesAccountedFor )
        {
            CPLError( CE_Warning, CPLE_IllegalArg,
                      "PHOTOMETRIC=%s value does not correspond to number "
                      "of bands (%d), ignoring.\n"
                      "Set the Photometric Interpretation as MINISBLACK.",
                      pszValue, nBands );
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK );
        }
    }
    else
    {
        /*
         * If image contains 3 or 4 bands and datatype is Byte then we will
         * assume it is RGB. In all other cases assume it is MINISBLACK.
         */
        if( nBands == 3 && eType == GDT_Byte )
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB );
            nSamplesAccountedFor = 3;
        }
        else if( nBands == 4 && eType == GDT_Byte )
        {
            uint16 v[1];

            v[0] = GTiffGetAlphaValue(CSLFetchNameValue(papszParmList,"ALPHA"),
                                      DEFAULT_ALPHA_TYPE);

            TIFFSetField(hTIFF, TIFFTAG_EXTRASAMPLES, 1, v);
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB );
            nSamplesAccountedFor = 4;
        }
        else
        {
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK );
            nSamplesAccountedFor = 1;
        }
    }

/* -------------------------------------------------------------------- */
/*      If there are extra samples, we need to mark them with an        */
/*      appropriate extrasamples definition here.                       */
/* -------------------------------------------------------------------- */
    if( nBands > nSamplesAccountedFor )
    {
        int nExtraSamples = nBands - nSamplesAccountedFor;

        uint16 *v = (uint16 *) CPLMalloc( sizeof(uint16) * nExtraSamples );

        v[0] = GTiffGetAlphaValue(CSLFetchNameValue(papszParmList, "ALPHA"),
                                  EXTRASAMPLE_UNSPECIFIED);

        for( int i = 1; i < nExtraSamples; i++ )
            v[i] = EXTRASAMPLE_UNSPECIFIED;

        TIFFSetField(hTIFF, TIFFTAG_EXTRASAMPLES, nExtraSamples, v );

        CPLFree(v);
    }

    /* Set the ICC color profile. */
    if (!EQUAL(pszProfile,"BASELINE"))
    {
        SaveICCProfile(NULL, hTIFF, papszParmList, nBitsPerSample);
    }

    /* Set the compression method before asking the default strip size */
    /* This is useful when translating to a JPEG-In-TIFF file where */
    /* the default strip size is 8 or 16 depending on the photometric value */
    TIFFSetField( hTIFF, TIFFTAG_COMPRESSION, nCompression );

/* -------------------------------------------------------------------- */
/*      Setup tiling/stripping flags.                                   */
/* -------------------------------------------------------------------- */
    if( bTiled )
    {
        if( nBlockXSize == 0 )
            nBlockXSize = 256;

        if( nBlockYSize == 0 )
            nBlockYSize = 256;

        if (!TIFFSetField( hTIFF, TIFFTAG_TILEWIDTH, nBlockXSize ) ||
            !TIFFSetField( hTIFF, TIFFTAG_TILELENGTH, nBlockYSize ))
        {
            XTIFFClose(hTIFF);
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
            return NULL;
        }
    }
    else
    {
        uint32 nRowsPerStrip;

        if( nBlockYSize == 0 )
            nRowsPerStrip = MIN(nYSize, (int)TIFFDefaultStripSize(hTIFF,0));
        else
            nRowsPerStrip = nBlockYSize;

        TIFFSetField( hTIFF, TIFFTAG_ROWSPERSTRIP, nRowsPerStrip );
    }

/* -------------------------------------------------------------------- */
/*      Set compression related tags.                                   */
/* -------------------------------------------------------------------- */
    if ( nCompression == COMPRESSION_LZW ||
         nCompression == COMPRESSION_ADOBE_DEFLATE )
        TIFFSetField( hTIFF, TIFFTAG_PREDICTOR, nPredictor );
    if (nCompression == COMPRESSION_ADOBE_DEFLATE
        && nZLevel != -1)
        TIFFSetField( hTIFF, TIFFTAG_ZIPQUALITY, nZLevel );
    else if( nCompression == COMPRESSION_JPEG
        && nJpegQuality != -1 )
        TIFFSetField( hTIFF, TIFFTAG_JPEGQUALITY, nJpegQuality );
    else if( nCompression == COMPRESSION_LZMA && nLZMAPreset != -1)
        TIFFSetField( hTIFF, TIFFTAG_LZMAPRESET, nLZMAPreset );

    if( nCompression == COMPRESSION_JPEG )
        TIFFSetField( hTIFF, TIFFTAG_JPEGTABLESMODE, nJpegTablesMode );

/* -------------------------------------------------------------------- */
/*      If we forced production of a file with photometric=palette,     */
/*      we need to push out a default color table.                      */
/* -------------------------------------------------------------------- */
    if( bForceColorTable )
    {
        int nColors;

        if( eType == GDT_Byte )
            nColors = 256;
        else
            nColors = 65536;

        unsigned short *panTRed, *panTGreen, *panTBlue;

        panTRed = (unsigned short *) CPLMalloc(sizeof(unsigned short)*nColors);
        panTGreen = (unsigned short *) CPLMalloc(sizeof(unsigned short)*nColors);
        panTBlue = (unsigned short *) CPLMalloc(sizeof(unsigned short)*nColors);

        for( int iColor = 0; iColor < nColors; iColor++ )
        {
            if( eType == GDT_Byte )
            {
                panTRed[iColor] = (unsigned short) (257 * iColor);
                panTGreen[iColor] = (unsigned short) (257 * iColor);
                panTBlue[iColor] = (unsigned short) (257 * iColor);
            }
            else
            {
                panTRed[iColor] = (unsigned short) iColor;
                panTGreen[iColor] = (unsigned short) iColor;
                panTBlue[iColor] = (unsigned short) iColor;
            }
        }

        TIFFSetField( hTIFF, TIFFTAG_COLORMAP,
                      panTRed, panTGreen, panTBlue );

        CPLFree( panTRed );
        CPLFree( panTGreen );
        CPLFree( panTBlue );
    }

    /* Would perhaps works with libtiff 3.X but didn't bother trying */
    /* This trick creates a temporary in-memory file and fetches its JPEG tables */
    /* so that we can directly set them, before tif_jpeg.c compute them at */
    /* the first strip/tile writing, which is too late, since we have already */
    /* crystalized the directory. This way we avoid a directory rewriting */
#if defined(BIGTIFF_SUPPORT)
    if( nCompression == COMPRESSION_JPEG &&
        !STARTS_WITH(pszFilename, "/vsimem/gtiffdataset_jpg_tmp_") &&
        CPLTestBool(CSLFetchNameValueDef(papszParmList, "WRITE_JPEGTABLE_TAG", "YES")) )
    {
        CPLString osTmpFilenameIn;
        osTmpFilenameIn.Printf("/vsimem/gtiffdataset_jpg_tmp_%p", hTIFF);
        VSILFILE* fpTmp = NULL;
        CPLString osTmp;
        char** papszLocalParameters = NULL;
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                               "COMPRESS", "JPEG");
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                "JPEG_QUALITY", CSLFetchNameValue(papszParmList, "JPEG_QUALITY"));
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                "PHOTOMETRIC", CSLFetchNameValue(papszParmList, "PHOTOMETRIC"));
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                               "BLOCKYSIZE", "16");
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                "NBITS", CSLFetchNameValue(papszParmList, "NBITS"));
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                "JPEGTABLESMODE", CSLFetchNameValue(papszParmList, "JPEGTABLESMODE"));
        TIFF* hTIFFTmp = CreateLL( osTmpFilenameIn, 16, 16, (nBands <= 4) ? nBands : 1,
                                   eType, 0.0, papszLocalParameters, &fpTmp, osTmp );
        CSLDestroy(papszLocalParameters);
        if( hTIFFTmp )
        {
            uint16 nPhotometric;
            int nJpegTablesModeIn;
            TIFFGetField( hTIFFTmp, TIFFTAG_PHOTOMETRIC, &(nPhotometric) );
            TIFFGetField( hTIFFTmp, TIFFTAG_JPEGTABLESMODE, &nJpegTablesModeIn );
            TIFFWriteCheck( hTIFFTmp, FALSE, "CreateLL" );
            TIFFWriteDirectory( hTIFFTmp );
            TIFFSetDirectory( hTIFFTmp, 0 );
            // Now, reset quality and jpegcolormode.
            if(nJpegQuality > 0)
                TIFFSetField(hTIFFTmp, TIFFTAG_JPEGQUALITY, nJpegQuality);
            if( nPhotometric == PHOTOMETRIC_YCBCR
                && CPLTestBool( CPLGetConfigOption("CONVERT_YCBCR_TO_RGB",
                                                    "YES") ) )
            {
                TIFFSetField(hTIFFTmp, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
            }
            if (nJpegTablesModeIn >= 0 )
                TIFFSetField(hTIFFTmp, TIFFTAG_JPEGTABLESMODE, nJpegTablesModeIn);

            GByte abyZeroData[(16*16*4*3)/2];
            memset(abyZeroData, 0, (16*16*4*3)/2);
            int nBlockSize = 16 * 16 * ((nBands <= 4) ? nBands : 1);
            if( nBitsPerSample == 12 )
               nBlockSize = (nBlockSize * 3) / 2;
            TIFFWriteEncodedStrip( hTIFFTmp, 0, abyZeroData, nBlockSize);

            uint32 nJPEGTableSize = 0;
            void* pJPEGTable = NULL;
            if( TIFFGetField(hTIFFTmp, TIFFTAG_JPEGTABLES, &nJPEGTableSize, &pJPEGTable) )
                TIFFSetField(hTIFF, TIFFTAG_JPEGTABLES, nJPEGTableSize, pJPEGTable);

            float *ref;
            if( TIFFGetField(hTIFFTmp, TIFFTAG_REFERENCEBLACKWHITE, &ref) )
                TIFFSetField(hTIFF, TIFFTAG_REFERENCEBLACKWHITE, ref);

            XTIFFClose(hTIFFTmp);
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpTmp));
        }
        VSIUnlink(osTmpFilenameIn);
    }
#endif

    *pfpL = fpL;

    return( hTIFF );
}

/************************************************************************/
/*                              GuessJPEGQuality()                      */
/*                                                                      */
/*      Guess JPEG quality from JPEGTABLES tag.                         */
/************************************************************************/

static const GByte* GTIFFFindNextTable(const GByte* paby, GByte byMarker,
                                       int nLen, int* pnLenTable)
{
    for(int i = 0; i+1 < nLen; )
    {
        if( paby[i] != 0xFF )
            return NULL;
        i ++;
        if( paby[i] == 0xD8 )
        {
            i ++;
            continue;
        }
        if( i+2 >= nLen )
            return NULL;
        int nMarkerLen = paby[i+1] * 256 + paby[i+2];
        if( i+1+nMarkerLen >= nLen )
            return NULL;
        if( paby[i] == byMarker )
        {
            if( pnLenTable ) *pnLenTable = nMarkerLen;
            return paby + i + 1;
        }
        i += 1 + nMarkerLen;
    }
    return NULL;
}

/* We assume that if there are several quantization tables, they are */
/* in the same order. Which is a reasonable assumption for updating */
/* a file generated by ourselves */
static int GTIFFQuantizationTablesEqual(const GByte* paby1, int nLen1,
                                        const GByte* paby2, int nLen2)
{
    int bFound = FALSE;
    while(true)
    {
        int nLenTable1 = 0;
        int nLenTable2 = 0;
        const GByte* paby1New = GTIFFFindNextTable(paby1, 0xDB, nLen1, &nLenTable1);
        const GByte* paby2New = GTIFFFindNextTable(paby2, 0xDB, nLen2, &nLenTable2);
        if( paby1New == NULL && paby2New == NULL )
            return bFound;
        if( paby1New == NULL && paby2New != NULL )
            return FALSE;
        if( paby1New != NULL && paby2New == NULL )
            return FALSE;
        if( nLenTable1 != nLenTable2 )
            return FALSE;
        if( memcmp(paby1New, paby2New, nLenTable1) != 0 )
            return FALSE;
        paby1New += nLenTable1;
        paby2New += nLenTable2;
        nLen1 -= static_cast<int>(paby1New - paby1);
        nLen2 -= static_cast<int>(paby2New - paby2);
        paby1 = paby1New;
        paby2 = paby2New;
        bFound = TRUE;
    }
}

int GTiffDataset::GuessJPEGQuality(int& bOutHasQuantizationTable,
                                   int& bOutHasHuffmanTable)
{
    CPLAssert( nCompression == COMPRESSION_JPEG );
    uint32 nJPEGTableSize = 0;
    void* pJPEGTable = NULL;
    if( !TIFFGetField(hTIFF, TIFFTAG_JPEGTABLES, &nJPEGTableSize, &pJPEGTable) )
    {
        bOutHasQuantizationTable = FALSE;
        bOutHasHuffmanTable = FALSE;
        return -1;
    }

    bOutHasQuantizationTable = GTIFFFindNextTable((const GByte*)pJPEGTable, 0xDB, nJPEGTableSize, NULL) != NULL;
    bOutHasHuffmanTable = GTIFFFindNextTable((const GByte*)pJPEGTable, 0xC4, nJPEGTableSize, NULL) != NULL;
    if( !bOutHasQuantizationTable )
        return -1;

    char** papszLocalParameters = NULL;
    papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                            "COMPRESS", "JPEG");
    if( nPhotometric == PHOTOMETRIC_YCBCR )
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                            "PHOTOMETRIC", "YCBCR");
    else if( nPhotometric == PHOTOMETRIC_SEPARATED )
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                            "PHOTOMETRIC", "CMYK");
    papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                            "BLOCKYSIZE", "16");
    if( nBitsPerSample == 12 )
        papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                                "NBITS", "12");

    CPLString osTmpFilenameIn;
    osTmpFilenameIn.Printf("/vsimem/gtiffdataset_guess_jpeg_quality_tmp_%p", this);

    int nRet = -1;
    for(int nQuality=0;nQuality<=100 && nRet < 0;nQuality++)
    {
        VSILFILE* fpTmp = NULL;
        if( nQuality == 0 )
            papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                                   "JPEG_QUALITY", "75");
        else
            papszLocalParameters = CSLSetNameValue(papszLocalParameters,
                                "JPEG_QUALITY", CPLSPrintf("%d", nQuality));

        CPLPushErrorHandler(CPLQuietErrorHandler);
        CPLString osTmp;
        TIFF* hTIFFTmp = CreateLL( osTmpFilenameIn, 16, 16, (nBands <= 4) ? nBands : 1,
                                   GetRasterBand(1)->GetRasterDataType(), 0.0,
                                   papszLocalParameters, &fpTmp, osTmp );
        CPLPopErrorHandler();
        if( !hTIFFTmp )
        {
            break;
        }

        TIFFWriteCheck( hTIFFTmp, FALSE, "CreateLL" );
        TIFFWriteDirectory( hTIFFTmp );
        TIFFSetDirectory( hTIFFTmp, 0 );
        // Now reset jpegcolormode.
        if( nPhotometric == PHOTOMETRIC_YCBCR
            && CPLTestBool( CPLGetConfigOption("CONVERT_YCBCR_TO_RGB",
                                                "YES") ) )
        {
            TIFFSetField(hTIFFTmp, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
        }

        GByte abyZeroData[(16*16*4*3)/2];
        memset(abyZeroData, 0, (16*16*4*3)/2);
        int nBlockSize = (16 * 16 * ((nBands <= 4) ? nBands : 1) * nBitsPerSample) / 8;
        TIFFWriteEncodedStrip( hTIFFTmp, 0, abyZeroData, nBlockSize);

        uint32 nJPEGTableSizeTry = 0;
        void* pJPEGTableTry = NULL;
        if( TIFFGetField(hTIFFTmp, TIFFTAG_JPEGTABLES,
                         &nJPEGTableSizeTry, &pJPEGTableTry) )
        {
            if( GTIFFQuantizationTablesEqual((GByte*)pJPEGTable, nJPEGTableSize,
                                             (GByte*)pJPEGTableTry, nJPEGTableSizeTry) )
            {
                nRet = (nQuality == 0 ) ? 75 : nQuality;
            }
        }

        XTIFFClose(hTIFFTmp);
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpTmp));
    }

    CSLDestroy(papszLocalParameters);
    VSIUnlink(osTmpFilenameIn);

    return nRet;
}

/************************************************************************/
/*                               Create()                               */
/*                                                                      */
/*      Create a new GeoTIFF or TIFF file.                              */
/************************************************************************/

GDALDataset *GTiffDataset::Create( const char * pszFilename,
                                   int nXSize, int nYSize, int nBands,
                                   GDALDataType eType,
                                   char **papszParmList )

{
    GTiffDataset *poDS;
    TIFF *hTIFF;
    VSILFILE* fpL = NULL;
    CPLString           osTmpFilename;

/* -------------------------------------------------------------------- */
/*      Create the underlying TIFF file.                                */
/* -------------------------------------------------------------------- */
    hTIFF = CreateLL( pszFilename,
                      nXSize, nYSize, nBands,
                      eType, 0, papszParmList, &fpL, osTmpFilename );
    int bStreaming = (osTmpFilename.size() != 0);

    if( hTIFF == NULL )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Create the new GTiffDataset object.                             */
/* -------------------------------------------------------------------- */
    poDS = new GTiffDataset();
    poDS->hTIFF = hTIFF;
    poDS->fpL = fpL;
    if( bStreaming )
    {
        poDS->bStreamingOut = TRUE;
        poDS->osTmpFilename = osTmpFilename;
        poDS->fpToWrite = VSIFOpenL( pszFilename, "wb" );
        if( poDS->fpToWrite == NULL )
        {
            VSIUnlink(osTmpFilename);
            delete poDS;
            return NULL;
        }
    }
    poDS->poActiveDS = poDS;
    poDS->ppoActiveDSRef = &(poDS->poActiveDS);

    poDS->nRasterXSize = nXSize;
    poDS->nRasterYSize = nYSize;
    poDS->eAccess = GA_Update;
    poDS->bCrystalized = FALSE;
    poDS->nSamplesPerPixel = (uint16) nBands;
    poDS->osFilename = pszFilename;

    /* Avoid premature crystalization that will cause directory re-writing */
    /* if GetProjectionRef() or GetGeoTransform() are called on the newly created GeoTIFF */
    poDS->bLookedForProjection = TRUE;

    TIFFGetField( hTIFF, TIFFTAG_SAMPLEFORMAT, &(poDS->nSampleFormat) );
    TIFFGetField( hTIFF, TIFFTAG_PLANARCONFIG, &(poDS->nPlanarConfig) );
    // Weird that we need this, but otherwise we get a Valgrind warning on tiff_write_124
    if( !TIFFGetField( hTIFF, TIFFTAG_PHOTOMETRIC, &(poDS->nPhotometric) ) )
        poDS->nPhotometric = PHOTOMETRIC_MINISBLACK;
    TIFFGetField( hTIFF, TIFFTAG_BITSPERSAMPLE, &(poDS->nBitsPerSample) );
    TIFFGetField( hTIFF, TIFFTAG_COMPRESSION, &(poDS->nCompression) );

    if( TIFFIsTiled(hTIFF) )
    {
        TIFFGetField( hTIFF, TIFFTAG_TILEWIDTH, &(poDS->nBlockXSize) );
        TIFFGetField( hTIFF, TIFFTAG_TILELENGTH, &(poDS->nBlockYSize) );
    }
    else
    {
        if( !TIFFGetField( hTIFF, TIFFTAG_ROWSPERSTRIP,
                           &(poDS->nRowsPerStrip) ) )
            poDS->nRowsPerStrip = 1; /* dummy value */

        poDS->nBlockXSize = nXSize;
        poDS->nBlockYSize = MIN((int)poDS->nRowsPerStrip,nYSize);
    }

    poDS->nBlocksPerBand =
        DIV_ROUND_UP(nYSize, poDS->nBlockYSize)
        * DIV_ROUND_UP(nXSize, poDS->nBlockXSize);

    if( CSLFetchNameValue( papszParmList, "PROFILE" ) != NULL )
        poDS->osProfile = CSLFetchNameValue( papszParmList, "PROFILE" );

/* -------------------------------------------------------------------- */
/*      YCbCr JPEG compressed images should be translated on the fly    */
/*      to RGB by libtiff/libjpeg unless specifically requested         */
/*      otherwise.                                                      */
/* -------------------------------------------------------------------- */
    if( poDS->nCompression == COMPRESSION_JPEG
        && poDS->nPhotometric == PHOTOMETRIC_YCBCR
        && CPLTestBool( CPLGetConfigOption("CONVERT_YCBCR_TO_RGB",
                                              "YES") ) )
    {
        int nColorMode;

        poDS->SetMetadataItem( "SOURCE_COLOR_SPACE", "YCbCr", "IMAGE_STRUCTURE" );
        if ( !TIFFGetField( hTIFF, TIFFTAG_JPEGCOLORMODE, &nColorMode ) ||
             nColorMode != JPEGCOLORMODE_RGB )
            TIFFSetField(hTIFF, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
    }

/* -------------------------------------------------------------------- */
/*      Read palette back as a color table if it has one.               */
/* -------------------------------------------------------------------- */
    unsigned short *panRed, *panGreen, *panBlue;

    if( poDS->nPhotometric == PHOTOMETRIC_PALETTE
        && TIFFGetField( hTIFF, TIFFTAG_COLORMAP,
                         &panRed, &panGreen, &panBlue) )
    {
        int nColorCount;
        GDALColorEntry oEntry;

        poDS->poColorTable = new GDALColorTable();

        nColorCount = 1 << poDS->nBitsPerSample;

        for( int iColor = nColorCount - 1; iColor >= 0; iColor-- )
        {
            oEntry.c1 = panRed[iColor] / 256;
            oEntry.c2 = panGreen[iColor] / 256;
            oEntry.c3 = panBlue[iColor] / 256;
            oEntry.c4 = 255;

            poDS->poColorTable->SetColorEntry( iColor, &oEntry );
        }
    }

/* -------------------------------------------------------------------- */
/*      Do we want to ensure all blocks get written out on close to     */
/*      avoid sparse files?                                             */
/* -------------------------------------------------------------------- */
    if( !CSLFetchBoolean( papszParmList, "SPARSE_OK", FALSE ) )
        poDS->bFillEmptyTiles = TRUE;

/* -------------------------------------------------------------------- */
/*      Preserve creation options for consulting later (for instance    */
/*      to decide if a TFW file should be written).                     */
/* -------------------------------------------------------------------- */
    poDS->papszCreationOptions = CSLDuplicate( papszParmList );

    poDS->nZLevel = GTiffGetZLevel(papszParmList);
    poDS->nLZMAPreset = GTiffGetLZMAPreset(papszParmList);
    poDS->nJpegQuality = GTiffGetJpegQuality(papszParmList);
    poDS->nJpegTablesMode = GTiffGetJpegTablesMode(papszParmList);
    poDS->InitCreationOrOpenOptions(papszParmList);

#if !defined(BIGTIFF_SUPPORT)
/* -------------------------------------------------------------------- */
/*      If we are writing jpeg compression we need to write some        */
/*      imagery to force the jpegtables to get created.  This is,       */
/*      likely only needed with libtiff >= 3.9.3 (#3633)                */
/* -------------------------------------------------------------------- */
    if( poDS->nCompression == COMPRESSION_JPEG
        && strstr(TIFFLIB_VERSION_STR, "Version 3.9") != NULL )
    {
        CPLDebug( "GDAL",
                  "Writing zero block to force creation of JPEG tables." );
        if( TIFFIsTiled( hTIFF ) )
        {
            int cc = TIFFTileSize( hTIFF );
            unsigned char *pabyZeros = (unsigned char *) CPLCalloc(cc,1);
            TIFFWriteEncodedTile(hTIFF, 0, pabyZeros, cc);
            CPLFree( pabyZeros );
        }
        else
        {
            int cc = TIFFStripSize( hTIFF );
            unsigned char *pabyZeros = (unsigned char *) CPLCalloc(cc,1);
            TIFFWriteEncodedStrip(hTIFF, 0, pabyZeros, cc);
            CPLFree( pabyZeros );
        }
        poDS->bDontReloadFirstBlock = TRUE;
    }
#endif

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    int iBand;

    for( iBand = 0; iBand < nBands; iBand++ )
    {
        if( poDS->nBitsPerSample == 8 ||
            poDS->nBitsPerSample == 16 ||
            poDS->nBitsPerSample == 32 ||
            poDS->nBitsPerSample == 64 ||
            poDS->nBitsPerSample == 128)
            poDS->SetBand( iBand+1, new GTiffRasterBand( poDS, iBand+1 ) );
        else
        {
            poDS->SetBand( iBand+1, new GTiffOddBitsBand( poDS, iBand+1 ) );
            poDS->GetRasterBand( iBand+1 )->
                SetMetadataItem( "NBITS",
                                 CPLString().Printf("%d",poDS->nBitsPerSample),
                                 "IMAGE_STRUCTURE" );
        }
    }

    poDS->GetDiscardLsbOption(papszParmList);

    if( poDS->nPlanarConfig == PLANARCONFIG_CONTIG && nBands != 1 )
        poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
    else
        poDS->SetMetadataItem( "INTERLEAVE", "BAND", "IMAGE_STRUCTURE" );

    poDS->oOvManager.Initialize( poDS, pszFilename );

    return( poDS );
}

/************************************************************************/
/*                             CreateCopy()                             */
/************************************************************************/

GDALDataset *
GTiffDataset::CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
                          int bStrict, char ** papszOptions,
                          GDALProgressFunc pfnProgress, void * pProgressData )

{
    TIFF *hTIFF;
    int nXSize = poSrcDS->GetRasterXSize();
    int nYSize = poSrcDS->GetRasterYSize();
    int nBands = poSrcDS->GetRasterCount();
    CPLErr      eErr = CE_None;
    uint16 nPlanarConfig;
    uint16 nBitsPerSample;
    GDALRasterBand *poPBand;

    if( poSrcDS->GetRasterCount() == 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Unable to export GeoTIFF files with zero bands." );
        return NULL;
    }

    poPBand = poSrcDS->GetRasterBand(1);
    GDALDataType eType = poPBand->GetRasterDataType();

/* -------------------------------------------------------------------- */
/*      Check, whether all bands in input dataset has the same type.    */
/* -------------------------------------------------------------------- */
    for ( int iBand = 2; iBand <= nBands; iBand++ )
    {
        if ( eType != poSrcDS->GetRasterBand(iBand)->GetRasterDataType() )
        {
            if ( bStrict )
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "Unable to export GeoTIFF file with different datatypes per\n"
                          "different bands. All bands should have the same types in TIFF." );
                return NULL;
            }
            else
            {
                CPLError( CE_Warning, CPLE_AppDefined,
                          "Unable to export GeoTIFF file with different datatypes per\n"
                          "different bands. All bands should have the same types in TIFF." );
            }
        }
    }

    if( !pfnProgress( 0.0, NULL, pProgressData ) )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Capture the profile.                                            */
/* -------------------------------------------------------------------- */
    const char          *pszProfile;
    int                 bGeoTIFF;

    pszProfile = CSLFetchNameValue(papszOptions,"PROFILE");
    if( pszProfile == NULL )
        pszProfile = "GDALGeoTIFF";

    if( !EQUAL(pszProfile,"BASELINE")
        && !EQUAL(pszProfile,"GeoTIFF")
        && !EQUAL(pszProfile,"GDALGeoTIFF") )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "PROFILE=%s not supported in GTIFF driver.",
                  pszProfile );
        return NULL;
    }

    if( EQUAL(pszProfile,"BASELINE") )
        bGeoTIFF = FALSE;
    else
        bGeoTIFF = TRUE;

/* -------------------------------------------------------------------- */
/*      Special handling for NBITS.  Copy from band metadata if found.  */
/* -------------------------------------------------------------------- */
    char     **papszCreateOptions = CSLDuplicate( papszOptions );

    if( poPBand->GetMetadataItem( "NBITS", "IMAGE_STRUCTURE" ) != NULL
        && atoi(poPBand->GetMetadataItem( "NBITS", "IMAGE_STRUCTURE" )) > 0
        && CSLFetchNameValue( papszCreateOptions, "NBITS") == NULL )
    {
        papszCreateOptions =
            CSLSetNameValue( papszCreateOptions, "NBITS",
                             poPBand->GetMetadataItem( "NBITS",
                                                       "IMAGE_STRUCTURE" ) );
    }

    if( CSLFetchNameValue( papszOptions, "PIXELTYPE" ) == NULL
        && eType == GDT_Byte
        && poPBand->GetMetadataItem( "PIXELTYPE", "IMAGE_STRUCTURE" ) )
    {
        papszCreateOptions =
            CSLSetNameValue( papszCreateOptions, "PIXELTYPE",
                             poPBand->GetMetadataItem(
                                 "PIXELTYPE", "IMAGE_STRUCTURE" ) );
    }

/* -------------------------------------------------------------------- */
/*      Color profile.  Copy from band metadata if found.              */
/* -------------------------------------------------------------------- */
    if (bGeoTIFF)
    {
        const char* pszOptionsMD[] = {
            "SOURCE_ICC_PROFILE",
            "SOURCE_PRIMARIES_RED",
            "SOURCE_PRIMARIES_GREEN",
            "SOURCE_PRIMARIES_BLUE",
            "SOURCE_WHITEPOINT",
            "TIFFTAG_TRANSFERFUNCTION_RED",
            "TIFFTAG_TRANSFERFUNCTION_GREEN",
            "TIFFTAG_TRANSFERFUNCTION_BLUE",
            "TIFFTAG_TRANSFERRANGE_BLACK",
            "TIFFTAG_TRANSFERRANGE_WHITE",
            NULL
        };

        /* Copy all the tags. Options will override tags in the source */
        int i = 0;
        while(pszOptionsMD[i] != NULL)
        {
            char const *pszMD = CSLFetchNameValue(papszOptions, pszOptionsMD[i]);
            if (pszMD == NULL)
                pszMD = poSrcDS->GetMetadataItem( pszOptionsMD[i], "COLOR_PROFILE" );

            if ((pszMD != NULL) && !EQUAL(pszMD, "") )
            {
                papszCreateOptions =
                    CSLSetNameValue( papszCreateOptions, pszOptionsMD[i], pszMD );

                /* If an ICC profile exists, other tags are not needed */
                if (EQUAL(pszOptionsMD[i], "SOURCE_ICC_PROFILE"))
                    break;
            }

            i++;
        }
    }

    int nSrcOverviews = poSrcDS->GetRasterBand(1)->GetOverviewCount();
    double dfExtraSpaceForOverviews = 0;
    if (nSrcOverviews != 0 &&
        CSLFetchBoolean(papszOptions, "COPY_SRC_OVERVIEWS", FALSE))
    {
        for(int j=1;j<=nBands;j++)
        {
            if( poSrcDS->GetRasterBand(j)->GetOverviewCount() != nSrcOverviews )
            {
                CPLError( CE_Failure, CPLE_NotSupported,
                  "COPY_SRC_OVERVIEWS cannot be used when the bands have not the same number of overview levels." );
                CSLDestroy(papszCreateOptions);
                return NULL;
            }
            for(int i=0;i<nSrcOverviews;i++)
            {
                GDALRasterBand* poOvrBand = poSrcDS->GetRasterBand(j)->GetOverview(i);
                if( poOvrBand == NULL )
                {
                    CPLError( CE_Failure, CPLE_NotSupported,
                        "COPY_SRC_OVERVIEWS cannot be used when one overview band is NULL." );
                    CSLDestroy(papszCreateOptions);
                    return NULL;
                }
                GDALRasterBand* poOvrFirstBand = poSrcDS->GetRasterBand(1)->GetOverview(i);
                if( poOvrBand->GetXSize() != poOvrFirstBand->GetXSize() ||
                    poOvrBand->GetYSize() != poOvrFirstBand->GetYSize() )
                {
                    CPLError( CE_Failure, CPLE_NotSupported,
                    "COPY_SRC_OVERVIEWS cannot be used when the overview bands have not the same dimensions among bands." );
                    CSLDestroy(papszCreateOptions);
                    return NULL;
                }
            }
        }

        for(int i=0;i<nSrcOverviews;i++)
        {
            dfExtraSpaceForOverviews += ((double)poSrcDS->GetRasterBand(1)->GetOverview(i)->GetXSize()) *
                                        poSrcDS->GetRasterBand(1)->GetOverview(i)->GetYSize();
        }
        dfExtraSpaceForOverviews *= nBands * (GDALGetDataTypeSize(eType) / 8);
    }

/* -------------------------------------------------------------------- */
/*      Should we use optimized way of copying from an input JPEG       */
/*      dataset ?                                                       */
/* -------------------------------------------------------------------- */
#if defined(HAVE_LIBJPEG)
    int bCopyFromJPEG = FALSE;
#endif
#if defined(HAVE_LIBJPEG) || defined(JPEG_DIRECT_COPY)
    int bDirectCopyFromJPEG = FALSE;
#endif

    /* Note: JPEG_DIRECT_COPY is not defined by default, because it is mainly */
    /* useful for debugging purposes */
#ifdef JPEG_DIRECT_COPY
    if (CSLFetchBoolean(papszCreateOptions, "JPEG_DIRECT_COPY", FALSE) &&
        GTIFF_CanDirectCopyFromJPEG(poSrcDS, papszCreateOptions))
    {
        CPLDebug("GTiff", "Using special direct copy mode from a JPEG dataset");

        bDirectCopyFromJPEG = TRUE;
    }
#endif

#ifdef HAVE_LIBJPEG
    /* when CreateCopy'ing() from a JPEG dataset, and asking for COMPRESS=JPEG, */
    /* use DCT coefficients (unless other options are incompatible, like strip/tile dimensions, */
    /* specifying JPEG_QUALITY option, incompatible PHOTOMETRIC with the source colorspace, etc...) */
    /* to avoid the lossy steps involved by decompression/recompression */
    if (!bDirectCopyFromJPEG && GTIFF_CanCopyFromJPEG(poSrcDS, papszCreateOptions))
    {
        CPLDebug("GTiff", "Using special copy mode from a JPEG dataset");

        bCopyFromJPEG = TRUE;
    }
#endif

/* -------------------------------------------------------------------- */
/*      Create the file.                                                */
/* -------------------------------------------------------------------- */
    VSILFILE* fpL = NULL;
    CPLString osTmpFilename;

    hTIFF = CreateLL( pszFilename, nXSize, nYSize, nBands,
                      eType, dfExtraSpaceForOverviews, papszCreateOptions, &fpL, osTmpFilename );
    int bStreaming = (osTmpFilename.size() != 0);

    CSLDestroy( papszCreateOptions );
    papszCreateOptions = NULL;

    if( hTIFF == NULL )
    {
        if( bStreaming ) VSIUnlink(osTmpFilename);
        return NULL;
    }

    TIFFGetField( hTIFF, TIFFTAG_PLANARCONFIG, &nPlanarConfig );
    TIFFGetField(hTIFF, TIFFTAG_BITSPERSAMPLE, &nBitsPerSample );

    uint16      nCompression;

    if( !TIFFGetField( hTIFF, TIFFTAG_COMPRESSION, &(nCompression) ) )
        nCompression = COMPRESSION_NONE;

    bool bForcePhotometric =
        CSLFetchNameValue(papszOptions,"PHOTOMETRIC") != NULL;

/* -------------------------------------------------------------------- */
/*      If the source is RGB, then set the PHOTOMETRIC_RGB value        */
/* -------------------------------------------------------------------- */
    if( nBands >= 3 && !bForcePhotometric &&
        nCompression != COMPRESSION_JPEG &&
        poSrcDS->GetRasterBand(1)->GetColorInterpretation() == GCI_RedBand &&
        poSrcDS->GetRasterBand(2)->GetColorInterpretation() == GCI_GreenBand &&
        poSrcDS->GetRasterBand(3)->GetColorInterpretation() == GCI_BlueBand )
    {
        TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB );

        /* We need to update the number of extra samples */
        uint16 *v;
        uint16 count = 0;
        uint16 nNewExtraSamplesCount = static_cast<uint16>(nBands - 3);
        if( nBands >= 4 &&
            TIFFGetField( hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v ) &&
            count > nNewExtraSamplesCount )
        {
            uint16* pasNewExtraSamples =
                (uint16*)CPLMalloc( nNewExtraSamplesCount * sizeof(uint16) );
            memcpy( pasNewExtraSamples, v + count - nNewExtraSamplesCount,
                    nNewExtraSamplesCount * sizeof(uint16) );

            TIFFSetField(hTIFF, TIFFTAG_EXTRASAMPLES, nNewExtraSamplesCount, pasNewExtraSamples);

            CPLFree(pasNewExtraSamples);
        }
    }

/* -------------------------------------------------------------------- */
/*      Set the alpha channel if it is the last one.                    */
/* -------------------------------------------------------------------- */
    if( poSrcDS->GetRasterBand(nBands)->GetColorInterpretation()==GCI_AlphaBand )
    {
        uint16 *v;
        uint16 count = 0;
        if( TIFFGetField( hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v ) )
        {
            int nBaseSamples = nBands - count;
            if( nBands > nBaseSamples && nBands - nBaseSamples - 1 < count )
            {
                // We need to allocate a new array as (current) libtiff
                // versions will not like that we reuse the array we got from
                // TIFFGetField().

                uint16* pasNewExtraSamples =
                    (uint16*)CPLMalloc( count * sizeof(uint16) );
                memcpy( pasNewExtraSamples, v, count * sizeof(uint16) );
                pasNewExtraSamples[nBands - nBaseSamples - 1] =
                    GTiffGetAlphaValue(CPLGetConfigOption("GTIFF_ALPHA", CSLFetchNameValue(papszOptions,"ALPHA")),
                                            DEFAULT_ALPHA_TYPE);

                TIFFSetField(hTIFF, TIFFTAG_EXTRASAMPLES, count, pasNewExtraSamples);

                CPLFree(pasNewExtraSamples);
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      If the output is jpeg compressed, and the input is RGB make     */
/*      sure we note that.                                              */
/* -------------------------------------------------------------------- */

    if( nCompression == COMPRESSION_JPEG )
    {
        if( nBands >= 3
            && (poSrcDS->GetRasterBand(1)->GetColorInterpretation()
                == GCI_YCbCr_YBand)
            && (poSrcDS->GetRasterBand(2)->GetColorInterpretation()
                == GCI_YCbCr_CbBand)
            && (poSrcDS->GetRasterBand(3)->GetColorInterpretation()
                == GCI_YCbCr_CrBand) )
        {
            /* do nothing ... */
        }
        else
        {
            /* we assume RGB if it isn't explicitly YCbCr */
            CPLDebug( "GTiff", "Setting JPEGCOLORMODE_RGB" );
            TIFFSetField( hTIFF, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
        }
    }

/* -------------------------------------------------------------------- */
/*      Does the source image consist of one band, with a palette?      */
/*      If so, copy over.                                               */
/* -------------------------------------------------------------------- */
    if( (nBands == 1 || nBands == 2) && poSrcDS->GetRasterBand(1)->GetColorTable() != NULL
        && eType == GDT_Byte )
    {
        unsigned short anTRed[256], anTGreen[256], anTBlue[256];
        GDALColorTable *poCT;

        poCT = poSrcDS->GetRasterBand(1)->GetColorTable();

        for( int iColor = 0; iColor < 256; iColor++ )
        {
            if( iColor < poCT->GetColorEntryCount() )
            {
                GDALColorEntry  sRGB;

                poCT->GetColorEntryAsRGB( iColor, &sRGB );

                anTRed[iColor] = (unsigned short) (257 * sRGB.c1);
                anTGreen[iColor] = (unsigned short) (257 * sRGB.c2);
                anTBlue[iColor] = (unsigned short) (257 * sRGB.c3);
            }
            else
            {
                anTRed[iColor] = anTGreen[iColor] = anTBlue[iColor] = 0;
            }
        }

        if( !bForcePhotometric )
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE );
        TIFFSetField( hTIFF, TIFFTAG_COLORMAP, anTRed, anTGreen, anTBlue );
    }
    else if( (nBands == 1 || nBands == 2)
             && poSrcDS->GetRasterBand(1)->GetColorTable() != NULL
             && eType == GDT_UInt16 )
    {
        unsigned short *panTRed, *panTGreen, *panTBlue;
        GDALColorTable *poCT;

        panTRed   = (unsigned short *) CPLMalloc(65536*sizeof(unsigned short));
        panTGreen = (unsigned short *) CPLMalloc(65536*sizeof(unsigned short));
        panTBlue  = (unsigned short *) CPLMalloc(65536*sizeof(unsigned short));

        poCT = poSrcDS->GetRasterBand(1)->GetColorTable();

        for( int iColor = 0; iColor < 65536; iColor++ )
        {
            if( iColor < poCT->GetColorEntryCount() )
            {
                GDALColorEntry  sRGB;

                poCT->GetColorEntryAsRGB( iColor, &sRGB );

                panTRed[iColor] = (unsigned short) (256 * sRGB.c1);
                panTGreen[iColor] = (unsigned short) (256 * sRGB.c2);
                panTBlue[iColor] = (unsigned short) (256 * sRGB.c3);
            }
            else
            {
                panTRed[iColor] = panTGreen[iColor] = panTBlue[iColor] = 0;
            }
        }

        if( !bForcePhotometric )
            TIFFSetField( hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE );
        TIFFSetField( hTIFF, TIFFTAG_COLORMAP, panTRed, panTGreen, panTBlue );

        CPLFree( panTRed );
        CPLFree( panTGreen );
        CPLFree( panTBlue );
    }
    else if( poSrcDS->GetRasterBand(1)->GetColorTable() != NULL )
        CPLError( CE_Warning, CPLE_AppDefined,
                  "Unable to export color table to GeoTIFF file.  Color tables\n"
                  "can only be written to 1 band or 2 bands Byte or UInt16 GeoTIFF files." );

    if( nBands == 2
        && poSrcDS->GetRasterBand(1)->GetColorTable() != NULL
        && (eType == GDT_Byte || eType == GDT_UInt16) )
    {
        uint16 v[1] = { EXTRASAMPLE_UNASSALPHA };

        TIFFSetField(hTIFF, TIFFTAG_EXTRASAMPLES, 1, v );
    }

    // FIXME? libtiff writes extended tags in the order they are specified
    // and not in increasing order

/* -------------------------------------------------------------------- */
/*      Transfer some TIFF specific metadata, if available.             */
/*      The return value will tell us if we need to try again later with*/
/*      PAM because the profile doesn't allow to write some metadata    */
/*      as TIFF tag                                                     */
/* -------------------------------------------------------------------- */
    int bHasWrittenMDInGeotiffTAG =
            GTiffDataset::WriteMetadata( poSrcDS, hTIFF, FALSE, pszProfile,
                                 pszFilename, papszOptions );

/* -------------------------------------------------------------------- */
/*      Write NoData value, if exist.                                   */
/* -------------------------------------------------------------------- */
    if( EQUAL(pszProfile,"GDALGeoTIFF") )
    {
        int bSuccess;
        double dfNoData;

        dfNoData = poSrcDS->GetRasterBand(1)->GetNoDataValue( &bSuccess );
        if ( bSuccess )
            GTiffDataset::WriteNoDataValue( hTIFF, dfNoData );
    }

/* -------------------------------------------------------------------- */
/*      Are we addressing PixelIsPoint mode?                            */
/* -------------------------------------------------------------------- */
    bool bPixelIsPoint = false;
    bool bPointGeoIgnore = false;

    if( poSrcDS->GetMetadataItem( GDALMD_AREA_OR_POINT )
        && EQUAL(poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT),
                 GDALMD_AOP_POINT) )
    {
        bPixelIsPoint = true;
        bPointGeoIgnore =
            CPLTestBool( CPLGetConfigOption( "GTIFF_POINT_GEO_IGNORE",
                                                "FALSE") );
    }

/* -------------------------------------------------------------------- */
/*      Write affine transform if it is meaningful.                     */
/* -------------------------------------------------------------------- */
    const char *pszProjection = NULL;
    double      adfGeoTransform[6];

    if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None
        && (adfGeoTransform[0] != 0.0 || adfGeoTransform[1] != 1.0
            || adfGeoTransform[2] != 0.0 || adfGeoTransform[3] != 0.0
            || adfGeoTransform[4] != 0.0 || adfGeoTransform[5] != 1.0 ))
    {
        if( bGeoTIFF )
        {
            if( adfGeoTransform[2] == 0.0 && adfGeoTransform[4] == 0.0
                && adfGeoTransform[5] < 0.0 )
            {

                double adfPixelScale[3], adfTiePoints[6];

                adfPixelScale[0] = adfGeoTransform[1];
                adfPixelScale[1] = fabs(adfGeoTransform[5]);
                adfPixelScale[2] = 0.0;

                TIFFSetField( hTIFF, TIFFTAG_GEOPIXELSCALE, 3, adfPixelScale );

                adfTiePoints[0] = 0.0;
                adfTiePoints[1] = 0.0;
                adfTiePoints[2] = 0.0;
                adfTiePoints[3] = adfGeoTransform[0];
                adfTiePoints[4] = adfGeoTransform[3];
                adfTiePoints[5] = 0.0;

                if( bPixelIsPoint && !bPointGeoIgnore )
                {
                    adfTiePoints[3] += adfGeoTransform[1] * 0.5 + adfGeoTransform[2] * 0.5;
                    adfTiePoints[4] += adfGeoTransform[4] * 0.5 + adfGeoTransform[5] * 0.5;
                }

                TIFFSetField( hTIFF, TIFFTAG_GEOTIEPOINTS, 6, adfTiePoints );
            }
            else
            {
                double adfMatrix[16];

                memset(adfMatrix,0,sizeof(double) * 16);

                adfMatrix[0] = adfGeoTransform[1];
                adfMatrix[1] = adfGeoTransform[2];
                adfMatrix[3] = adfGeoTransform[0];
                adfMatrix[4] = adfGeoTransform[4];
                adfMatrix[5] = adfGeoTransform[5];
                adfMatrix[7] = adfGeoTransform[3];
                adfMatrix[15] = 1.0;

                if( bPixelIsPoint && !bPointGeoIgnore )
                {
                    adfMatrix[3] += adfGeoTransform[1] * 0.5 + adfGeoTransform[2] * 0.5;
                    adfMatrix[7] += adfGeoTransform[4] * 0.5 + adfGeoTransform[5] * 0.5;
                }

                TIFFSetField( hTIFF, TIFFTAG_GEOTRANSMATRIX, 16, adfMatrix );
            }

            pszProjection = poSrcDS->GetProjectionRef();
        }

/* -------------------------------------------------------------------- */
/*      Do we need a TFW file?                                          */
/* -------------------------------------------------------------------- */
        if( CSLFetchBoolean( papszOptions, "TFW", FALSE ) )
            GDALWriteWorldFile( pszFilename, "tfw", adfGeoTransform );
        else if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
            GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform );
    }

/* -------------------------------------------------------------------- */
/*      Otherwise write tiepoints if they are available.                */
/* -------------------------------------------------------------------- */
    else if( poSrcDS->GetGCPCount() > 0 && bGeoTIFF )
    {
        const GDAL_GCP *pasGCPs = poSrcDS->GetGCPs();
        double *padfTiePoints;

        padfTiePoints = (double *)
            CPLMalloc(6*sizeof(double)*poSrcDS->GetGCPCount());

        for( int iGCP = 0; iGCP < poSrcDS->GetGCPCount(); iGCP++ )
        {

            padfTiePoints[iGCP*6+0] = pasGCPs[iGCP].dfGCPPixel;
            padfTiePoints[iGCP*6+1] = pasGCPs[iGCP].dfGCPLine;
            padfTiePoints[iGCP*6+2] = 0;
            padfTiePoints[iGCP*6+3] = pasGCPs[iGCP].dfGCPX;
            padfTiePoints[iGCP*6+4] = pasGCPs[iGCP].dfGCPY;
            padfTiePoints[iGCP*6+5] = pasGCPs[iGCP].dfGCPZ;

            if( bPixelIsPoint && !bPointGeoIgnore )
            {
                padfTiePoints[iGCP*6+0] += 0.5;
                padfTiePoints[iGCP*6+1] += 0.5;
            }
        }

        TIFFSetField( hTIFF, TIFFTAG_GEOTIEPOINTS,
                      6*poSrcDS->GetGCPCount(), padfTiePoints );
        CPLFree( padfTiePoints );

        pszProjection = poSrcDS->GetGCPProjection();

        if( CSLFetchBoolean( papszOptions, "TFW", FALSE )
            || CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "TFW=ON or WORLDFILE=ON creation options are ignored when GCPs are available");
        }
    }

    else
        pszProjection = poSrcDS->GetProjectionRef();

/* -------------------------------------------------------------------- */
/*      Write the projection information, if possible.                  */
/* -------------------------------------------------------------------- */
    const bool bHasProjection = (pszProjection != NULL && strlen(pszProjection) > 0);
    if( (bHasProjection || bPixelIsPoint) && bGeoTIFF )
    {
        GTIF *psGTIF;

        psGTIF = GTIFNew( hTIFF );

        if( bHasProjection )
        {
            GTIFSetFromOGISDefnEx( psGTIF, pszProjection, GetGTIFFKeysFlavor(papszOptions) );
        }

        if( bPixelIsPoint )
        {
            GTIFKeySet(psGTIF, GTRasterTypeGeoKey, TYPE_SHORT, 1,
                       RasterPixelIsPoint);
        }

        GTIFWriteKeys( psGTIF );
        GTIFFree( psGTIF );
    }

    int bDontReloadFirstBlock = FALSE;

#ifdef HAVE_LIBJPEG
    if (bCopyFromJPEG)
    {
        GTIFF_CopyFromJPEG_WriteAdditionalTags(hTIFF,
                                               poSrcDS);
    }
#else
    if (0)
    {
    }
#endif

#if !defined(BIGTIFF_SUPPORT)
    /* -------------------------------------------------------------------- */
    /*      If we are writing jpeg compression we need to write some        */
    /*      imagery to force the jpegtables to get created.  This is,       */
    /*      likely only needed with libtiff >= 3.9.3 (#3633)                */
    /* -------------------------------------------------------------------- */
    else if( nCompression == COMPRESSION_JPEG
            && strstr(TIFFLIB_VERSION_STR, "Version 3.9") != NULL )
    {
        CPLDebug( "GDAL",
                  "Writing zero block to force creation of JPEG tables." );
        if( TIFFIsTiled( hTIFF ) )
        {
            int cc = TIFFTileSize( hTIFF );
            unsigned char *pabyZeros = (unsigned char *) CPLCalloc(cc,1);
            TIFFWriteEncodedTile(hTIFF, 0, pabyZeros, cc);
            CPLFree( pabyZeros );
        }
        else
        {
            int cc = TIFFStripSize( hTIFF );
            unsigned char *pabyZeros = (unsigned char *) CPLCalloc(cc,1);
            TIFFWriteEncodedStrip(hTIFF, 0, pabyZeros, cc);
            CPLFree( pabyZeros );
        }
        bDontReloadFirstBlock = TRUE;
    }
#endif

/* -------------------------------------------------------------------- */
/*      Cleanup                                                         */
/* -------------------------------------------------------------------- */

    TIFFWriteCheck( hTIFF, TIFFIsTiled(hTIFF), "GTiffCreateCopy()");
    TIFFWriteDirectory( hTIFF );
    if( bStreaming )
    {
        /* We need to write twice the directory to be sure that custom */
        /* TIFF tags are correctly sorted and that padding bytes have been */
        /* added */
        TIFFSetDirectory( hTIFF, 0 );
        TIFFWriteDirectory( hTIFF );

        if( VSIFSeekL( fpL, 0, SEEK_END ) != 0 )
            CPLError(CE_Failure, CPLE_FileIO, "Cannot seek");
        int nSize = (int) VSIFTellL(fpL);

        vsi_l_offset nDataLength;
        VSIGetMemFileBuffer( osTmpFilename, &nDataLength, FALSE);
        TIFFSetDirectory( hTIFF, 0 );
        GTiffFillStreamableOffsetAndCount( hTIFF, nSize );
        TIFFWriteDirectory( hTIFF );
    }
    TIFFFlush( hTIFF );
    XTIFFClose( hTIFF );
    hTIFF = NULL;
    if( VSIFCloseL(fpL) != 0 )
        eErr = CE_Failure;
    fpL = NULL;

    if( eErr != CE_None )
    {
        VSIUnlink( bStreaming ? osTmpFilename.c_str() : pszFilename );
        return NULL;
    }

    if( bStreaming )
    {
        vsi_l_offset nDataLength;
        void* pabyBuffer = VSIGetMemFileBuffer( osTmpFilename, &nDataLength, FALSE);
        fpL = VSIFOpenL( pszFilename, "wb" );
        if( fpL == NULL )
        {
            VSIUnlink(osTmpFilename);
            return NULL;
        }
        if( (int)VSIFWriteL( pabyBuffer, 1, (int)nDataLength, fpL ) != (int)nDataLength )
        {
            CPLError(CE_Failure, CPLE_FileIO, "Could not write %d bytes",
                     (int)nDataLength);
            CPL_IGNORE_RET_VAL(VSIFCloseL( fpL ));
            VSIUnlink(osTmpFilename);
            return NULL;
        }
    }

/* -------------------------------------------------------------------- */
/*      Re-open as a dataset and copy over missing metadata using       */
/*      PAM facilities.                                                 */
/* -------------------------------------------------------------------- */
    GTiffDataset *poDS;
    CPLString osFileName("GTIFF_RAW:");

    osFileName += bStreaming ? osTmpFilename.c_str() : pszFilename;

    GDALOpenInfo oOpenInfo( osFileName, GA_Update );
    if( bStreaming )
    {
        /* In case of single strip file, there's a libtiff check that would */
        /* issue a warning since the file hasn't the required size */
        CPLPushErrorHandler(CPLQuietErrorHandler);
    }
    poDS = (GTiffDataset *) Open(&oOpenInfo);
    if( bStreaming )
        CPLPopErrorHandler();
    if( poDS == NULL )
    {
        oOpenInfo.eAccess = GA_ReadOnly;
        poDS = (GTiffDataset *) Open(&oOpenInfo);
    }

    if ( poDS == NULL )
    {
        VSIUnlink( bStreaming ? osTmpFilename.c_str() : pszFilename );
        return NULL;
    }

    if( bStreaming )
    {
        VSIUnlink(osTmpFilename);
        poDS->fpToWrite = fpL;
    }
    poDS->osProfile = pszProfile;

    int nCloneInfoFlags = GCIF_PAM_DEFAULT & ~GCIF_MASK;

    // If we explicitly asked not to tag the alpha band as such, don't
    // reintroduce this alpha color interpretation in PAM.
    if( poSrcDS->GetRasterBand(nBands)->GetColorInterpretation()==GCI_AlphaBand &&
        GTiffGetAlphaValue(CPLGetConfigOption("GTIFF_ALPHA", CSLFetchNameValue(papszOptions,"ALPHA")), DEFAULT_ALPHA_TYPE) == EXTRASAMPLE_UNSPECIFIED )
    {
        nCloneInfoFlags = nCloneInfoFlags & ~GCIF_COLORINTERP;
    }

    poDS->CloneInfo( poSrcDS, nCloneInfoFlags );
    poDS->papszCreationOptions = CSLDuplicate( papszOptions );
    poDS->bDontReloadFirstBlock = bDontReloadFirstBlock;

/* -------------------------------------------------------------------- */
/*      CloneInfo() doesn't merge metadata, it just replaces it totally */
/*      So we have to merge it                                          */
/* -------------------------------------------------------------------- */

    char **papszSRC_MD = poSrcDS->GetMetadata();
    char **papszDST_MD = CSLDuplicate(poDS->GetMetadata());

    papszDST_MD = CSLMerge( papszDST_MD, papszSRC_MD );

    poDS->SetMetadata( papszDST_MD );
    CSLDestroy( papszDST_MD );

    /* Depending on the PHOTOMETRIC tag, the TIFF file may not have */
    /* the same band count as the source. Will fail later in GDALDatasetCopyWholeRaster anyway... */
    for( int nBand = 1;
         nBand <= MIN(poDS->GetRasterCount(), poSrcDS->GetRasterCount()) ;
         nBand++ )
    {
        GDALRasterBand* poSrcBand = poSrcDS->GetRasterBand(nBand);
        GDALRasterBand* poDstBand = poDS->GetRasterBand(nBand);
        papszSRC_MD = poSrcBand->GetMetadata();
        papszDST_MD = CSLDuplicate(poDstBand->GetMetadata());

        papszDST_MD = CSLMerge( papszDST_MD, papszSRC_MD );

        poDstBand->SetMetadata( papszDST_MD );
        CSLDestroy( papszDST_MD );

        char** papszCatNames;
        papszCatNames = poSrcBand->GetCategoryNames();
        if (NULL != papszCatNames)
            poDstBand->SetCategoryNames( papszCatNames );
    }

    hTIFF = (TIFF*) poDS->GetInternalHandle(NULL);

/* -------------------------------------------------------------------- */
/*      Handle forcing xml:ESRI data to be written to PAM.              */
/* -------------------------------------------------------------------- */
    if( CPLTestBool(CPLGetConfigOption( "ESRI_XML_PAM", "NO" )) )
    {
        char **papszESRIMD = poSrcDS->GetMetadata("xml:ESRI");
        if( papszESRIMD )
        {
            poDS->SetMetadata( papszESRIMD, "xml:ESRI");
        }
    }

/* -------------------------------------------------------------------- */
/*      Second chance: now that we have a PAM dataset, it is possible   */
/*      to write metadata that we could not write as a TIFF tag.        */
/* -------------------------------------------------------------------- */
    if (!bHasWrittenMDInGeotiffTAG && !bStreaming)
        GTiffDataset::WriteMetadata( poDS, hTIFF, TRUE, pszProfile,
                                     pszFilename, papszOptions, TRUE /* don't write RPC and IMD file again */);

    if( !bStreaming )
        GTiffDataset::WriteRPC( poDS, hTIFF, TRUE, pszProfile,
                                     pszFilename, papszOptions, TRUE /* write only in PAM AND if needed */ );

    /* To avoid unnecessary directory rewriting */
    poDS->bMetadataChanged = FALSE;
    poDS->bGeoTIFFInfoChanged = FALSE;
    poDS->bNoDataChanged = FALSE;
    poDS->bForceUnsetGTOrGCPs = FALSE;
    poDS->bForceUnsetProjection = FALSE;
    poDS->bStreamingOut = bStreaming;

    /* We must re-set the compression level at this point, since it has */
    /* been lost a few lines above when closing the newly create TIFF file */
    /* The TIFFTAG_ZIPQUALITY & TIFFTAG_JPEGQUALITY are not store in the TIFF file. */
    /* They are just TIFF session parameters */

    poDS->nZLevel = GTiffGetZLevel(papszOptions);
    poDS->nLZMAPreset = GTiffGetLZMAPreset(papszOptions);
    poDS->nJpegQuality = GTiffGetJpegQuality(papszOptions);
    poDS->nJpegTablesMode = GTiffGetJpegTablesMode(papszOptions);
    poDS->GetDiscardLsbOption(papszOptions);
    poDS->InitCreationOrOpenOptions(papszOptions);

    if (nCompression == COMPRESSION_ADOBE_DEFLATE)
    {
        if (poDS->nZLevel != -1)
        {
            TIFFSetField( hTIFF, TIFFTAG_ZIPQUALITY, poDS->nZLevel );
        }
    }
    else if( nCompression == COMPRESSION_JPEG)
    {
        if (poDS->nJpegQuality != -1)
        {
            TIFFSetField( hTIFF, TIFFTAG_JPEGQUALITY, poDS->nJpegQuality );
        }
        TIFFSetField( hTIFF, TIFFTAG_JPEGTABLESMODE, poDS->nJpegTablesMode );
    }
    else if( nCompression == COMPRESSION_LZMA)
    {
        if (poDS->nLZMAPreset != -1)
        {
            TIFFSetField( hTIFF, TIFFTAG_LZMAPRESET, poDS->nLZMAPreset );
        }
    }

    /* Precreate (internal) mask, so that the IBuildOverviews() below */
    /* has a chance to create also the overviews of the mask */
    int nMaskFlags = poSrcDS->GetRasterBand(1)->GetMaskFlags();
    if( eErr == CE_None
        && !(nMaskFlags & (GMF_ALL_VALID|GMF_ALPHA|GMF_NODATA) )
        && (nMaskFlags & GMF_PER_DATASET) )
    {
        eErr = poDS->CreateMaskBand( nMaskFlags );
    }

/* -------------------------------------------------------------------- */
/*      Create and then copy existing overviews if requested            */
/*  We do it such that all the IFDs are at the beginning of the file,   */
/*  and that the imagery data for the smallest overview is written      */
/*  first, that way the file is more usable when embedded in a          */
/*  compressed stream.                                                  */
/* -------------------------------------------------------------------- */

    /* For scaled progress due to overview copying */
    double dfTotalPixels = ((double)nXSize) * nYSize;
    double dfCurPixels = 0;

    if (eErr == CE_None &&
        nSrcOverviews != 0 &&
        CSLFetchBoolean(papszOptions, "COPY_SRC_OVERVIEWS", FALSE))
    {
        eErr = poDS->CreateOverviewsFromSrcOverviews(poSrcDS);

        if (poDS->nOverviewCount != nSrcOverviews)
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Did only manage to instantiate %d overview levels, "
                      "whereas source contains %d",
                     poDS->nOverviewCount, nSrcOverviews);
            eErr = CE_Failure;
        }

        int i;
        for(i=0;i<nSrcOverviews;i++)
        {
            GDALRasterBand* poOvrBand = poSrcDS->GetRasterBand(1)->GetOverview(i);
            dfTotalPixels += ((double)poOvrBand->GetXSize()) *
                                      poOvrBand->GetYSize();
        }

        char* papszCopyWholeRasterOptions[2] = { NULL, NULL };
        if (nCompression != COMPRESSION_NONE)
            papszCopyWholeRasterOptions[0] = (char*) "COMPRESSED=YES";
        /* Now copy the imagery */
        for(i=0;eErr == CE_None && i<nSrcOverviews;i++)
        {
            /* Begin with the smallest overview */
            int iOvrLevel = nSrcOverviews-1-i;

            /* Create a fake dataset with the source overview level so that */
            /* GDALDatasetCopyWholeRaster can cope with it */
            GDALDataset* poSrcOvrDS = GDALCreateOverviewDataset(poSrcDS, iOvrLevel, TRUE, FALSE);

            GDALRasterBand* poOvrBand =
                    poSrcDS->GetRasterBand(1)->GetOverview(iOvrLevel);
            double dfNextCurPixels = dfCurPixels +
                    ((double)poOvrBand->GetXSize()) * poOvrBand->GetYSize();

            void* pScaledData = GDALCreateScaledProgress( dfCurPixels / dfTotalPixels,
                                      dfNextCurPixels / dfTotalPixels,
                                      pfnProgress, pProgressData);

            eErr = GDALDatasetCopyWholeRaster( (GDALDatasetH) poSrcOvrDS,
                                                (GDALDatasetH) poDS->papoOverviewDS[iOvrLevel],
                                                papszCopyWholeRasterOptions,
                                                GDALScaledProgress, pScaledData );

            dfCurPixels = dfNextCurPixels;
            GDALDestroyScaledProgress(pScaledData);

            delete poSrcOvrDS;
            poDS->papoOverviewDS[iOvrLevel]->FlushCache();

            /* Copy mask of the overview */
            if (eErr == CE_None && poDS->poMaskDS != NULL)
            {
                eErr = GDALRasterBandCopyWholeRaster( poOvrBand->GetMaskBand(),
                                                    poDS->papoOverviewDS[iOvrLevel]->poMaskDS->GetRasterBand(1),
                                                    papszCopyWholeRasterOptions,
                                                    GDALDummyProgress, NULL);
                poDS->papoOverviewDS[iOvrLevel]->poMaskDS->FlushCache();
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Copy actual imagery.                                            */
/* -------------------------------------------------------------------- */
    void* pScaledData = GDALCreateScaledProgress( dfCurPixels / dfTotalPixels,
                                                  1.0,
                                                  pfnProgress, pProgressData);

    int bTryCopy = TRUE;

#ifdef HAVE_LIBJPEG
    if (bCopyFromJPEG)
    {
        eErr = GTIFF_CopyFromJPEG(poDS, poSrcDS,
                                  pfnProgress, pProgressData,
                                  bTryCopy);

        /* In case of failure in the decompression step, try normal copy */
        if (bTryCopy)
            eErr = CE_None;
    }
#endif

#ifdef JPEG_DIRECT_COPY
    if (bDirectCopyFromJPEG)
    {
        eErr = GTIFF_DirectCopyFromJPEG(poDS, poSrcDS,
                                        pfnProgress, pProgressData,
                                        bTryCopy);

        /* In case of failure in the reading step, try normal copy */
        if (bTryCopy)
            eErr = CE_None;
    }
#endif

    if (bTryCopy && (poDS->bTreatAsSplit || poDS->bTreatAsSplitBitmap))
    {
        /* For split bands, we use TIFFWriteScanline() interface */
        CPLAssert(poDS->nBitsPerSample == 8 || poDS->nBitsPerSample == 1);

        if (poDS->nPlanarConfig == PLANARCONFIG_CONTIG && poDS->nBands > 1)
        {
            int j;
            GByte* pabyScanline = (GByte *) VSI_MALLOC_VERBOSE(TIFFScanlineSize(hTIFF));
            if( pabyScanline == NULL )
                eErr = CE_Failure;
            for(j=0;j<nYSize && eErr == CE_None;j++)
            {
                eErr = poSrcDS->RasterIO(GF_Read, 0, j, nXSize, 1,
                                         pabyScanline, nXSize, 1,
                                         GDT_Byte, nBands, NULL, poDS->nBands, 0, 1,
                                         NULL);
                if (eErr == CE_None &&
                    TIFFWriteScanline( hTIFF, pabyScanline, j, 0) == -1)
                {
                    CPLError( CE_Failure, CPLE_AppDefined,
                              "TIFFWriteScanline() failed." );
                    eErr = CE_Failure;
                }
                if( !GDALScaledProgress( (j+1) * 1.0 / nYSize, NULL, pScaledData ) )
                    eErr = CE_Failure;
            }
            CPLFree(pabyScanline);
        }
        else
        {
            int j;
            GByte* pabyScanline = (GByte *) VSI_MALLOC_VERBOSE(nXSize);
            if( pabyScanline == NULL )
                eErr = CE_Failure;
            else
                eErr = CE_None;
            for(int iBand=1;iBand<=nBands && eErr == CE_None;iBand++)
            {
                for(j=0;j<nYSize && eErr == CE_None;j++)
                {
                    eErr = poSrcDS->GetRasterBand(iBand)->RasterIO(
                                                    GF_Read, 0, j, nXSize, 1,
                                                    pabyScanline, nXSize, 1,
                                                    GDT_Byte, 0, 0, NULL);
                    if (poDS->bTreatAsSplitBitmap)
                    {
                        for(int i=0;i<nXSize;i++)
                        {
                            GByte byVal = pabyScanline[i];
                            if ((i & 0x7) == 0)
                                pabyScanline[i >> 3] = 0;
                            if (byVal)
                                pabyScanline[i >> 3] |= (0x80 >> (i & 0x7));
                        }
                    }
                    if (eErr == CE_None &&
                        TIFFWriteScanline( hTIFF, pabyScanline, j, (uint16) (iBand-1)) == -1)
                    {
                        CPLError( CE_Failure, CPLE_AppDefined,
                                  "TIFFWriteScanline() failed." );
                        eErr = CE_Failure;
                    }
                    if( !GDALScaledProgress( (j+1 + (iBand - 1) * nYSize) * 1.0 /
                                      (nBands * nYSize), NULL, pScaledData ) )
                        eErr = CE_Failure;
                }
            }
            CPLFree(pabyScanline);
        }

        /* Necessary to be able to read the file without re-opening */
#if defined(HAVE_TIFFGETSIZEPROC)
        TIFFSizeProc pfnSizeProc = TIFFGetSizeProc( hTIFF );

        TIFFFlushData( hTIFF );

        toff_t nNewDirOffset = pfnSizeProc( TIFFClientdata( hTIFF ) );
        if( (nNewDirOffset % 2) == 1 )
            nNewDirOffset++;
#endif

        TIFFFlush( hTIFF );

#if defined(HAVE_TIFFGETSIZEPROC)
        if( poDS->nDirOffset != TIFFCurrentDirOffset( hTIFF ) )
        {
            poDS->nDirOffset = nNewDirOffset;
            CPLDebug( "GTiff", "directory moved during flush." );
        }
#endif
    }
    else if (bTryCopy && eErr == CE_None)
    {
        char* papszCopyWholeRasterOptions[2] = { NULL, NULL };
        if (nCompression != COMPRESSION_NONE)
            papszCopyWholeRasterOptions[0] = (char*) "COMPRESSED=YES";
        /* For streaming with separate, we really want that bands are written */
        /* after each other, even if the source is pixel interleaved */
        else if( bStreaming && poDS->nPlanarConfig == PLANARCONFIG_SEPARATE )
            papszCopyWholeRasterOptions[0] = (char*) "INTERLEAVE=BAND";
        eErr = GDALDatasetCopyWholeRaster( (GDALDatasetH) poSrcDS,
                                            (GDALDatasetH) poDS,
                                            papszCopyWholeRasterOptions,
                                            GDALScaledProgress, pScaledData );
    }

    GDALDestroyScaledProgress(pScaledData);

    if (eErr == CE_None && !bStreaming)
    {
        if (poDS->poMaskDS)
        {
            const char* l_papszOptions[2] = { "COMPRESSED=YES", NULL };
            eErr = GDALRasterBandCopyWholeRaster(
                                    poSrcDS->GetRasterBand(1)->GetMaskBand(),
                                    poDS->GetRasterBand(1)->GetMaskBand(),
                                    (char**)l_papszOptions,
                                    GDALDummyProgress, NULL);
        }
        else
            eErr = GDALDriver::DefaultCopyMasks( poSrcDS, poDS, bStrict );
    }

    if( eErr == CE_Failure )
    {
        delete poDS;
        poDS = NULL;

        if (CPLTestBool(CPLGetConfigOption("GTIFF_DELETE_ON_ERROR", "YES")))
        {
            if( !bStreaming )
                VSIUnlink( pszFilename ); // should really delete more carefully.
        }
    }

    return poDS;
}

/************************************************************************/
/*                          GetProjectionRef()                          */
/************************************************************************/

const char *GTiffDataset::GetProjectionRef()

{
    if( nGCPCount == 0 )
    {
        LoadGeoreferencingAndPamIfNeeded();
        LookForProjection();

        if( EQUAL(pszProjection,"") )
            return GDALPamDataset::GetProjectionRef();
        else
            return( pszProjection );
    }
    else
        return "";
}

/************************************************************************/
/*                           SetProjection()                            */
/************************************************************************/

CPLErr GTiffDataset::SetProjection( const char * pszNewProjection )

{
    if( bStreamingOut && bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify projection at that point in a streamed output file");
        return CE_Failure;
    }

    LoadGeoreferencingAndPamIfNeeded();
    LookForProjection();

    if( !STARTS_WITH_CI(pszNewProjection, "GEOGCS")
        && !STARTS_WITH_CI(pszNewProjection, "PROJCS")
        && !STARTS_WITH_CI(pszNewProjection, "LOCAL_CS")
        && !STARTS_WITH_CI(pszNewProjection, "COMPD_CS")
        && !STARTS_WITH_CI(pszNewProjection, "GEOCCS")
        && !EQUAL(pszNewProjection,"") )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                "Only OGC WKT Projections supported for writing to GeoTIFF.\n"
                "%s not supported.",
                  pszNewProjection );

        return CE_Failure;
    }

    if ( EQUAL(pszNewProjection, "") &&
         pszProjection != NULL &&
         !EQUAL(pszProjection, "") )
    {
        bForceUnsetProjection = TRUE;
    }

    CPLFree( pszProjection );
    pszProjection = CPLStrdup( pszNewProjection );

    bGeoTIFFInfoChanged = TRUE;

    return CE_None;
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr GTiffDataset::GetGeoTransform( double * padfTransform )

{
    LoadGeoreferencingAndPamIfNeeded();

    memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );

    if( !bGeoTransformValid )
        return CE_Failure;
    else
        return CE_None;
}

/************************************************************************/
/*                          SetGeoTransform()                           */
/************************************************************************/

CPLErr GTiffDataset::SetGeoTransform( double * padfTransform )

{
    if( bStreamingOut && bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify geotransform at that point in a streamed output file");
        return CE_Failure;
    }

    LoadGeoreferencingAndPamIfNeeded();

    if( GetAccess() == GA_Update )
    {
        if (
            padfTransform[0] == 0.0 &&
            padfTransform[1] == 1.0 &&
            padfTransform[2] == 0.0 &&
            padfTransform[3] == 0.0 &&
            padfTransform[4] == 0.0 &&
            padfTransform[5] == 1.0 &&
          !(adfGeoTransform[0] == 0.0 &&
            adfGeoTransform[1] == 1.0 &&
            adfGeoTransform[2] == 0.0 &&
            adfGeoTransform[3] == 0.0 &&
            adfGeoTransform[4] == 0.0 &&
            adfGeoTransform[5] == 1.0) )
        {
            bForceUnsetGTOrGCPs = TRUE;
        }

        memcpy( adfGeoTransform, padfTransform, sizeof(double)*6 );
        bGeoTransformValid = TRUE;
        bGeoTIFFInfoChanged = TRUE;

        return( CE_None );
    }
    else
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Attempt to call SetGeoTransform() on a read-only GeoTIFF file." );
        return CE_Failure;
    }
}

/************************************************************************/
/*                            GetGCPCount()                             */
/************************************************************************/

int GTiffDataset::GetGCPCount()

{
    LoadGeoreferencingAndPamIfNeeded();

    return nGCPCount;
}

/************************************************************************/
/*                          GetGCPProjection()                          */
/************************************************************************/

const char *GTiffDataset::GetGCPProjection()

{
    LoadGeoreferencingAndPamIfNeeded();

    if( nGCPCount > 0 )
    {
        LookForProjection();
    }
    if (pszProjection != NULL)
        return pszProjection;
    else
        return "";
}

/************************************************************************/
/*                               GetGCPs()                              */
/************************************************************************/

const GDAL_GCP *GTiffDataset::GetGCPs()

{
    LoadGeoreferencingAndPamIfNeeded();

    return pasGCPList;
}

/************************************************************************/
/*                               SetGCPs()                              */
/************************************************************************/

CPLErr GTiffDataset::SetGCPs( int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
                              const char *pszGCPProjection )
{
    LoadGeoreferencingAndPamIfNeeded();

    if( GetAccess() == GA_Update )
    {
        LookForProjection();

        if (nGCPCount > 0 && nGCPCountIn == 0)
            bForceUnsetGTOrGCPs = TRUE;
        if( !EQUAL(pszProjection, "") &&
                   (pszGCPProjection == NULL ||
                   pszGCPProjection[0] == '\0') )
            bForceUnsetProjection = TRUE;

        if( nGCPCount > 0 )
        {
            GDALDeinitGCPs( nGCPCount, pasGCPList );
            CPLFree( pasGCPList );
        }

        nGCPCount = nGCPCountIn;
        pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);

        CPLFree( pszProjection );
        pszProjection = CPLStrdup( pszGCPProjection );
        bGeoTIFFInfoChanged = TRUE;

        return CE_None;
    }
    else
    {
        CPLError( CE_Failure, CPLE_NotSupported,
            "SetGCPs() is only supported on newly created GeoTIFF files." );
        return CE_Failure;
    }
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **GTiffDataset::GetMetadataDomainList()
{
    LoadGeoreferencingAndPamIfNeeded();

    return BuildMetadataDomainList(CSLDuplicate(oGTiffMDMD.GetDomainList()),
                                   TRUE,
                                   "", "ProxyOverviewRequest", MD_DOMAIN_RPC, MD_DOMAIN_IMD, "SUBDATASETS", "EXIF",
                                   "xml:XMP", "COLOR_PROFILE", NULL);
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char **GTiffDataset::GetMetadata( const char * pszDomain )

{
    if( pszDomain == NULL || !EQUAL(pszDomain, "IMAGE_STRUCTURE") )
    {
        LoadGeoreferencingAndPamIfNeeded();
    }

    if( pszDomain != NULL && EQUAL(pszDomain,"ProxyOverviewRequest") )
        return GDALPamDataset::GetMetadata( pszDomain );

    else if( pszDomain != NULL && (EQUAL(pszDomain, MD_DOMAIN_RPC) ||
                                   EQUAL(pszDomain, MD_DOMAIN_IMD) ||
                                   EQUAL(pszDomain, MD_DOMAIN_IMAGERY)) )
        LoadMetadata();

    else if( pszDomain != NULL && EQUAL(pszDomain,"SUBDATASETS") )
        ScanDirectories();

    else if( pszDomain != NULL && EQUAL(pszDomain,"EXIF") )
        LoadEXIFMetadata();

    else if( pszDomain != NULL && EQUAL(pszDomain,"COLOR_PROFILE") )
        LoadICCProfile();

    else if( pszDomain == NULL || EQUAL(pszDomain, "") )
        LoadMDAreaOrPoint(); /* to set GDALMD_AREA_OR_POINT */

    return oGTiffMDMD.GetMetadata( pszDomain );
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/
CPLErr GTiffDataset::SetMetadata( char ** papszMD, const char *pszDomain )

{
    LoadGeoreferencingAndPamIfNeeded();

    if( bStreamingOut && bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify metadata at that point in a streamed output file");
        return CE_Failure;
    }

    if ((papszMD != NULL) && (pszDomain != NULL) && EQUAL(pszDomain, "COLOR_PROFILE"))
        bColorProfileMetadataChanged = TRUE;
    else if( pszDomain == NULL || !EQUAL(pszDomain,"_temporary_") )
    {
        bMetadataChanged = TRUE;
        // Cancel any existing metadata from PAM file
        if( eAccess == GA_Update &&
            GDALPamDataset::GetMetadata(pszDomain) != NULL )
            GDALPamDataset::SetMetadata(NULL, pszDomain);
    }

    if( (pszDomain == NULL || EQUAL(pszDomain, "")) &&
        CSLFetchNameValue(papszMD, GDALMD_AREA_OR_POINT) != NULL )
    {
        const char* pszPrevValue =
                GetMetadataItem(GDALMD_AREA_OR_POINT);
        const char* pszNewValue =
                CSLFetchNameValue(papszMD, GDALMD_AREA_OR_POINT);
        if (pszPrevValue == NULL || pszNewValue == NULL ||
            !EQUAL(pszPrevValue, pszNewValue))
        {
            LookForProjection();
            bGeoTIFFInfoChanged = TRUE;
        }
    }

    return oGTiffMDMD.SetMetadata( papszMD, pszDomain );
}

/************************************************************************/
/*                          GetMetadataItem()                           */
/************************************************************************/

const char *GTiffDataset::GetMetadataItem( const char * pszName,
                                           const char * pszDomain )

{
    if( pszDomain == NULL || !EQUAL(pszDomain, "IMAGE_STRUCTURE") )
    {
        LoadGeoreferencingAndPamIfNeeded();
    }

    if( pszDomain != NULL && EQUAL(pszDomain,"ProxyOverviewRequest") )
        return GDALPamDataset::GetMetadataItem( pszName, pszDomain );

    else if( pszDomain != NULL && (EQUAL(pszDomain, MD_DOMAIN_RPC) ||
                                   EQUAL(pszDomain, MD_DOMAIN_IMD) ||
                                   EQUAL(pszDomain, MD_DOMAIN_IMAGERY)) )
        LoadMetadata();

    else if( pszDomain != NULL && EQUAL(pszDomain,"SUBDATASETS") )
        ScanDirectories();

    else if( pszDomain != NULL && EQUAL(pszDomain,"EXIF") )
        LoadEXIFMetadata();

    else if( pszDomain != NULL && EQUAL(pszDomain,"COLOR_PROFILE") )
        LoadICCProfile();

    else if( (pszDomain == NULL || EQUAL(pszDomain, "")) &&
        pszName != NULL && EQUAL(pszName, GDALMD_AREA_OR_POINT) )
    {
        LoadMDAreaOrPoint(); /* to set GDALMD_AREA_OR_POINT */
    }

#ifdef DEBUG_REACHED_VIRTUAL_MEM_IO
    else if( pszDomain != NULL && EQUAL(pszDomain, "_DEBUG_") &&
             pszName != NULL && EQUAL(pszName, "UNREACHED_VIRTUALMEMIO_CODE_PATH") )
    {
        CPLString osMissing;
        for(int i=0;i<(int)(sizeof(anReachedVirtualMemIO)/sizeof(anReachedVirtualMemIO[0]));i++)
        {
            if( !anReachedVirtualMemIO[i] )
            {
                if( osMissing.size() ) osMissing += ",";
                osMissing += CPLSPrintf("%d", i);
            }
        }
        return (osMissing.size()) ? CPLSPrintf("%s", osMissing.c_str()) : NULL;
    }
#endif
    else if( pszDomain != NULL && EQUAL(pszDomain, "_DEBUG_") &&
             pszName != NULL && EQUAL(pszName, "TIFFTAG_EXTRASAMPLES") )
    {
        CPLString osRet;
        uint16 *v;
        uint16 count = 0;

        if( TIFFGetField( hTIFF, TIFFTAG_EXTRASAMPLES, &count, &v ) )
        {
            for(int i=0; i < (int)count; i++)
            {
                if( i > 0 ) osRet += ",";
                osRet += CPLSPrintf("%d", v[i]);
            }
        }
        return (osRet.size()) ? CPLSPrintf("%s", osRet.c_str()) : NULL;
    }
    else if( pszDomain != NULL && EQUAL(pszDomain, "_DEBUG_") &&
             pszName != NULL && EQUAL(pszName, "TIFFTAG_PHOTOMETRIC") )
    {
        return CPLSPrintf("%d", nPhotometric);
    }

    return oGTiffMDMD.GetMetadataItem( pszName, pszDomain );
}

/************************************************************************/
/*                          SetMetadataItem()                           */
/************************************************************************/

CPLErr GTiffDataset::SetMetadataItem( const char *pszName,
                                      const char *pszValue,
                                      const char *pszDomain )

{
    LoadGeoreferencingAndPamIfNeeded();

    if( bStreamingOut && bCrystalized )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot modify metadata at that point in a streamed output file");
        return CE_Failure;
    }

    if ((pszDomain != NULL) && EQUAL(pszDomain, "COLOR_PROFILE"))
        bColorProfileMetadataChanged = TRUE;
    else if( pszDomain == NULL || !EQUAL(pszDomain,"_temporary_") )
    {
        bMetadataChanged = TRUE;
        // Cancel any existing metadata from PAM file
        if( eAccess == GA_Update &&
            GDALPamDataset::GetMetadataItem(pszName, pszDomain) != NULL )
            GDALPamDataset::SetMetadataItem(pszName, NULL, pszDomain);
    }

    if( (pszDomain == NULL || EQUAL(pszDomain, "")) &&
        pszName != NULL && EQUAL(pszName, GDALMD_AREA_OR_POINT) )
    {
        LookForProjection();
        bGeoTIFFInfoChanged = TRUE;
    }

    return oGTiffMDMD.SetMetadataItem( pszName, pszValue, pszDomain );
}

/************************************************************************/
/*                         GetInternalHandle()                          */
/************************************************************************/

void *GTiffDataset::GetInternalHandle( const char * /* pszHandleName */ )

{
    return hTIFF;
}

/************************************************************************/
/*                         LoadEXIFMetadata()                           */
/************************************************************************/

void GTiffDataset::LoadEXIFMetadata()
{
    if (bEXIFMetadataLoaded)
        return;
    bEXIFMetadataLoaded = TRUE;

    if (!SetDirectory())
        return;

    VSILFILE* fp = VSI_TIFFGetVSILFile(TIFFClientdata( hTIFF ));

    GByte          abyHeader[2];
    if( VSIFSeekL(fp, 0, SEEK_SET) != 0 ||
        VSIFReadL(abyHeader, 1, 2, fp) != 2 )
        return;

    int bLittleEndian = abyHeader[0] == 'I' && abyHeader[1] == 'I';
    int bSwabflag = bLittleEndian ^ CPL_IS_LSB;

    char** papszMetadata = NULL;
    toff_t nOffset;

    if (TIFFGetField(hTIFF, TIFFTAG_EXIFIFD, &nOffset))
    {
        int nExifOffset = (int)nOffset, nInterOffset = 0, nGPSOffset = 0;
        EXIFExtractMetadata(papszMetadata,
                            fp, (int)nOffset,
                            bSwabflag, 0,
                            nExifOffset, nInterOffset, nGPSOffset);
    }

    if (TIFFGetField(hTIFF, TIFFTAG_GPSIFD, &nOffset))
    {
        int nExifOffset = 0, nInterOffset = 0, nGPSOffset = (int)nOffset;
        EXIFExtractMetadata(papszMetadata,
                            fp, (int)nOffset,
                            bSwabflag, 0,
                            nExifOffset, nInterOffset, nGPSOffset);
    }

    oGTiffMDMD.SetMetadata( papszMetadata, "EXIF" );
    CSLDestroy( papszMetadata );
}

/************************************************************************/
/*                           LoadMetadata()                             */
/************************************************************************/
void GTiffDataset::LoadMetadata()
{
    if(TRUE == bIMDRPCMetadataLoaded)
        return;
    bIMDRPCMetadataLoaded = TRUE;

    GDALMDReaderManager mdreadermanager;
    GDALMDReaderBase* mdreader = mdreadermanager.GetReader(osFilename,
                                         oOvManager.GetSiblingFiles(), MDR_ANY);

    if(NULL != mdreader)
    {
        mdreader->FillMetadata(&oGTiffMDMD);

        if(mdreader->GetMetadataDomain(MD_DOMAIN_RPC) == NULL)
        {
            char** papszRPCMD = GTiffDatasetReadRPCTag(hTIFF);
            if( papszRPCMD )
            {
                oGTiffMDMD.SetMetadata( papszRPCMD, MD_DOMAIN_RPC );
                CSLDestroy( papszRPCMD );
            }
        }

        papszMetadataFiles = mdreader->GetMetadataFiles();
    }
    else
    {
        char** papszRPCMD = GTiffDatasetReadRPCTag(hTIFF);
        if( papszRPCMD )
        {
            oGTiffMDMD.SetMetadata( papszRPCMD, MD_DOMAIN_RPC );
            CSLDestroy( papszRPCMD );
        }
    }
}

/************************************************************************/
/*                            GetFileList()                             */
/************************************************************************/

char **GTiffDataset::GetFileList()

{
    LoadGeoreferencingAndPamIfNeeded();

    char **papszFileList = GDALPamDataset::GetFileList();

    LoadMetadata();
    if(NULL != papszMetadataFiles)
    {
        for( int i = 0; papszMetadataFiles[i] != NULL; i++ )
        {
            papszFileList = CSLAddString( papszFileList, papszMetadataFiles[i] );
        }
    }

    if (osGeorefFilename.size() != 0 &&
        CSLFindString(papszFileList, osGeorefFilename) == -1)
    {
        papszFileList = CSLAddString( papszFileList, osGeorefFilename );
    }

    return papszFileList;
}

/************************************************************************/
/*                         CreateMaskBand()                             */
/************************************************************************/

CPLErr GTiffDataset::CreateMaskBand(int nFlagsIn)
{
    ScanDirectories();

    if (poMaskDS != NULL)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "This TIFF dataset has already an internal mask band");
        return CE_Failure;
    }
    else if (CPLTestBool(CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK", "NO")))
    {
        toff_t  nOffset;
        int     bIsTiled;
        int     bIsOverview = FALSE;
        uint32 nSubType;
        int     l_nCompression;

        if (nFlagsIn != GMF_PER_DATASET)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "The only flag value supported for internal mask is GMF_PER_DATASET");
            return CE_Failure;
        }

        if( strstr(GDALGetMetadataItem(GDALGetDriverByName( "GTiff" ),
                                       GDAL_DMD_CREATIONOPTIONLIST, NULL ),
                   "<Value>DEFLATE</Value>") != NULL )
            l_nCompression = COMPRESSION_ADOBE_DEFLATE;
        else
            l_nCompression = COMPRESSION_PACKBITS;

    /* -------------------------------------------------------------------- */
    /*      If we don't have read access, then create the mask externally.  */
    /* -------------------------------------------------------------------- */
        if( GetAccess() != GA_Update )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                    "File open for read-only accessing, "
                    "creating mask externally." );

            return GDALPamDataset::CreateMaskBand(nFlagsIn);
        }

        if (poBaseDS)
        {
            if (!poBaseDS->SetDirectory())
                return CE_Failure;
        }
        if (!SetDirectory())
            return CE_Failure;

        if( TIFFGetField(hTIFF, TIFFTAG_SUBFILETYPE, &nSubType))
        {
            bIsOverview = (nSubType & FILETYPE_REDUCEDIMAGE) != 0;

            if ((nSubType & FILETYPE_MASK) != 0)
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "Cannot create a mask on a TIFF mask IFD !" );
                return CE_Failure;
            }
        }

        bIsTiled = TIFFIsTiled(hTIFF);

        FlushDirectory();

        nOffset = GTIFFWriteDirectory(hTIFF,
                                      (bIsOverview) ? FILETYPE_REDUCEDIMAGE | FILETYPE_MASK : FILETYPE_MASK,
                                      nRasterXSize, nRasterYSize,
                                      1, PLANARCONFIG_CONTIG, 1,
                                      nBlockXSize, nBlockYSize,
                                      bIsTiled, l_nCompression,
                                      PHOTOMETRIC_MASK, PREDICTOR_NONE,
                                      SAMPLEFORMAT_UINT, NULL, NULL, NULL, 0, NULL, "");
        if (nOffset == 0)
            return CE_Failure;

        poMaskDS = new GTiffDataset();
        poMaskDS->bPromoteTo8Bits = CPLTestBool(CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK_TO_8BIT", "YES"));
        if( poMaskDS->OpenOffset( hTIFF, ppoActiveDSRef, nOffset,
                                  FALSE, GA_Update ) != CE_None)
        {
            delete poMaskDS;
            poMaskDS = NULL;
            return CE_Failure;
        }

        return CE_None;
    }
    else
    {
        return GDALPamDataset::CreateMaskBand(nFlagsIn);
    }
}

/************************************************************************/
/*                         CreateMaskBand()                             */
/************************************************************************/

CPLErr GTiffRasterBand::CreateMaskBand(int nFlagsIn)
{
    poGDS->ScanDirectories();

    if (poGDS->poMaskDS != NULL)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "This TIFF dataset has already an internal mask band");
        return CE_Failure;
    }
    else if (CPLTestBool(CPLGetConfigOption("GDAL_TIFF_INTERNAL_MASK", "NO")))
    {
        return poGDS->CreateMaskBand(nFlagsIn);
    }
    else
    {
        return GDALPamRasterBand::CreateMaskBand(nFlagsIn);
    }
}

/************************************************************************/
/*                       PrepareTIFFErrorFormat()                       */
/*                                                                      */
/*      sometimes the "module" has stuff in it that has special         */
/*      meaning in a printf() style format, so we try to escape it.     */
/*      For now we hope the only thing we have to escape is %'s.        */
/************************************************************************/

static char *PrepareTIFFErrorFormat( const char *module, const char *fmt )

{
    char      *pszModFmt;
    int       iIn, iOut;

    pszModFmt = (char *) CPLMalloc( strlen(module)*2 + strlen(fmt) + 2 );
    for( iOut = 0, iIn = 0; module[iIn] != '\0'; iIn++ )
    {
        if( module[iIn] == '%' )
        {
            pszModFmt[iOut++] = '%';
            pszModFmt[iOut++] = '%';
        }
        else
            pszModFmt[iOut++] = module[iIn];
    }
    pszModFmt[iOut] = '\0';
    strcat( pszModFmt, ":" );
    strcat( pszModFmt, fmt );

    return pszModFmt;
}

/************************************************************************/
/*                        GTiffWarningHandler()                         */
/************************************************************************/
static void
GTiffWarningHandler(const char* module, const char* fmt, va_list ap )
{
    char *pszModFmt;

    if( strstr(fmt,"nknown field") != NULL )
        return;

    pszModFmt = PrepareTIFFErrorFormat( module, fmt );
    if( strstr(fmt, "does not end in null byte") != NULL )
    {
        CPLString osMsg;
        osMsg.vPrintf(pszModFmt, ap);
        CPLDebug( "GTiff", "%s", osMsg.c_str() );
    }
    else
        CPLErrorV( CE_Warning, CPLE_AppDefined, pszModFmt, ap );
    CPLFree( pszModFmt );
}

/************************************************************************/
/*                         GTiffErrorHandler()                          */
/************************************************************************/
static void
GTiffErrorHandler(const char* module, const char* fmt, va_list ap )
{
    char *pszModFmt;

#if SIZEOF_VOIDP == 4
    /* Case of one-strip file where the strip size is > 2GB (#5403) */
    if( strcmp(module, "TIFFStripSize") == 0 &&
        strstr(fmt, "Integer overflow") != NULL )
    {
        bGlobalStripIntegerOverflow = TRUE;
        return;
    }
    if( bGlobalStripIntegerOverflow &&
        strstr(fmt, "Cannot handle zero strip size") != NULL )
    {
        return;
    }
#endif

#ifdef BIGTIFF_SUPPORT
    if( strcmp(fmt, "Maximum TIFF file size exceeded") == 0 )
    {
        // Ideally there would be a thread-safe way of setting this flag,
        // but we cannot really use the extended error handler, since the
        // handler is for all TIFF handles, and not necessarily the ones of
        // this driver.
        if( bGlobalInExternalOvr )
            fmt = "Maximum TIFF file size exceeded. Use --config BIGTIFF_OVERVIEW YES configuration option.";
        else
            fmt = "Maximum TIFF file size exceeded. Use BIGTIFF=YES creation option.";
    }
#endif

    pszModFmt = PrepareTIFFErrorFormat( module, fmt );
    CPLErrorV( CE_Failure, CPLE_AppDefined, pszModFmt, ap );
    CPLFree( pszModFmt );
}

/************************************************************************/
/*                          GTiffTagExtender()                          */
/*                                                                      */
/*      Install tags specially known to GDAL.                           */
/************************************************************************/

static TIFFExtendProc _ParentExtender = NULL;

static void GTiffTagExtender(TIFF *tif)

{
    static const TIFFFieldInfo xtiffFieldInfo[] = {
        { TIFFTAG_GDAL_METADATA,    -1,-1, TIFF_ASCII, FIELD_CUSTOM,
          TRUE, FALSE, (char*) "GDALMetadata" },
        { TIFFTAG_GDAL_NODATA, -1,-1, TIFF_ASCII, FIELD_CUSTOM,
          TRUE, FALSE, (char*) "GDALNoDataValue" },
        { TIFFTAG_RPCCOEFFICIENT,   -1,-1, TIFF_DOUBLE, FIELD_CUSTOM,
          TRUE, TRUE, (char*) "RPCCoefficient" }
    };

    if (_ParentExtender)
        (*_ParentExtender)(tif);

    TIFFMergeFieldInfo( tif, xtiffFieldInfo,
                        sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0]) );
}

/************************************************************************/
/*                          GTiffOneTimeInit()                          */
/*                                                                      */
/*      This is stuff that is initialized for the TIFF library just     */
/*      once.  We deliberately defer the initialization till the        */
/*      first time we are likely to call into libtiff to avoid          */
/*      unnecessary paging in of the library for GDAL apps that         */
/*      don't use it.                                                   */
/************************************************************************/
#if defined(HAVE_DLFCN_H) && !defined(WIN32)
#include <dlfcn.h>
#endif

static CPLMutex* hGTiffOneTimeInitMutex = NULL;

int GTiffOneTimeInit()

{
    static int bInitIsOk = TRUE;
    static int bOneTimeInitDone = FALSE;
    CPLMutexHolder oHolder( &hGTiffOneTimeInitMutex);
    if( bOneTimeInitDone )
        return bInitIsOk;

    bOneTimeInitDone = TRUE;

    /* This is a frequent configuration error that is difficult to track down */
    /* for people unaware of the issue : GDAL built against internal libtiff (4.X) */
    /* but used by an application that links with external libtiff (3.X) */
    /* Note: on my conf, the order that cause GDAL to crash - and that is detected */
    /* by the following code - is "-ltiff -lgdal". "-lgdal -ltiff" works for the */
    /* GTiff driver but probably breaks the application that believes it uses libtiff 3.X */
    /* but we cannot detect that... */
#if defined(BIGTIFF_SUPPORT) && !defined(RENAME_INTERNAL_LIBTIFF_SYMBOLS)
#if defined(HAVE_DLFCN_H) && !defined(WIN32)
    const char* (*pfnVersion)(void);
    pfnVersion = (const char* (*)(void)) dlsym(RTLD_DEFAULT, "TIFFGetVersion");
    if (pfnVersion)
    {
        const char* pszVersion = pfnVersion();
        if (pszVersion && strstr(pszVersion, "Version 3.") != NULL)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "libtiff version mismatch : You're linking against libtiff 3.X, but GDAL has been compiled against libtiff >= 4.0.0");
        }
    }
#endif
#endif

    _ParentExtender = TIFFSetTagExtender(GTiffTagExtender);

    TIFFSetWarningHandler( GTiffWarningHandler );
    TIFFSetErrorHandler( GTiffErrorHandler );

    LibgeotiffOneTimeInit();

    return TRUE;
}

/************************************************************************/
/*                        GDALDeregister_GTiff()                        */
/************************************************************************/

static
void GDALDeregister_GTiff( GDALDriver * )

{
    CSVDeaccess( NULL );

#if defined(LIBGEOTIFF_VERSION) && LIBGEOTIFF_VERSION > 1150
    GTIFDeaccessCSV();
#endif

    if( hGTiffOneTimeInitMutex != NULL )
    {
        CPLDestroyMutex(hGTiffOneTimeInitMutex);
        hGTiffOneTimeInitMutex = NULL;
    }

    LibgeotiffOneTimeCleanupMutex();
}

/************************************************************************/
/*                   GTIFFGetCompressionMethod()                        */
/************************************************************************/

int GTIFFGetCompressionMethod(const char* pszValue, const char* pszVariableName)
{
    int nCompression = COMPRESSION_NONE;
    if( EQUAL( pszValue, "NONE" ) )
        nCompression = COMPRESSION_NONE;
    else if( EQUAL( pszValue, "JPEG" ) )
        nCompression = COMPRESSION_JPEG;
    else if( EQUAL( pszValue, "LZW" ) )
        nCompression = COMPRESSION_LZW;
    else if( EQUAL( pszValue, "PACKBITS" ))
        nCompression = COMPRESSION_PACKBITS;
    else if( EQUAL( pszValue, "DEFLATE" ) || EQUAL( pszValue, "ZIP" ))
        nCompression = COMPRESSION_ADOBE_DEFLATE;
    else if( EQUAL( pszValue, "FAX3" )
                || EQUAL( pszValue, "CCITTFAX3" ))
        nCompression = COMPRESSION_CCITTFAX3;
    else if( EQUAL( pszValue, "FAX4" )
                || EQUAL( pszValue, "CCITTFAX4" ))
        nCompression = COMPRESSION_CCITTFAX4;
    else if( EQUAL( pszValue, "CCITTRLE" ) )
        nCompression = COMPRESSION_CCITTRLE;
    else if( EQUAL( pszValue, "LZMA" ) )
        nCompression = COMPRESSION_LZMA;
    else
        CPLError( CE_Warning, CPLE_IllegalArg,
                    "%s=%s value not recognised, ignoring.",
                    pszVariableName,pszValue );

#if defined(TIFFLIB_VERSION) && TIFFLIB_VERSION > 20031007 /* 3.6.0 */
    if (nCompression != COMPRESSION_NONE &&
        !TIFFIsCODECConfigured((uint16) nCompression))
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                "Cannot create TIFF file due to missing codec for %s.", pszValue );
        return -1;
    }
#endif

    return nCompression;
}

/************************************************************************/
/*                          GDALRegister_GTiff()                        */
/************************************************************************/

void GDALRegister_GTiff()

{
    if( GDALGetDriverByName( "GTiff" ) != NULL )
        return;

    char szCreateOptions[5000];
    char szOptionalCompressItems[500];
    bool bHasJPEG = false;
    bool bHasLZW = false;
    bool bHasDEFLATE = false;
    bool bHasLZMA = false;

    GDALDriver *poDriver = new GDALDriver();

/* -------------------------------------------------------------------- */
/*      Determine which compression codecs are available that we        */
/*      want to advertise.  If we are using an old libtiff we won't     */
/*      be able to find out so we just assume all are available.        */
/* -------------------------------------------------------------------- */
    strcpy( szOptionalCompressItems, "       <Value>NONE</Value>" );

#if TIFFLIB_VERSION <= 20040919
    strcat( szOptionalCompressItems,
            "       <Value>PACKBITS</Value>"
            "       <Value>JPEG</Value>"
            "       <Value>LZW</Value>"
            "       <Value>DEFLATE</Value>" );
    bHasLZW = bHasDEFLATE = true;
#else
    TIFFCodec *c;
    TIFFCodec *codecs = TIFFGetConfiguredCODECs();

    for( c = codecs; c->name; c++ )
    {
        if( c->scheme == COMPRESSION_PACKBITS )
            strcat( szOptionalCompressItems,
                    "       <Value>PACKBITS</Value>" );
        else if( c->scheme == COMPRESSION_JPEG )
        {
            bHasJPEG = true;
            strcat( szOptionalCompressItems,
                    "       <Value>JPEG</Value>" );
        }
        else if( c->scheme == COMPRESSION_LZW )
        {
            bHasLZW = TRUE;
            strcat( szOptionalCompressItems,
                    "       <Value>LZW</Value>" );
        }
        else if( c->scheme == COMPRESSION_ADOBE_DEFLATE )
        {
            bHasDEFLATE = TRUE;
            strcat( szOptionalCompressItems,
                    "       <Value>DEFLATE</Value>" );
        }
        else if( c->scheme == COMPRESSION_CCITTRLE )
            strcat( szOptionalCompressItems,
                    "       <Value>CCITTRLE</Value>" );
        else if( c->scheme == COMPRESSION_CCITTFAX3 )
            strcat( szOptionalCompressItems,
                    "       <Value>CCITTFAX3</Value>" );
        else if( c->scheme == COMPRESSION_CCITTFAX4 )
            strcat( szOptionalCompressItems,
                    "       <Value>CCITTFAX4</Value>" );
        else if( c->scheme == COMPRESSION_LZMA )
        {
            bHasLZMA = true;
            strcat( szOptionalCompressItems,
                    "       <Value>LZMA</Value>" );
        }
    }
    _TIFFfree( codecs );
#endif

/* -------------------------------------------------------------------- */
/*      Build full creation option list.                                */
/* -------------------------------------------------------------------- */
    snprintf( szCreateOptions, sizeof(szCreateOptions), "%s%s%s",
              "<CreationOptionList>"
              "   <Option name='COMPRESS' type='string-select'>",
              szOptionalCompressItems,
              "   </Option>");
    if (bHasLZW || bHasDEFLATE)
        strcat( szCreateOptions, ""
"   <Option name='PREDICTOR' type='int' description='Predictor Type (1=default, 2=horizontal differencing, 3=floating point prediction)'/>");
    strcat( szCreateOptions, ""
"   <Option name='DISCARD_LSB' type='string' description='Number of least-significant bits to set to clear as a single value or comma-separated list of values for per-band values'/>" );
    if (bHasJPEG)
    {
        strcat( szCreateOptions, ""
"   <Option name='JPEG_QUALITY' type='int' description='JPEG quality 1-100' default='75'/>"
"   <Option name='JPEGTABLESMODE' type='int' description='Content of JPEGTABLES tag. 0=no JPEGTABLES tag, 1=Quantization tables only, 2=Huffman tables only, 3=Both' default='1'/>" );
#ifdef JPEG_DIRECT_COPY
        strcat( szCreateOptions, ""
"   <Option name='JPEG_DIRECT_COPY' type='boolean' description='To copy without any decompression/recompression a JPEG source file' default='NO'/>");
#endif
    }
    if (bHasDEFLATE)
        strcat( szCreateOptions, ""
"   <Option name='ZLEVEL' type='int' description='DEFLATE compression level 1-9' default='6'/>");
    if (bHasLZMA)
        strcat( szCreateOptions, ""
"   <Option name='LZMA_PRESET' type='int' description='LZMA compression level 0(fast)-9(slow)' default='6'/>");
    strcat( szCreateOptions, ""
"   <Option name='NUM_THREADS' type='string' description='Number of worker threads for compression. Can be set to ALL_CPUS' default='1'/>"
"   <Option name='NBITS' type='int' description='BITS for sub-byte files (1-7), sub-uint16 (9-15), sub-uint32 (17-31)'/>"
"   <Option name='INTERLEAVE' type='string-select' default='PIXEL'>"
"       <Value>BAND</Value>"
"       <Value>PIXEL</Value>"
"   </Option>"
"   <Option name='TILED' type='boolean' description='Switch to tiled format'/>"
"   <Option name='TFW' type='boolean' description='Write out world file'/>"
"   <Option name='RPB' type='boolean' description='Write out .RPB (RPC) file'/>"
"   <Option name='RPCTXT' type='boolean' description='Write out _RPC.TXT file'/>"
"   <Option name='BLOCKXSIZE' type='int' description='Tile Width'/>"
"   <Option name='BLOCKYSIZE' type='int' description='Tile/Strip Height'/>"
"   <Option name='PHOTOMETRIC' type='string-select'>"
"       <Value>MINISBLACK</Value>"
"       <Value>MINISWHITE</Value>"
"       <Value>PALETTE</Value>"
"       <Value>RGB</Value>"
"       <Value>CMYK</Value>"
"       <Value>YCBCR</Value>"
"       <Value>CIELAB</Value>"
"       <Value>ICCLAB</Value>"
"       <Value>ITULAB</Value>"
"   </Option>"
"   <Option name='SPARSE_OK' type='boolean' description='Can newly created files have missing blocks?' default='FALSE'/>"
"   <Option name='ALPHA' type='string-select' description='Mark first extrasample as being alpha'>"
"       <Value>NON-PREMULTIPLIED</Value>"
"       <Value>PREMULTIPLIED</Value>"
"       <Value>UNSPECIFIED</Value>"
"       <Value aliasOf='NON-PREMULTIPLIED'>YES</Value>"
"       <Value aliasOf='UNSPECIFIED'>NO</Value>"
"   </Option>"
"   <Option name='PROFILE' type='string-select' default='GDALGeoTIFF'>"
"       <Value>GDALGeoTIFF</Value>"
"       <Value>GeoTIFF</Value>"
"       <Value>BASELINE</Value>"
"   </Option>"
"   <Option name='PIXELTYPE' type='string-select'>"
"       <Value>DEFAULT</Value>"
"       <Value>SIGNEDBYTE</Value>"
"   </Option>"
#ifdef BIGTIFF_SUPPORT
"   <Option name='BIGTIFF' type='string-select' description='Force creation of BigTIFF file'>"
"     <Value>YES</Value>"
"     <Value>NO</Value>"
"     <Value>IF_NEEDED</Value>"
"     <Value>IF_SAFER</Value>"
"   </Option>"
#endif
"   <Option name='ENDIANNESS' type='string-select' default='NATIVE' description='Force endianness of created file. For DEBUG purpose mostly'>"
"       <Value>NATIVE</Value>"
"       <Value>INVERTED</Value>"
"       <Value>LITTLE</Value>"
"       <Value>BIG</Value>"
"   </Option>"
"   <Option name='COPY_SRC_OVERVIEWS' type='boolean' default='NO' description='Force copy of overviews of source dataset (CreateCopy())'/>"
"   <Option name='SOURCE_ICC_PROFILE' type='string' description='ICC profile'/>"
"   <Option name='SOURCE_PRIMARIES_RED' type='string' description='x,y,1.0 (xyY) red chromaticity'/>"
"   <Option name='SOURCE_PRIMARIES_GREEN' type='string' description='x,y,1.0 (xyY) green chromaticity'/>"
"   <Option name='SOURCE_PRIMARIES_BLUE' type='string' description='x,y,1.0 (xyY) blue chromaticity'/>"
"   <Option name='SOURCE_WHITEPOINT' type='string' description='x,y,1.0 (xyY) whitepoint'/>"
"   <Option name='TIFFTAG_TRANSFERFUNCTION_RED' type='string' description='Transfer function for red'/>"
"   <Option name='TIFFTAG_TRANSFERFUNCTION_GREEN' type='string' description='Transfer function for green'/>"
"   <Option name='TIFFTAG_TRANSFERFUNCTION_BLUE' type='string' description='Transfer function for blue'/>"
"   <Option name='TIFFTAG_TRANSFERRANGE_BLACK' type='string' description='Transfer range for black'/>"
"   <Option name='TIFFTAG_TRANSFERRANGE_WHITE' type='string' description='Transfer range for white'/>"
"   <Option name='STREAMABLE_OUTPUT' type='boolean' default='NO' description='Enforce a mode compatible with a streamable file'/>"
"   <Option name='GEOTIFF_KEYS_FLAVOR' type='string-select' default='STANDARD' description='Which flavor of GeoTIFF keys must be used'>"
"       <Value>STANDARD</Value>"
"       <Value>ESRI_PE</Value>"
"   </Option>"
"</CreationOptionList>" );

/* -------------------------------------------------------------------- */
/*      Set the driver details.                                         */
/* -------------------------------------------------------------------- */
    poDriver->SetDescription( "GTiff" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "GeoTIFF" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_gtiff.html" );
    poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/tiff" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "tif" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSIONS, "tif tiff" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
                               "Byte UInt16 Int16 UInt32 Int32 Float32 "
                               "Float64 CInt16 CInt32 CFloat32 CFloat64" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, szCreateOptions );
    poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST,
"<OpenOptionList>"
"   <Option name='NUM_THREADS' type='string' description='Number of worker threads for compression. Can be set to ALL_CPUS' default='1'/>"
"   <Option name='GEOTIFF_KEYS_FLAVOR' type='string-select' default='STANDARD' description='Which flavor of GeoTIFF keys must be used (for writing)'>"
"       <Value>STANDARD</Value>"
"       <Value>ESRI_PE</Value>"
"   </Option>"
"</OpenOptionList>" );
    poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

#ifdef INTERNAL_LIBTIFF
    poDriver->SetMetadataItem( "LIBTIFF", "INTERNAL" );
#else
    poDriver->SetMetadataItem( "LIBTIFF", TIFFLIB_VERSION_STR );
#endif

    poDriver->pfnOpen = GTiffDataset::Open;
    poDriver->pfnCreate = GTiffDataset::Create;
    poDriver->pfnCreateCopy = GTiffDataset::CreateCopy;
    poDriver->pfnUnloadDriver = GDALDeregister_GTiff;
    poDriver->pfnIdentify = GTiffDataset::Identify;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
