/******************************************************************************
 * tif_overview.c,v 1.9 2005/05/25 09:03:16 dron Exp
 *
 * Project:  TIFF Overview Builder
 * Purpose:  Library function for building overviews in a TIFF file.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 * Notes:
 *  o Currently only images with bits_per_sample of a multiple of eight
 *    will work.
 *
 *  o The downsampler currently just takes the top left pixel from the
 *    source rectangle.  Eventually sampling options of averaging, mode, and
 *    ``center pixel'' should be offered.
 *
 *  o The code will attempt to use the same kind of compression,
 *    photometric interpretation, and organization as the source image, but
 *    it doesn't copy geotiff tags to the reduced resolution images.
 *
 *  o Reduced resolution overviews for multi-sample files will currently
 *    always be generated as PLANARCONFIG_SEPARATE.  This could be fixed
 *    reasonable easily if needed to improve compatibility with other
 *    packages.  Many don't properly support PLANARCONFIG_SEPARATE.
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 *
 * 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.
 ******************************************************************************
 */

/* TODO: update notes in header above */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "tif_ovrcache.h"
#include "tiffio.h"

#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif

#ifndef MAX
#define MIN(a, b) ((a < b) ? a : b)
#define MAX(a, b) ((a > b) ? a : b)
#endif

#define TIFF_DIR_MAX 65534

void TIFFBuildOverviews(TIFF *, int, int *, int, const char *,
                        int (*)(double, void *), void *);

/************************************************************************/
/*                         TIFF_WriteOverview()                         */
/*                                                                      */
/*      Create a new directory, without any image data for an overview. */
/*      Returns offset of newly created overview directory, but the     */
/*      current directory is reset to be the one in used when this      */
/*      function is called.                                             */
/************************************************************************/

uint32_t TIFF_WriteOverview(TIFF *hTIFF, uint32_t nXSize, uint32_t nYSize,
                            int nBitsPerPixel, int nPlanarConfig, int nSamples,
                            int nBlockXSize, int nBlockYSize, int bTiled,
                            int nCompressFlag, int nPhotometric,
                            int nSampleFormat, unsigned short *panRed,
                            unsigned short *panGreen, unsigned short *panBlue,
                            int bUseSubIFDs, int nHorSubsampling,
                            int nVerSubsampling)

{
    toff_t nBaseDirOffset;
    toff_t nOffset;
    tdir_t iNumDir;

    (void)bUseSubIFDs;

    nBaseDirOffset = TIFFCurrentDirOffset(hTIFF);

    TIFFCreateDirectory(hTIFF);

    /* -------------------------------------------------------------------- */
    /*      Setup TIFF fields.                                              */
    /* -------------------------------------------------------------------- */
    TIFFSetField(hTIFF, TIFFTAG_IMAGEWIDTH, nXSize);
    TIFFSetField(hTIFF, TIFFTAG_IMAGELENGTH, nYSize);
    if (nSamples == 1)
        TIFFSetField(hTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    else
        TIFFSetField(hTIFF, TIFFTAG_PLANARCONFIG, nPlanarConfig);

    TIFFSetField(hTIFF, TIFFTAG_BITSPERSAMPLE, nBitsPerPixel);
    TIFFSetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, nSamples);
    TIFFSetField(hTIFF, TIFFTAG_COMPRESSION, nCompressFlag);
    TIFFSetField(hTIFF, TIFFTAG_PHOTOMETRIC, nPhotometric);
    TIFFSetField(hTIFF, TIFFTAG_SAMPLEFORMAT, nSampleFormat);

    if (bTiled)
    {
        TIFFSetField(hTIFF, TIFFTAG_TILEWIDTH, nBlockXSize);
        TIFFSetField(hTIFF, TIFFTAG_TILELENGTH, nBlockYSize);
    }
    else
        TIFFSetField(hTIFF, TIFFTAG_ROWSPERSTRIP, nBlockYSize);

    TIFFSetField(hTIFF, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE);

    if (nPhotometric == PHOTOMETRIC_YCBCR || nPhotometric == PHOTOMETRIC_ITULAB)
    {
        TIFFSetField(hTIFF, TIFFTAG_YCBCRSUBSAMPLING, nHorSubsampling,
                     nVerSubsampling);
        /* TODO: also write YCbCrPositioning and YCbCrCoefficients tag identical
         * to source IFD */
    }
    /* TODO: add command-line parameter for selecting jpeg compression quality
     * that gets ignored when compression isn't jpeg */

    /* -------------------------------------------------------------------- */
    /*	Write color table if one is present.				*/
    /* -------------------------------------------------------------------- */
    if (panRed != NULL)
    {
        TIFFSetField(hTIFF, TIFFTAG_COLORMAP, panRed, panGreen, panBlue);
    }

    /* -------------------------------------------------------------------- */
    /*      Write directory, and return byte offset.                        */
    /* -------------------------------------------------------------------- */
    if (TIFFWriteCheck(hTIFF, bTiled, "TIFFBuildOverviews") == 0)
        return 0;

    if (!TIFFWriteDirectory(hTIFF))
        return 0;
    iNumDir = TIFFNumberOfDirectories(hTIFF);
    if (iNumDir > TIFF_DIR_MAX)
    {
        TIFFErrorExt(TIFFClientdata(hTIFF), "TIFF_WriteOverview",
                     "File `%s' has too many directories.\n",
                     TIFFFileName(hTIFF));
        exit(-1);
    }
    TIFFSetDirectory(hTIFF, (tdir_t)(iNumDir - 1));

    nOffset = TIFFCurrentDirOffset(hTIFF);

    TIFFSetSubDirectory(hTIFF, nBaseDirOffset);

    return nOffset;
}

/************************************************************************/
/*                       TIFF_GetSourceSamples()                        */
/************************************************************************/

static void TIFF_GetSourceSamples(double *padfSamples, unsigned char *pabySrc,
                                  int nPixelBytes, int nSampleFormat,
                                  uint32_t nXSize, uint32_t nYSize,
                                  int nPixelOffset, int nLineOffset)
{
    uint32_t iXOff, iYOff;
    int iSample;

    iSample = 0;

    for (iYOff = 0; iYOff < nYSize; iYOff++)
    {
        for (iXOff = 0; iXOff < nXSize; iXOff++)
        {
            unsigned char *pabyData;

            pabyData = pabySrc + iYOff * nLineOffset + iXOff * nPixelOffset;

            if (nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 1)
            {
                padfSamples[iSample++] = *pabyData;
            }
            else if (nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 2)
            {
                padfSamples[iSample++] = ((uint16_t *)pabyData)[0];
            }
            else if (nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 4)
            {
                padfSamples[iSample++] = ((uint32_t *)pabyData)[0];
            }
            else if (nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 2)
            {
                padfSamples[iSample++] = ((int16_t *)pabyData)[0];
            }
            else if (nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 32)
            {
                padfSamples[iSample++] = ((int32_t *)pabyData)[0];
            }
            else if (nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 4)
            {
                padfSamples[iSample++] = ((float *)pabyData)[0];
            }
            else if (nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 8)
            {
                padfSamples[iSample++] = ((double *)pabyData)[0];
            }
        }
    }
}

/************************************************************************/
/*                           TIFF_SetSample()                           */
/************************************************************************/

static void TIFF_SetSample(unsigned char *pabyData, int nPixelBytes,
                           int nSampleFormat, double dfValue)

{
    if (nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 1)
    {
        *pabyData = (unsigned char)MAX(0, MIN(255, dfValue));
    }
    else if (nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 2)
    {
        *((uint16_t *)pabyData) = (uint16_t)MAX(0, MIN(65535, dfValue));
    }
    else if (nSampleFormat == SAMPLEFORMAT_UINT && nPixelBytes == 4)
    {
        *((uint32_t *)pabyData) = (uint32_t)dfValue;
    }
    else if (nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 2)
    {
        *((int16_t *)pabyData) = (int16_t)MAX(-32768, MIN(32767, dfValue));
    }
    else if (nSampleFormat == SAMPLEFORMAT_INT && nPixelBytes == 32)
    {
        *((int32_t *)pabyData) = (int32_t)dfValue;
    }
    else if (nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 4)
    {
        *((float *)pabyData) = (float)dfValue;
    }
    else if (nSampleFormat == SAMPLEFORMAT_IEEEFP && nPixelBytes == 8)
    {
        *((double *)pabyData) = dfValue;
    }
}

/************************************************************************/
/*                          TIFF_DownSample()                           */
/*                                                                      */
/*      Down sample a tile of full res data into a window of a tile     */
/*      of downsampled data.                                            */
/************************************************************************/

static void TIFF_DownSample(unsigned char *pabySrcTile, uint32_t nBlockXSize,
                            uint32_t nBlockYSize, int nPixelSkewBits,
                            int nBitsPerPixel, unsigned char *pabyOTile,
                            uint32_t nOBlockXSize, uint32_t nOBlockYSize,
                            uint32_t nTXOff, uint32_t nTYOff, int nOMult,
                            int nSampleFormat, const char *pszResampling)

{
    uint32_t i, j;
    int k, nPixelBytes = (nBitsPerPixel) / 8;
    int nPixelGroupBytes = (nBitsPerPixel + nPixelSkewBits) / 8;
    unsigned char *pabySrc, *pabyDst;
    double *padfSamples;
    size_t tpadfSamples_size, padfSamples_size;

    assert(nBitsPerPixel >= 8);

    /* sizeof(double) * nOMult * nOMult */
    tpadfSamples_size = nOMult * nOMult;
    if ((nOMult != 0) && (tpadfSamples_size / nOMult == (size_t)nOMult))
    {
        padfSamples_size = tpadfSamples_size;
        tpadfSamples_size = padfSamples_size * sizeof(double);
        if ((tpadfSamples_size / padfSamples_size) == sizeof(double))
            padfSamples_size = tpadfSamples_size;
        else
            padfSamples_size = 0;
    }
    else
    {
        padfSamples_size = 0;
    }
    if (padfSamples_size == 0)
    {
        /* TODO: This is an error condition */
        return;
    }
    padfSamples = (double *)malloc(padfSamples_size);

    /* ==================================================================== */
    /*      Loop over scanline chunks to process, establishing where the    */
    /*      data is going.                                                  */
    /* ==================================================================== */
    for (j = 0; j * nOMult < nBlockYSize; j++)
    {
        if (j + nTYOff >= nOBlockYSize)
            break;

        pabyDst = pabyOTile + ((j + nTYOff) * nOBlockXSize + nTXOff) *
                                  nPixelBytes * nPixelGroupBytes;

        /* --------------------------------------------------------------------
         */
        /*      Handler nearest resampling ... we don't even care about the */
        /*      data type, we just do a bytewise copy. */
        /* --------------------------------------------------------------------
         */
        if (strncmp(pszResampling, "nearest", 4) == 0 ||
            strncmp(pszResampling, "NEAR", 4) == 0)
        {
            pabySrc = pabySrcTile + j * nOMult * nBlockXSize * nPixelGroupBytes;

            for (i = 0; i * nOMult < nBlockXSize; i++)
            {
                if (i + nTXOff >= nOBlockXSize)
                    break;

                /*
                 * For now use simple subsampling, from the top left corner
                 * of the source block of pixels.
                 */

                for (k = 0; k < nPixelBytes; k++)
                    pabyDst[k] = pabySrc[k];

                pabyDst += nPixelBytes * nPixelGroupBytes;
                pabySrc += nOMult * nPixelGroupBytes;
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Handle the case of averaging.  For this we also have to */
        /*      handle each sample format we are concerned with. */
        /* --------------------------------------------------------------------
         */
        else if (strncmp(pszResampling, "averag", 6) == 0 ||
                 strncmp(pszResampling, "AVERAG", 6) == 0)
        {
            pabySrc = pabySrcTile + j * nOMult * nBlockXSize * nPixelGroupBytes;

            for (i = 0; i * nOMult < nBlockXSize; i++)
            {
                double dfTotal;
                uint32_t nXSize, nYSize, iSample;

                if (i + nTXOff >= nOBlockXSize)
                    break;

                nXSize = MIN((uint32_t)nOMult, nBlockXSize - i);
                nYSize = MIN((uint32_t)nOMult, nBlockYSize - j);

                TIFF_GetSourceSamples(
                    padfSamples, pabySrc, nPixelBytes, nSampleFormat, nXSize,
                    nYSize, nPixelGroupBytes, nPixelGroupBytes * nBlockXSize);

                dfTotal = 0;
                for (iSample = 0; iSample < nXSize * nYSize; iSample++)
                {
                    dfTotal += padfSamples[iSample];
                }

                TIFF_SetSample(pabyDst, nPixelBytes, nSampleFormat,
                               dfTotal / (nXSize * nYSize));

                pabySrc += nOMult * nPixelGroupBytes;
                pabyDst += nPixelBytes;
            }
        }
    }

    free(padfSamples);
}

/************************************************************************/
/*                     TIFF_DownSample_Subsampled()                     */
/************************************************************************/
static void TIFF_DownSample_Subsampled(
    unsigned char *pabySrcTile, int nSample, uint32_t nBlockXSize,
    uint32_t nBlockYSize, unsigned char *pabyOTile, uint32_t nOBlockXSize,
    uint32_t nOBlockYSize, uint32_t nTXOff, uint32_t nTYOff, int nOMult,
    const char *pszResampling, int nHorSubsampling, int nVerSubsampling)
{
    /* TODO: test with variety of subsampling values, and incovinient tile/strip
     * sizes */
    int nSampleBlockSize;
    int nSourceSampleRowSize;
    int nDestSampleRowSize;
    uint32_t nSourceX, nSourceY;
    uint32_t nSourceXSec, nSourceYSec;
    uint32_t nSourceXSecEnd, nSourceYSecEnd;
    uint32_t nDestX, nDestY;
    int nSampleOffsetInSampleBlock;
    unsigned int nCummulator;
    unsigned int nCummulatorCount;

    nSampleBlockSize = nHorSubsampling * nVerSubsampling + 2;
    nSourceSampleRowSize =
        ((nBlockXSize + nHorSubsampling - 1) / nHorSubsampling) *
        nSampleBlockSize;
    nDestSampleRowSize =
        ((nOBlockXSize + nHorSubsampling - 1) / nHorSubsampling) *
        nSampleBlockSize;

    if (strncmp(pszResampling, "nearest", 4) == 0 ||
        strncmp(pszResampling, "NEAR", 4) == 0)
    {
        if (nSample == 0)
        {
            for (nSourceY = 0, nDestY = nTYOff; nSourceY < nBlockYSize;
                 nSourceY += nOMult, nDestY++)
            {
                if (nDestY >= nOBlockYSize)
                    break;

                for (nSourceX = 0, nDestX = nTXOff; nSourceX < nBlockXSize;
                     nSourceX += nOMult, nDestX++)
                {
                    if (nDestX >= nOBlockXSize)
                        break;

                    *(pabyOTile +
                      (nDestY / nVerSubsampling) * nDestSampleRowSize +
                      (nDestY % nVerSubsampling) * nHorSubsampling +
                      (nDestX / nHorSubsampling) * nSampleBlockSize +
                      (nDestX % nHorSubsampling)) =
                        *(pabySrcTile +
                          (nSourceY / nVerSubsampling) * nSourceSampleRowSize +
                          (nSourceY % nVerSubsampling) * nHorSubsampling +
                          (nSourceX / nHorSubsampling) * nSampleBlockSize +
                          (nSourceX % nHorSubsampling));
                }
            }
        }
        else
        {
            nSampleOffsetInSampleBlock =
                nHorSubsampling * nVerSubsampling + nSample - 1;
            for (nSourceY = 0, nDestY = (nTYOff / nVerSubsampling);
                 nSourceY < (nBlockYSize / nVerSubsampling);
                 nSourceY += nOMult, nDestY++)
            {
                if (nDestY * nVerSubsampling >= nOBlockYSize)
                    break;

                for (nSourceX = 0, nDestX = (nTXOff / nHorSubsampling);
                     nSourceX < (nBlockXSize / nHorSubsampling);
                     nSourceX += nOMult, nDestX++)
                {
                    if (nDestX * nHorSubsampling >= nOBlockXSize)
                        break;

                    *(pabyOTile + nDestY * nDestSampleRowSize +
                      nDestX * nSampleBlockSize + nSampleOffsetInSampleBlock) =
                        *(pabySrcTile + nSourceY * nSourceSampleRowSize +
                          nSourceX * nSampleBlockSize +
                          nSampleOffsetInSampleBlock);
                }
            }
        }
    }
    else if (strncmp(pszResampling, "averag", 6) == 0 ||
             strncmp(pszResampling, "AVERAG", 6) == 0)
    {
        if (nSample == 0)
        {
            for (nSourceY = 0, nDestY = nTYOff; nSourceY < nBlockYSize;
                 nSourceY += nOMult, nDestY++)
            {
                if (nDestY >= nOBlockYSize)
                    break;

                for (nSourceX = 0, nDestX = nTXOff; nSourceX < nBlockXSize;
                     nSourceX += nOMult, nDestX++)
                {
                    if (nDestX >= nOBlockXSize)
                        break;

                    nSourceXSecEnd = nSourceX + nOMult;
                    if (nSourceXSecEnd > nBlockXSize)
                        nSourceXSecEnd = nBlockXSize;
                    nSourceYSecEnd = nSourceY + nOMult;
                    if (nSourceYSecEnd > nBlockYSize)
                        nSourceYSecEnd = nBlockYSize;
                    nCummulator = 0;
                    for (nSourceYSec = nSourceY; nSourceYSec < nSourceYSecEnd;
                         nSourceYSec++)
                    {
                        for (nSourceXSec = nSourceX;
                             nSourceXSec < nSourceXSecEnd; nSourceXSec++)
                        {
                            nCummulator += *(pabySrcTile +
                                             (nSourceYSec / nVerSubsampling) *
                                                 nSourceSampleRowSize +
                                             (nSourceYSec % nVerSubsampling) *
                                                 nHorSubsampling +
                                             (nSourceXSec / nHorSubsampling) *
                                                 nSampleBlockSize +
                                             (nSourceXSec % nHorSubsampling));
                        }
                    }
                    nCummulatorCount = (nSourceXSecEnd - nSourceX) *
                                       (nSourceYSecEnd - nSourceY);
                    *(pabyOTile +
                      (nDestY / nVerSubsampling) * nDestSampleRowSize +
                      (nDestY % nVerSubsampling) * nHorSubsampling +
                      (nDestX / nHorSubsampling) * nSampleBlockSize +
                      (nDestX % nHorSubsampling)) =
                        ((nCummulator + (nCummulatorCount >> 1)) /
                         nCummulatorCount);
                }
            }
        }
        else
        {
            nSampleOffsetInSampleBlock =
                nHorSubsampling * nVerSubsampling + nSample - 1;
            for (nSourceY = 0, nDestY = (nTYOff / nVerSubsampling);
                 nSourceY < (nBlockYSize / nVerSubsampling);
                 nSourceY += nOMult, nDestY++)
            {
                if (nDestY * nVerSubsampling >= nOBlockYSize)
                    break;

                for (nSourceX = 0, nDestX = (nTXOff / nHorSubsampling);
                     nSourceX < (nBlockXSize / nHorSubsampling);
                     nSourceX += nOMult, nDestX++)
                {
                    if (nDestX * nHorSubsampling >= nOBlockXSize)
                        break;

                    nSourceXSecEnd = nSourceX + nOMult;
                    if (nSourceXSecEnd > (nBlockXSize / nHorSubsampling))
                        nSourceXSecEnd = (nBlockXSize / nHorSubsampling);
                    nSourceYSecEnd = nSourceY + nOMult;
                    if (nSourceYSecEnd > (nBlockYSize / nVerSubsampling))
                        nSourceYSecEnd = (nBlockYSize / nVerSubsampling);
                    nCummulator = 0;
                    for (nSourceYSec = nSourceY; nSourceYSec < nSourceYSecEnd;
                         nSourceYSec++)
                    {
                        for (nSourceXSec = nSourceX;
                             nSourceXSec < nSourceXSecEnd; nSourceXSec++)
                        {
                            nCummulator +=
                                *(pabySrcTile +
                                  nSourceYSec * nSourceSampleRowSize +
                                  nSourceXSec * nSampleBlockSize +
                                  nSampleOffsetInSampleBlock);
                        }
                    }
                    nCummulatorCount = (nSourceXSecEnd - nSourceX) *
                                       (nSourceYSecEnd - nSourceY);
                    *(pabyOTile + nDestY * nDestSampleRowSize +
                      nDestX * nSampleBlockSize + nSampleOffsetInSampleBlock) =
                        ((nCummulator + (nCummulatorCount >> 1)) /
                         nCummulatorCount);
                }
            }
        }
    }
}

/************************************************************************/
/*                      TIFF_ProcessFullResBlock()                      */
/*                                                                      */
/*      Process one block of full res data, downsampling into each      */
/*      of the overviews.                                               */
/************************************************************************/

void TIFF_ProcessFullResBlock(TIFF *hTIFF, int nPlanarConfig, int bSubsampled,
                              int nHorSubsampling, int nVerSubsampling,
                              int nOverviews, int *panOvList, int nBitsPerPixel,
                              int nSamples, TIFFOvrCache **papoRawBIs,
                              uint32_t nSXOff, uint32_t nSYOff,
                              unsigned char *pabySrcTile, uint32_t nBlockXSize,
                              uint32_t nBlockYSize, int nSampleFormat,
                              const char *pszResampling)

{
    int iOverview, iSample;

    for (iSample = 0; iSample < nSamples; iSample++)
    {
        /*
         * We have to read a tile/strip for each sample for
         * PLANARCONFIG_SEPARATE.  Otherwise, we just read all the samples
         * at once when handling the first sample.
         */
        if (nPlanarConfig == PLANARCONFIG_SEPARATE || iSample == 0)
        {
            if (TIFFIsTiled(hTIFF))
            {
                TIFFReadEncodedTile(hTIFF,
                                    TIFFComputeTile(hTIFF, nSXOff, nSYOff, 0,
                                                    (tsample_t)iSample),
                                    pabySrcTile, TIFFTileSize(hTIFF));
            }
            else
            {
                TIFFReadEncodedStrip(
                    hTIFF, TIFFComputeStrip(hTIFF, nSYOff, (tsample_t)iSample),
                    pabySrcTile, TIFFStripSize(hTIFF));
            }
        }

        /*
         * Loop over destination overview layers
         */
        for (iOverview = 0; iOverview < nOverviews; iOverview++)
        {
            TIFFOvrCache *poRBI = papoRawBIs[iOverview];
            unsigned char *pabyOTile;
            uint32_t nTXOff, nTYOff, nOXOff, nOYOff, nOMult;
            uint32_t nOBlockXSize = poRBI->nBlockXSize;
            uint32_t nOBlockYSize = poRBI->nBlockYSize;
            int nSkewBits, nSampleByteOffset;

            /*
             * Fetch the destination overview tile
             */
            nOMult = panOvList[iOverview];
            nOXOff = (nSXOff / nOMult) / nOBlockXSize;
            nOYOff = (nSYOff / nOMult) / nOBlockYSize;

            if (bSubsampled)
            {
                pabyOTile = TIFFGetOvrBlock_Subsampled(poRBI, nOXOff, nOYOff);

                /*
                 * Establish the offset into this tile at which we should
                 * start placing data.
                 */
                nTXOff = (nSXOff - nOXOff * nOMult * nOBlockXSize) / nOMult;
                nTYOff = (nSYOff - nOYOff * nOMult * nOBlockYSize) / nOMult;

#ifdef DBMALLOC
                malloc_chain_check(1);
#endif
                TIFF_DownSample_Subsampled(
                    pabySrcTile, iSample, nBlockXSize, nBlockYSize, pabyOTile,
                    poRBI->nBlockXSize, poRBI->nBlockYSize, nTXOff, nTYOff,
                    nOMult, pszResampling, nHorSubsampling, nVerSubsampling);
#ifdef DBMALLOC
                malloc_chain_check(1);
#endif
            }
            else
            {

                pabyOTile = TIFFGetOvrBlock(poRBI, nOXOff, nOYOff, iSample);

                /*
                 * Establish the offset into this tile at which we should
                 * start placing data.
                 */
                nTXOff = (nSXOff - nOXOff * nOMult * nOBlockXSize) / nOMult;
                nTYOff = (nSYOff - nOYOff * nOMult * nOBlockYSize) / nOMult;

                /*
                 * Figure out the skew (extra space between ``our samples'') and
                 * the byte offset to the first sample.
                 */
                assert((nBitsPerPixel % 8) == 0);
                if (nPlanarConfig == PLANARCONFIG_SEPARATE)
                {
                    nSkewBits = 0;
                    nSampleByteOffset = 0;
                }
                else
                {
                    nSkewBits = nBitsPerPixel * (nSamples - 1);
                    nSampleByteOffset = (nBitsPerPixel / 8) * iSample;
                }

                /*
                 * Perform the downsampling.
                 */
#ifdef DBMALLOC
                malloc_chain_check(1);
#endif
                TIFF_DownSample(pabySrcTile + nSampleByteOffset, nBlockXSize,
                                nBlockYSize, nSkewBits, nBitsPerPixel,
                                pabyOTile, poRBI->nBlockXSize,
                                poRBI->nBlockYSize, nTXOff, nTYOff, nOMult,
                                nSampleFormat, pszResampling);
#ifdef DBMALLOC
                malloc_chain_check(1);
#endif
            }
        }
    }
}

/************************************************************************/
/*                        TIFF_BuildOverviews()                         */
/*                                                                      */
/*      Build the requested list of overviews.  Overviews are           */
/*      maintained in a bunch of temporary files and then these are     */
/*      written back to the TIFF file.  Only one pass through the       */
/*      source TIFF file is made for any number of output               */
/*      overviews.                                                      */
/************************************************************************/

void TIFFBuildOverviews(TIFF *hTIFF, int nOverviews, int *panOvList,
                        int bUseSubIFDs, const char *pszResampleMethod,
                        int (*pfnProgress)(double, void *), void *pProgressData)

{
    TIFFOvrCache **papoRawBIs;
    uint32_t nXSize, nYSize, nBlockXSize, nBlockYSize;
    uint16_t nBitsPerPixel, nPhotometric, nCompressFlag, nSamples,
        nPlanarConfig, nSampleFormat;
    int bSubsampled;
    uint16_t nHorSubsampling, nVerSubsampling;
    int bTiled, nSXOff, nSYOff, i;
    unsigned char *pabySrcTile;
    uint16_t *panRedMap, *panGreenMap, *panBlueMap;
    TIFFErrorHandler pfnWarning;

    (void)pfnProgress;
    (void)pProgressData;

    /* -------------------------------------------------------------------- */
    /*      Get the base raster size.                                       */
    /* -------------------------------------------------------------------- */
    TIFFGetField(hTIFF, TIFFTAG_IMAGEWIDTH, &nXSize);
    TIFFGetField(hTIFF, TIFFTAG_IMAGELENGTH, &nYSize);

    TIFFGetField(hTIFF, TIFFTAG_BITSPERSAMPLE, &nBitsPerPixel);
    /* TODO: nBitsPerPixel seems misnomer and may need renaming to
     * nBitsPerSample */
    TIFFGetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, &nSamples);
    TIFFGetFieldDefaulted(hTIFF, TIFFTAG_PLANARCONFIG, &nPlanarConfig);

    TIFFGetFieldDefaulted(hTIFF, TIFFTAG_PHOTOMETRIC, &nPhotometric);
    TIFFGetFieldDefaulted(hTIFF, TIFFTAG_COMPRESSION, &nCompressFlag);
    TIFFGetFieldDefaulted(hTIFF, TIFFTAG_SAMPLEFORMAT, &nSampleFormat);

    if (nPhotometric == PHOTOMETRIC_YCBCR || nPhotometric == PHOTOMETRIC_ITULAB)
    {
        if (nBitsPerPixel != 8 || nSamples != 3 ||
            nPlanarConfig != PLANARCONFIG_CONTIG ||
            nSampleFormat != SAMPLEFORMAT_UINT)
        {
            /* TODO: use of TIFFError is inconsistent with use of fprintf in
             * addtiffo.c, sort out */
            TIFFErrorExt(
                TIFFClientdata(hTIFF), "TIFFBuildOverviews",
                "File `%s' has an unsupported subsampling configuration.\n",
                TIFFFileName(hTIFF));
            /* If you need support for this particular flavor, please contact
             * either Frank Warmerdam warmerdam@pobox.com Joris Van Damme
             * info@awaresystems.be
             */
            return;
        }
        bSubsampled = 1;
        TIFFGetField(hTIFF, TIFFTAG_YCBCRSUBSAMPLING, &nHorSubsampling,
                     &nVerSubsampling);
        /* TODO: find out if maybe TIFFGetFieldDefaulted is better choice for
         * YCbCrSubsampling tag */
    }
    else
    {
        if (nBitsPerPixel < 8)
        {
            /* TODO: use of TIFFError is inconsistent with use of fprintf in
             * addtiffo.c, sort out */
            TIFFErrorExt(
                TIFFClientdata(hTIFF), "TIFFBuildOverviews",
                "File `%s' has samples of %d bits per sample.  Sample\n"
                "sizes of less than 8 bits per sample are not supported.\n",
                TIFFFileName(hTIFF), nBitsPerPixel);
            return;
        }
        bSubsampled = 0;
        nHorSubsampling = 1;
        nVerSubsampling = 1;
    }

    /* -------------------------------------------------------------------- */
    /*      Turn off warnings to avoid a lot of repeated warnings while      */
    /*      rereading directories.                                          */
    /* -------------------------------------------------------------------- */
    pfnWarning = TIFFSetWarningHandler(NULL);

    /* -------------------------------------------------------------------- */
    /*      Get the base raster block size.                                 */
    /* -------------------------------------------------------------------- */
    if (TIFFGetField(hTIFF, TIFFTAG_ROWSPERSTRIP, &(nBlockYSize)))
    {
        nBlockXSize = nXSize;
        bTiled = FALSE;
    }
    else
    {
        TIFFGetField(hTIFF, TIFFTAG_TILEWIDTH, &nBlockXSize);
        TIFFGetField(hTIFF, TIFFTAG_TILELENGTH, &nBlockYSize);
        bTiled = TRUE;
    }

    /* -------------------------------------------------------------------- */
    /*	Capture the palette if there is one.				*/
    /* -------------------------------------------------------------------- */
    if (TIFFGetField(hTIFF, TIFFTAG_COLORMAP, &panRedMap, &panGreenMap,
                     &panBlueMap))
    {
        uint16_t *panRed2, *panGreen2, *panBlue2;
        int nColorCount = 1 << nBitsPerPixel;

        panRed2 = (uint16_t *)_TIFFmalloc(2 * nColorCount);
        panGreen2 = (uint16_t *)_TIFFmalloc(2 * nColorCount);
        panBlue2 = (uint16_t *)_TIFFmalloc(2 * nColorCount);

        memcpy(panRed2, panRedMap, 2 * nColorCount);
        memcpy(panGreen2, panGreenMap, 2 * nColorCount);
        memcpy(panBlue2, panBlueMap, 2 * nColorCount);

        panRedMap = panRed2;
        panGreenMap = panGreen2;
        panBlueMap = panBlue2;
    }
    else
    {
        panRedMap = panGreenMap = panBlueMap = NULL;
    }

    /* -------------------------------------------------------------------- */
    /*      Initialize overviews.                                           */
    /* -------------------------------------------------------------------- */
    papoRawBIs = (TIFFOvrCache **)_TIFFmalloc(nOverviews * sizeof(void *));

    for (i = 0; i < nOverviews; i++)
    {
        uint32_t nOXSize, nOYSize, nOBlockXSize, nOBlockYSize;
        toff_t nDirOffset;

        nOXSize = (nXSize + panOvList[i] - 1) / panOvList[i];
        nOYSize = (nYSize + panOvList[i] - 1) / panOvList[i];

        nOBlockXSize = MIN(nBlockXSize, nOXSize);
        nOBlockYSize = MIN(nBlockYSize, nOYSize);

        if (bTiled)
        {
            if ((nOBlockXSize % 16) != 0)
                nOBlockXSize = nOBlockXSize + 16 - (nOBlockXSize % 16);

            if ((nOBlockYSize % 16) != 0)
                nOBlockYSize = nOBlockYSize + 16 - (nOBlockYSize % 16);
        }

        nDirOffset = TIFF_WriteOverview(
            hTIFF, nOXSize, nOYSize, nBitsPerPixel, nPlanarConfig, nSamples,
            nOBlockXSize, nOBlockYSize, bTiled, nCompressFlag, nPhotometric,
            nSampleFormat, panRedMap, panGreenMap, panBlueMap, bUseSubIFDs,
            nHorSubsampling, nVerSubsampling);

        papoRawBIs[i] = TIFFCreateOvrCache(hTIFF, nDirOffset);
    }

    if (panRedMap != NULL)
    {
        _TIFFfree(panRedMap);
        _TIFFfree(panGreenMap);
        _TIFFfree(panBlueMap);
    }

    /* -------------------------------------------------------------------- */
    /*      Allocate a buffer to hold a source block.                       */
    /* -------------------------------------------------------------------- */
    if (bTiled)
        pabySrcTile = (unsigned char *)_TIFFmalloc(TIFFTileSize(hTIFF));
    else
        pabySrcTile = (unsigned char *)_TIFFmalloc(TIFFStripSize(hTIFF));

    /* -------------------------------------------------------------------- */
    /*      Loop over the source raster, applying data to the               */
    /*      destination raster.                                             */
    /* -------------------------------------------------------------------- */
    for (nSYOff = 0; nSYOff < (int)nYSize; nSYOff += nBlockYSize)
    {
        for (nSXOff = 0; nSXOff < (int)nXSize; nSXOff += nBlockXSize)
        {
            /*
             * Read and resample into the various overview images.
             */

            TIFF_ProcessFullResBlock(
                hTIFF, nPlanarConfig, bSubsampled, nHorSubsampling,
                nVerSubsampling, nOverviews, panOvList, nBitsPerPixel, nSamples,
                papoRawBIs, nSXOff, nSYOff, pabySrcTile, nBlockXSize,
                nBlockYSize, nSampleFormat, pszResampleMethod);
        }
    }

    _TIFFfree(pabySrcTile);

    /* -------------------------------------------------------------------- */
    /*      Cleanup the rawblockedimage files.                              */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < nOverviews; i++)
    {
        TIFFDestroyOvrCache(papoRawBIs[i]);
    }

    if (papoRawBIs != NULL)
        _TIFFfree(papoRawBIs);

    TIFFSetWarningHandler(pfnWarning);
}
