/*
 * Copyright (c) 1988-1997 Sam Leffler
 * Copyright (c) 1991-1997 Silicon Graphics, Inc.
 *
 *  Revised:  2/18/01 BAR -- added syntax for extracting single images from
 *                          multi-image TIFF files.
 *
 *    New syntax is:  sourceFileName,image#
 *
 * image# ranges from 0..<n-1> where n is the # of images in the file.
 * There may be no white space between the comma and the filename or
 * image number.
 *
 *    Example:   tiffcp source.tif,1 destination.tif
 *
 * Copies the 2nd image in source.tif to the destination.
 *
 *****
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

#include "libport.h"
#include "tif_config.h"

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

#include <ctype.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "tiff_tools.h"
#include "tiffio.h"
#include "tiffiop.h"

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

#define streq(a, b) (strcmp(a, b) == 0)
#define strneq(a, b, n) (strncmp(a, b, n) == 0)

#define TRUE 1
#define FALSE 0

#define DEFAULT_MAX_MALLOC (256 * 1024 * 1024)

/* malloc size limit (in bytes)
 * disabled when set to 0 */
static tmsize_t maxMalloc = DEFAULT_MAX_MALLOC;

static int outtiled = -1;
static uint32_t tilewidth;
static uint32_t tilelength;

static uint16_t config;
static uint16_t compression;
static double max_z_error = 0.0;
static uint16_t predictor;
static int preset;
static uint16_t fillorder;
static uint16_t orientation;
static uint32_t rowsperstrip;
static uint32_t g3opts;
static int ignore = FALSE; /* if true, ignore read errors */
static uint32_t defg3opts = (uint32_t)-1;
static int quality = 75; /* JPEG quality */
static int jpeg_photometric = PHOTOMETRIC_YCBCR;
static uint16_t defcompression = (uint16_t)-1;
static uint16_t defpredictor = (uint16_t)-1;
static int defpreset = -1;
static int subcodec = -1;

static int tiffcp(TIFF *, TIFF *);
static int processCompressOptions(char *);
static void usage(int code);

static char comma = ','; /* (default) comma separator character */
static TIFF *bias = NULL;
static int pageNum = 0;
static int pageInSeq = 0;

/**
 * This custom malloc function enforce a maximum allocation size
 */
static void *limitMalloc(tmsize_t s)
{
    if (s < 0 || (maxMalloc && (s > maxMalloc)))
    {
        fprintf(stderr,
                "MemoryLimitError: allocation of %" TIFF_SSIZE_FORMAT
                " bytes is forbidden. Limit is %" TIFF_SSIZE_FORMAT ".\n",
                s, maxMalloc);
        fprintf(stderr, "                  use -m option to change limit.\n");
        return NULL;
    }
    return _TIFFmalloc(s);
}

static int nextSrcImage(TIFF *tif, char **imageSpec)
/*
  seek to the next image specified in *imageSpec
  returns 1 if success, 0 if no more images to process
  *imageSpec=NULL if subsequent images should be processed in sequence
*/
{
    if (**imageSpec == comma)
    { /* if not @comma, we've done all images */
        char *start = *imageSpec + 1;
        tdir_t nextImage = (tdir_t)strtol(start, imageSpec, 0);
        if (start == *imageSpec)
            nextImage = TIFFCurrentDirectory(tif);
        if (**imageSpec)
        {
            if (**imageSpec == comma)
            {
                /* a trailing comma denotes remaining images in sequence */
                if ((*imageSpec)[1] == '\0')
                    *imageSpec = NULL;
            }
            else
            {
                fprintf(stderr,
                        "Expected a %c separated image # list after %s\n",
                        comma, TIFFFileName(tif));
                exit(EXIT_FAILURE); /* syntax error */
            }
        }
        if (TIFFSetDirectory(tif, nextImage))
            return 1;
        fprintf(stderr, "%s%c%" PRIu32 " not found!\n", TIFFFileName(tif),
                comma, nextImage);
    }
    return 0;
}

static TIFF *openSrcImage(char **imageSpec)
/*
  imageSpec points to a pointer to a filename followed by optional ,image#'s
  Open the TIFF file and assign *imageSpec to either NULL if there are
  no images specified, or a pointer to the next image number text
*/
{
    /* disable strip shopping when using jbig compression */
    const char *mode = (defcompression == COMPRESSION_JBIG) ? "rc" : "r";
    TIFF *tif;
    char *fn = *imageSpec;
    *imageSpec = strchr(fn, comma);
    TIFFOpenOptions *opts = TIFFOpenOptionsAlloc();
    if (opts == NULL)
    {
        return NULL;
    }
    TIFFOpenOptionsSetMaxSingleMemAlloc(opts, maxMalloc);
    if (*imageSpec)
    { /* there is at least one image number specifier */
        **imageSpec = '\0';
        tif = TIFFOpenExt(fn, mode, opts);
        /* but, ignore any single trailing comma */
        if (!(*imageSpec)[1])
        {
            *imageSpec = NULL;
            TIFFOpenOptionsFree(opts);
            return tif;
        }
        if (tif)
        {
            **imageSpec = comma; /* replace the comma */
            if (!nextSrcImage(tif, imageSpec))
            {
                TIFFClose(tif);
                tif = NULL;
            }
        }
    }
    else
        tif = TIFFOpenExt(fn, mode, opts);
    TIFFOpenOptionsFree(opts);
    return tif;
}

int main(int argc, char *argv[])
{
    uint16_t defconfig = (uint16_t)-1;
    uint16_t deffillorder = 0;
    uint32_t deftilewidth = (uint32_t)-1;
    uint32_t deftilelength = (uint32_t)-1;
    uint32_t defrowsperstrip = (uint32_t)0;
    uint64_t diroff = 0;
    TIFF *in;
    TIFF *out;
    char mode[10];
    char *mp = mode;
    int c;
    long v;
#if !HAVE_DECL_OPTARG
    extern int optind;
    extern char *optarg;
#endif

    *mp++ = 'w';
    *mp = '\0';
    while ((c = getopt(argc, argv, "m:,:b:c:f:l:o:p:r:w:aistBLMC8xh")) != -1)
        switch (c)
        {
            case 'm':
                if (!TIFFToolsParseMemoryLimitMiB(optarg, &maxMalloc))
                    usage(EXIT_FAILURE);
                break;
            case ',':
                if (optarg[0] != '=')
                    usage(EXIT_FAILURE);
                comma = optarg[1];
                break;
            case 'b': /* this file is bias image subtracted from others */
                if (bias)
                {
                    fputs("Only 1 bias image may be specified\n", stderr);
                    exit(EXIT_FAILURE);
                }
                {
                    uint16_t samples = (uint16_t)-1;
                    char **biasFn = &optarg;
                    bias = openSrcImage(biasFn);
                    if (!bias)
                        exit(EXIT_FAILURE);
                    if (TIFFIsTiled(bias))
                    {
                        fputs("Bias image must be organized in strips\n",
                              stderr);
                        exit(EXIT_FAILURE);
                    }
                    TIFFGetField(bias, TIFFTAG_SAMPLESPERPIXEL, &samples);
                    if (samples != 1)
                    {
                        fputs("Bias image must be monochrome\n", stderr);
                        exit(EXIT_FAILURE);
                    }
                }
                break;
            case 'a': /* append to output */
                mode[0] = 'a';
                break;
            case 'c': /* compression scheme */
                if (!processCompressOptions(optarg))
                    usage(EXIT_FAILURE);
                break;
            case 'f': /* fill order */
                if (streq(optarg, "lsb2msb"))
                    deffillorder = FILLORDER_LSB2MSB;
                else if (streq(optarg, "msb2lsb"))
                    deffillorder = FILLORDER_MSB2LSB;
                else
                    usage(EXIT_FAILURE);
                break;
            case 'i': /* ignore errors */
                ignore = TRUE;
                break;
            case 'l': /* tile length */
                outtiled = TRUE;
                deftilelength = (uint32_t)atoi(optarg);
                break;
            case 'o': /* initial directory offset */
                v = strtol(optarg, NULL, 0);
                if (v < 0)
                    usage(EXIT_FAILURE);
                diroff = (uint64_t)v;
                break;
            case 'p': /* planar configuration */
                if (streq(optarg, "separate"))
                    defconfig = PLANARCONFIG_SEPARATE;
                else if (streq(optarg, "contig"))
                    defconfig = PLANARCONFIG_CONTIG;
                else
                    usage(EXIT_FAILURE);
                break;
            case 'r': /* rows/strip */
                defrowsperstrip = (uint32_t)atol(optarg);
                break;
            case 's': /* generate stripped output */
                outtiled = FALSE;
                break;
            case 't': /* generate tiled output */
                outtiled = TRUE;
                break;
            case 'w': /* tile width */
                outtiled = TRUE;
                deftilewidth = (uint32_t)atoi(optarg);
                break;
            case 'B':
                if (strlen(mode) < (sizeof(mode) - 1))
                {
                    *mp++ = 'b';
                    *mp = '\0';
                }
                break;
            case 'L':
                if (strlen(mode) < (sizeof(mode) - 1))
                {
                    *mp++ = 'l';
                    *mp = '\0';
                }
                break;
            case 'M':
                if (strlen(mode) < (sizeof(mode) - 1))
                {
                    *mp++ = 'm';
                    *mp = '\0';
                }
                break;
            case 'C':
                if (strlen(mode) < (sizeof(mode) - 1))
                {
                    *mp++ = 'c';
                    *mp = '\0';
                }
                break;
            case '8':
                if (strlen(mode) < (sizeof(mode) - 1))
                {
                    *mp++ = '8';
                    *mp = '\0';
                }
                break;
            case 'x':
                pageInSeq = 1;
                break;
            case 'h':
                usage(EXIT_SUCCESS);
                /*NOTREACHED*/
                break;
            case '?':
                usage(EXIT_FAILURE);
                /*NOTREACHED*/
                break;
            default:
                break;
        }
    if (argc - optind < 2)
        usage(EXIT_FAILURE);
    TIFFOpenOptions *opts = TIFFOpenOptionsAlloc();
    if (opts == NULL)
    {
        return EXIT_FAILURE;
    }
    TIFFOpenOptionsSetMaxSingleMemAlloc(opts, maxMalloc);
    out = TIFFOpenExt(argv[argc - 1], mode, opts);
    TIFFOpenOptionsFree(opts);
    if (out == NULL)
        return (EXIT_FAILURE);
    if ((argc - optind) == 2)
        pageNum = -1;
    for (; optind < argc - 1; optind++)
    {
        char *imageCursor = argv[optind];
        in = openSrcImage(&imageCursor);
        if (in == NULL)
        {
            TIFFClose(out);
            return (EXIT_FAILURE);
        }
        if (diroff != 0 && !TIFFSetSubDirectory(in, diroff))
        {
            TIFFError(TIFFFileName(in),
                      "Error, setting subdirectory at %" PRIu64, diroff);
            TIFFClose(in);
            TIFFClose(out);
            return (EXIT_FAILURE);
        }
        for (;;)
        {
            config = defconfig;
            compression = defcompression;
            predictor = defpredictor;
            preset = defpreset;
            fillorder = deffillorder;
            rowsperstrip = defrowsperstrip;
            tilewidth = deftilewidth;
            tilelength = deftilelength;
            g3opts = defg3opts;
            if (!tiffcp(in, out) || !TIFFWriteDirectory(out))
            {
                TIFFClose(in);
                TIFFClose(out);
                return (EXIT_FAILURE);
            }
            if (imageCursor)
            { /* seek next image directory */
                if (!nextSrcImage(in, &imageCursor))
                    break;
            }
            else if (!TIFFReadDirectory(in))
                break;
        }
        TIFFClose(in);
    }

    TIFFClose(out);
    return (EXIT_SUCCESS);
}

static void processZIPOptions(char *cp)
{
    if ((cp = strchr(cp, ':')))
    {
        do
        {
            cp++;
            if (isdigit((int)*cp))
                defpredictor = (uint16_t)atoi(cp);
            else if (*cp == 'p')
                defpreset = atoi(++cp);
            else if (*cp == 's')
                subcodec = atoi(++cp);
            else
                usage(EXIT_FAILURE);
        } while ((cp = strchr(cp, ':')));
    }
}

static void processLERCOptions(char *cp)
{
    if ((cp = strchr(cp, ':')))
    {
        do
        {
            cp++;
            if (isdigit((int)*cp))
                max_z_error = atof(cp);
            else if (*cp == 's')
                subcodec = atoi(++cp);
            else if (*cp == 'p')
                defpreset = atoi(++cp);
            else
                usage(EXIT_FAILURE);
        } while ((cp = strchr(cp, ':')));
    }
}

static void processG3Options(char *cp)
{
    if ((cp = strchr(cp, ':')))
    {
        if (defg3opts == (uint32_t)-1)
            defg3opts = 0;
        do
        {
            cp++;
            if (strneq(cp, "1d", 2))
                defg3opts &= ~(uint32_t)GROUP3OPT_2DENCODING;
            else if (strneq(cp, "2d", 2))
                defg3opts |= GROUP3OPT_2DENCODING;
            else if (strneq(cp, "fill", 4))
                defg3opts |= GROUP3OPT_FILLBITS;
            else
                usage(EXIT_FAILURE);
        } while ((cp = strchr(cp, ':')));
    }
}

static int processCompressOptions(char *opt)
{
    if (streq(opt, "none"))
    {
        defcompression = COMPRESSION_NONE;
    }
    else if (streq(opt, "packbits"))
    {
        defcompression = COMPRESSION_PACKBITS;
    }
    else if (strneq(opt, "jpeg", 4))
    {
        char *cp = strchr(opt, ':');

        defcompression = COMPRESSION_JPEG;
        while (cp)
        {
            if (isdigit((int)cp[1]))
                quality = atoi(cp + 1);
            else if (cp[1] == 'r')
                jpeg_photometric = PHOTOMETRIC_RGB;
            else
                usage(EXIT_FAILURE);

            cp = strchr(cp + 1, ':');
        }
    }
    else if (strneq(opt, "g3", 2))
    {
        processG3Options(opt);
        defcompression = COMPRESSION_CCITTFAX3;
    }
    else if (streq(opt, "g4"))
    {
        defcompression = COMPRESSION_CCITTFAX4;
    }
    else if (strneq(opt, "lzw", 3))
    {
        char *cp = strchr(opt, ':');
        if (cp)
            defpredictor = (uint16_t)atoi(cp + 1);
        defcompression = COMPRESSION_LZW;
    }
    else if (strneq(opt, "zip", 3))
    {
        processZIPOptions(opt);
        defcompression = COMPRESSION_ADOBE_DEFLATE;
    }
    else if (strneq(opt, "lerc", 4))
    {
        processLERCOptions(opt);
        defcompression = COMPRESSION_LERC;
    }
    else if (strneq(opt, "lzma", 4))
    {
        processZIPOptions(opt);
        defcompression = COMPRESSION_LZMA;
    }
    else if (strneq(opt, "zstd", 4))
    {
        processZIPOptions(opt);
        defcompression = COMPRESSION_ZSTD;
    }
    else if (strneq(opt, "webp", 4))
    {
        processZIPOptions(opt);
        defcompression = COMPRESSION_WEBP;
    }
    else if (strneq(opt, "jbig", 4))
    {
        defcompression = COMPRESSION_JBIG;
    }
    else if (strneq(opt, "sgilog", 6))
    {
        defcompression = COMPRESSION_SGILOG;
    }
    else
        return (0);
    return (1);
}

static const char usage_info[] =
    "Copy, convert, or combine TIFF files\n\n"
    "usage: tiffcp [options] input... output\n"
    "where options are:\n"
    " -a              append to output instead of overwriting\n"
    " -o offset       set initial directory offset\n"
    " -p contig       pack samples contiguously (e.g. RGBRGB...)\n"
    " -p separate     store samples separately (e.g. RRR...GGG...BBB...)\n"
    " -s              write output in strips\n"
    " -t              write output in tiles\n"
    " -x              force the merged tiff pages in sequence\n"
    " -8              write BigTIFF instead of default ClassicTIFF\n"
    " -B              write big-endian instead of native byte order\n"
    " -L              write little-endian instead of native byte order\n"
    " -M              disable use of memory-mapped files\n"
    " -C              disable strip chopping\n"
    " -i              ignore read errors\n"
    " -b file[,#]     bias (dark) monochrome image to be subtracted from all "
    "others\n"
    " -,=%            use % rather than , to separate image #'s (per Note "
    "below)\n"
    " -m size         set maximum memory allocation size (MiB). 0 to disable "
    "limit.\n"
    "\n"
    " -r #            make each strip have no more than # rows\n"
    " -w #            set output tile width (pixels)\n"
    " -l #            set output tile length (pixels)\n"
    "\n"
    " -f lsb2msb      force lsb-to-msb FillOrder for output\n"
    " -f msb2lsb      force msb-to-lsb FillOrder for output\n"
    "\n"
#ifdef LZW_SUPPORT
    " -c lzw[:opts]   compress output with Lempel-Ziv & Welch encoding\n"
    /* "    LZW options:" */
    "    #            set predictor value\n"
    "    For example, -c lzw:2 for LZW-encoded data with horizontal "
    "differencing\n"
#endif
#ifdef ZIP_SUPPORT
    " -c zip[:opts]   compress output with deflate encoding\n"
    /* "    Deflate (ZIP) options:", */
    "    #            set predictor value\n"
    "    p#           set compression level (preset)\n"
    "    For example, -c zip:3:p9 for maximum compression level and floating\n"
    "                 point predictor.\n"
#endif
#if defined(ZIP_SUPPORT) && defined(LIBDEFLATE_SUPPORT)
    "    s#           set subcodec: 0=zlib, 1=libdeflate (default 1)\n"
/* "                 (only for Deflate/ZIP)", */
#endif
#ifdef LERC_SUPPORT
    " -c lerc[:opts]  compress output with LERC encoding\n"
    /* "    LERC options:", */
    "    #            set max_z_error value\n"
    "    p#           set compression level (preset)\n"
#ifdef ZSTD_SUPPORT
    "    s#           set subcodec: 0=none, 1=deflate, 2=zstd (default 0)\n"
    "    For example, -c lerc:0.5:s2:p22 for max_z_error 0.5,\n"
    "    zstd additional compression with maximum compression level.\n"
#else
    "    s#           set subcodec: 0=none, 1=deflate (default 0)\n"
    "    For example, -c lerc:0.5:s1:p12 for max_z_error 0.5,\n"
    "    deflate additional compression with maximum compression level.\n"
#endif
#endif
#ifdef LZMA_SUPPORT
    " -c lzma[:opts]  compress output with LZMA2 encoding\n"
    /* "    LZMA options:", */
    "    #            set predictor value\n"
    "    p#           set compression level (preset)\n"
#endif
#ifdef ZSTD_SUPPORT
    " -c zstd[:opts]  compress output with ZSTD encoding\n"
    /* "    ZSTD options:", */
    "    #            set predictor value\n"
    "    p#           set compression level (preset)\n"
#endif
#ifdef WEBP_SUPPORT
    " -c webp[:opts]  compress output with WEBP encoding\n"
    /* "    WEBP options:", */
    "    #            set predictor value\n"
    "    p#           set compression level (preset)\n"
#endif
#ifdef JPEG_SUPPORT
    " -c jpeg[:opts]  compress output with JPEG encoding\n"
    /* "    JPEG options:", */
    "    #            set compression quality level (0-100, default 75)\n"
    "    r            output color image as RGB rather than YCbCr\n"
    "    For example, -c jpeg:r:50 for JPEG-encoded RGB with 50% comp. "
    "quality\n"
#endif
#ifdef JBIG_SUPPORT
    " -c jbig         compress output with ISO JBIG encoding\n"
#endif
#ifdef PACKBITS_SUPPORT
    " -c packbits     compress output with packbits encoding\n"
#endif
#ifdef CCITT_SUPPORT
    " -c g3[:opts]    compress output with CCITT Group 3 encoding\n"
    /* "    CCITT Group 3 options:", */
    "    1d           use default CCITT Group 3 1D-encoding\n"
    "    2d           use optional CCITT Group 3 2D-encoding\n"
    "    fill         byte-align EOL codes\n"
    "    For example, -c g3:2d:fill for G3-2D-encoded data with byte-aligned "
    "EOLs\n"
    " -c g4           compress output with CCITT Group 4 encoding\n"
#endif
#ifdef LOGLUV_SUPPORT
    " -c sgilog       compress output with SGILOG encoding\n"
#endif
#if defined(LZW_SUPPORT) || defined(ZIP_SUPPORT) || defined(LZMA_SUPPORT) ||   \
    defined(ZSTD_SUPPORT) || defined(WEBP_SUPPORT) || defined(JPEG_SUPPORT) || \
    defined(JBIG_SUPPORT) || defined(PACKBITS_SUPPORT) ||                      \
    defined(CCITT_SUPPORT) || defined(LOGLUV_SUPPORT) || defined(LERC_SUPPORT)
    " -c none         use no compression algorithm on output\n"
#endif
    "\n"
    "Note that input filenames may be of the form filename,x,y,z\n"
    "where x, y, and z specify image numbers in the filename to copy.\n"
    "example: tiffcp -b esp.tif,1 esp.tif,0 test.tif\n"
    "    subtract 2nd image in esp.tif from 1st yielding result test.tif\n";

static void usage(int code)
{
    FILE *out = (code == EXIT_SUCCESS) ? stdout : stderr;

    fprintf(out, "%s\n\n", TIFFGetVersion());
    fprintf(out, "%s", usage_info);
    exit(code);
}

#define CopyField(tag, v)                                                      \
    do                                                                         \
    {                                                                          \
        if (TIFFGetField(in, tag, &v))                                         \
            TIFFSetField(out, tag, v);                                         \
    } while (0)
#define CopyFieldFloat(tag, v)                                                 \
    do                                                                         \
    {                                                                          \
        if (TIFFGetField(in, tag, &v))                                         \
            TIFFSetField(out, tag, (double)(v));                               \
    } while (0)
#define CopyField2(tag, v1, v2)                                                \
    do                                                                         \
    {                                                                          \
        if (TIFFGetField(in, tag, &v1, &v2))                                   \
            TIFFSetField(out, tag, v1, v2);                                    \
    } while (0)
#define CopyField4(tag, v1, v2, v3, v4)                                        \
    do                                                                         \
    {                                                                          \
        if (TIFFGetField(in, tag, &v1, &v2, &v3, &v4))                         \
            TIFFSetField(out, tag, v1, v2, v3, v4);                            \
    } while (0)

static void cpTag(TIFF *in, TIFF *out, uint16_t tag, uint16_t count,
                  TIFFDataType type)
{
    switch (type)
    {
        case TIFF_SHORT:
            if (count == 1)
            {
                uint16_t shortv;
                CopyField(tag, shortv);
            }
            else if (count == 2)
            {
                uint16_t shortv1, shortv2;
                CopyField2(tag, shortv1, shortv2);
            }
            else if (count == 4)
            {
                uint16_t *tr, *tg, *tb, *ta;
                CopyField4(tag, tr, tg, tb, ta);
            }
            else if (count == (uint16_t)-1)
            {
                uint16_t shortv1;
                uint16_t *shortav;
                CopyField2(tag, shortv1, shortav);
            }
            break;
        case TIFF_LONG:
        {
            uint32_t longv;
            CopyField(tag, longv);
        }
        break;
        case TIFF_RATIONAL:
            if (count == 1)
            {
                float floatv;
                CopyFieldFloat(tag, floatv);
            }
            else if (count == (uint16_t)-1)
            {
                float *floatav;
                CopyField(tag, floatav);
            }
            break;
        case TIFF_ASCII:
        {
            char *stringv;
            CopyField(tag, stringv);
        }
        break;
        case TIFF_DOUBLE:
            if (count == 1)
            {
                double doublev;
                CopyField(tag, doublev);
            }
            else if (count == (uint16_t)-1)
            {
                double *doubleav;
                CopyField(tag, doubleav);
            }
            break;
        case TIFF_NOTYPE:
        case TIFF_BYTE:
        case TIFF_SBYTE:
        case TIFF_UNDEFINED:
        case TIFF_SSHORT:
        case TIFF_SLONG:
        case TIFF_SRATIONAL:
        case TIFF_FLOAT:
        case TIFF_IFD:
        case TIFF_LONG8:
        case TIFF_SLONG8:
        case TIFF_IFD8:
        default:
            TIFFError(TIFFFileName(in),
                      "Data type %u is not supported, tag %u skipped.",
                      (unsigned)type, (unsigned)tag);
    }
}

static const struct cpTag
{
    uint16_t tag;
    uint16_t count;
    TIFFDataType type;
} tags[] = {
    {TIFFTAG_SUBFILETYPE, 1, TIFF_LONG},
    {TIFFTAG_THRESHHOLDING, 1, TIFF_SHORT},
    {TIFFTAG_DOCUMENTNAME, 1, TIFF_ASCII},
    {TIFFTAG_IMAGEDESCRIPTION, 1, TIFF_ASCII},
    {TIFFTAG_MAKE, 1, TIFF_ASCII},
    {TIFFTAG_MODEL, 1, TIFF_ASCII},
    {TIFFTAG_MINSAMPLEVALUE, 1, TIFF_SHORT},
    {TIFFTAG_MAXSAMPLEVALUE, 1, TIFF_SHORT},
    {TIFFTAG_XRESOLUTION, 1, TIFF_RATIONAL},
    {TIFFTAG_YRESOLUTION, 1, TIFF_RATIONAL},
    {TIFFTAG_PAGENAME, 1, TIFF_ASCII},
    {TIFFTAG_XPOSITION, 1, TIFF_RATIONAL},
    {TIFFTAG_YPOSITION, 1, TIFF_RATIONAL},
    {TIFFTAG_RESOLUTIONUNIT, 1, TIFF_SHORT},
    {TIFFTAG_SOFTWARE, 1, TIFF_ASCII},
    {TIFFTAG_DATETIME, 1, TIFF_ASCII},
    {TIFFTAG_ARTIST, 1, TIFF_ASCII},
    {TIFFTAG_HOSTCOMPUTER, 1, TIFF_ASCII},
    {TIFFTAG_WHITEPOINT, (uint16_t)-1, TIFF_RATIONAL},
    {TIFFTAG_PRIMARYCHROMATICITIES, (uint16_t)-1, TIFF_RATIONAL},
    {TIFFTAG_HALFTONEHINTS, 2, TIFF_SHORT},
    {TIFFTAG_INKSET, 1, TIFF_SHORT},
    {TIFFTAG_DOTRANGE, 2, TIFF_SHORT},
    {TIFFTAG_TARGETPRINTER, 1, TIFF_ASCII},
    {TIFFTAG_SAMPLEFORMAT, 1, TIFF_SHORT},
    {TIFFTAG_EXTRASAMPLES, (uint16_t)-1, TIFF_SHORT},
    {TIFFTAG_SMINSAMPLEVALUE, 1, TIFF_DOUBLE},
    {TIFFTAG_SMAXSAMPLEVALUE, 1, TIFF_DOUBLE},
    {TIFFTAG_STONITS, 1, TIFF_DOUBLE},
};
#define NTAGS (sizeof(tags) / sizeof(tags[0]))

#define CopyTag(tag, count, type) cpTag(in, out, tag, count, type)

typedef int (*copyFunc)(TIFF *in, TIFF *out, uint32_t l, uint32_t w,
                        uint16_t samplesperpixel);
static copyFunc pickCopyFunc(TIFF *, TIFF *, uint16_t, uint16_t);

/* PODD */

static int tiffcp(TIFF *in, TIFF *out)
{
    uint16_t bitspersample = 1, samplesperpixel = 1;
    uint16_t input_compression, input_photometric = PHOTOMETRIC_MINISBLACK;
    copyFunc cf;
    uint32_t width, length;
    const struct cpTag *p;

    CopyField(TIFFTAG_IMAGEWIDTH, width);
    CopyField(TIFFTAG_IMAGELENGTH, length);
    CopyField(TIFFTAG_BITSPERSAMPLE, bitspersample);
    CopyField(TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
    if (compression != (uint16_t)-1)
        TIFFSetField(out, TIFFTAG_COMPRESSION, compression);
    else
        CopyField(TIFFTAG_COMPRESSION, compression);
    if (!TIFFIsCODECConfigured(compression))
        return FALSE;
    TIFFGetFieldDefaulted(in, TIFFTAG_COMPRESSION, &input_compression);
    if (!TIFFIsCODECConfigured(input_compression))
        return FALSE;
    TIFFGetFieldDefaulted(in, TIFFTAG_PHOTOMETRIC, &input_photometric);
    if (input_compression == COMPRESSION_JPEG)
    {
        /* Force conversion to RGB */
        TIFFSetField(in, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
    }
    else if (input_photometric == PHOTOMETRIC_YCBCR)
    {
        /* Otherwise, can't handle subsampled input */
        uint16_t subsamplinghor, subsamplingver;

        TIFFGetFieldDefaulted(in, TIFFTAG_YCBCRSUBSAMPLING, &subsamplinghor,
                              &subsamplingver);
        if (subsamplinghor != 1 || subsamplingver != 1)
        {
            fprintf(stderr,
                    "tiffcp: %s: Can't copy/convert subsampled image.\n",
                    TIFFFileName(in));
            return FALSE;
        }
    }
    if (compression == COMPRESSION_JPEG)
    {
        if (input_photometric == PHOTOMETRIC_RGB ||
            input_photometric == PHOTOMETRIC_YCBCR)
        {
            TIFFSetField(out, TIFFTAG_PHOTOMETRIC, jpeg_photometric);
            if (jpeg_photometric == PHOTOMETRIC_YCBCR)
            {
                uint16_t subsamplinghor = 2, subsamplingver = 2;
                if (input_compression == COMPRESSION_JPEG &&
                    input_photometric == PHOTOMETRIC_YCBCR)
                {
                    TIFFGetFieldDefaulted(in, TIFFTAG_YCBCRSUBSAMPLING,
                                          &subsamplinghor, &subsamplingver);

                    float *refBW = NULL;
                    if (TIFFGetField(in, TIFFTAG_REFERENCEBLACKWHITE, &refBW))
                    {
                        TIFFSetField(out, TIFFTAG_REFERENCEBLACKWHITE, refBW);
                    }
                }
                TIFFSetField(out, TIFFTAG_YCBCRSUBSAMPLING, subsamplinghor,
                             subsamplingver);
            }
        }
        else
            TIFFSetField(out, TIFFTAG_PHOTOMETRIC, input_photometric);
    }
    else if (compression == COMPRESSION_SGILOG ||
             compression == COMPRESSION_SGILOG24)
        TIFFSetField(out, TIFFTAG_PHOTOMETRIC,
                     samplesperpixel == 1 ? PHOTOMETRIC_LOGL
                                          : PHOTOMETRIC_LOGLUV);
    else
    {
        if (input_photometric == PHOTOMETRIC_YCBCR)
            TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
        else
            CopyTag(TIFFTAG_PHOTOMETRIC, 1, TIFF_SHORT);
    }
    if (fillorder != 0)
        TIFFSetField(out, TIFFTAG_FILLORDER, fillorder);
    else
        CopyTag(TIFFTAG_FILLORDER, 1, TIFF_SHORT);
    /*
     * Will copy `Orientation' tag from input image
     */
    TIFFGetFieldDefaulted(in, TIFFTAG_ORIENTATION, &orientation);
    TIFFSetField(out, TIFFTAG_ORIENTATION, orientation);
    /*
     * Choose tiles/strip for the output image according to
     * the command line arguments (-tiles, -strips) and the
     * structure of the input image.
     */
    if (outtiled == -1)
        outtiled = TIFFIsTiled(in);
    if (outtiled)
    {
        /*
         * Setup output file's tile width&height.  If either
         * is not specified, use either the value from the
         * input image or, if nothing is defined, use the
         * library default.
         */
        if (tilewidth == (uint32_t)-1)
            TIFFGetField(in, TIFFTAG_TILEWIDTH, &tilewidth);
        if (tilelength == (uint32_t)-1)
            TIFFGetField(in, TIFFTAG_TILELENGTH, &tilelength);
        TIFFDefaultTileSize(out, &tilewidth, &tilelength);
        TIFFSetField(out, TIFFTAG_TILEWIDTH, tilewidth);
        TIFFSetField(out, TIFFTAG_TILELENGTH, tilelength);
    }
    else
    {
        /*
         * RowsPerStrip is left unspecified: use either the
         * value from the input image or, if nothing is defined,
         * use the library default.
         */
        if (rowsperstrip == (uint32_t)0)
        {
            if (!TIFFGetField(in, TIFFTAG_ROWSPERSTRIP, &rowsperstrip))
            {
                rowsperstrip = TIFFDefaultStripSize(out, rowsperstrip);
            }
            if (rowsperstrip > length && rowsperstrip != (uint32_t)-1)
                rowsperstrip = length;
        }
        else if (rowsperstrip == (uint32_t)-1)
            rowsperstrip = length;
        TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
    }
    if (config != (uint16_t)-1)
        TIFFSetField(out, TIFFTAG_PLANARCONFIG, config);
    else
        CopyField(TIFFTAG_PLANARCONFIG, config);
    if (samplesperpixel <= 4)
        CopyTag(TIFFTAG_TRANSFERFUNCTION, 4, TIFF_SHORT);
    CopyTag(TIFFTAG_COLORMAP, 4, TIFF_SHORT);
    /* SMinSampleValue & SMaxSampleValue */
    switch (compression)
    {
        case COMPRESSION_JPEG:
            TIFFSetField(out, TIFFTAG_JPEGQUALITY, quality);
            /* For 3 sample images, the input data provided to libtiff is
             * always RGB, not YCbCr subsampled. */
            TIFFSetField(out, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
            break;
        case COMPRESSION_JBIG:
            CopyTag(TIFFTAG_FAXRECVPARAMS, 1, TIFF_LONG);
            CopyTag(TIFFTAG_FAXRECVTIME, 1, TIFF_LONG);
            CopyTag(TIFFTAG_FAXSUBADDRESS, 1, TIFF_ASCII);
            CopyTag(TIFFTAG_FAXDCS, 1, TIFF_ASCII);
            break;
        case COMPRESSION_LERC:
            if (max_z_error > 0)
            {
                if (TIFFSetField(out, TIFFTAG_LERC_MAXZERROR, max_z_error) != 1)
                {
                    return FALSE;
                }
            }
            if (subcodec != -1)
            {
                if (TIFFSetField(out, TIFFTAG_LERC_ADD_COMPRESSION, subcodec) !=
                    1)
                {
                    return FALSE;
                }
            }
            if (preset != -1)
            {
                switch (subcodec)
                {
                    case LERC_ADD_COMPRESSION_DEFLATE:
                        if (TIFFSetField(out, TIFFTAG_ZIPQUALITY, preset) != 1)
                        {
                            return FALSE;
                        }
                        break;
                    case LERC_ADD_COMPRESSION_ZSTD:
                        if (TIFFSetField(out, TIFFTAG_ZSTD_LEVEL, preset) != 1)
                        {
                            return FALSE;
                        }
                        break;
                    default:
                        break;
                }
            }
            break;
        case COMPRESSION_LZW:
        case COMPRESSION_ADOBE_DEFLATE:
        case COMPRESSION_DEFLATE:
        case COMPRESSION_LZMA:
        case COMPRESSION_ZSTD:
            if (predictor != (uint16_t)-1)
                TIFFSetField(out, TIFFTAG_PREDICTOR, predictor);
            else if (input_compression == COMPRESSION_LZW ||
                     input_compression == COMPRESSION_ADOBE_DEFLATE ||
                     input_compression == COMPRESSION_DEFLATE ||
                     input_compression == COMPRESSION_LZMA ||
                     input_compression == COMPRESSION_ZSTD)
            {
                CopyField(TIFFTAG_PREDICTOR, predictor);
            }
            if (compression == COMPRESSION_ADOBE_DEFLATE ||
                compression == COMPRESSION_DEFLATE)
            {
                if (subcodec != -1)
                {
                    if (TIFFSetField(out, TIFFTAG_DEFLATE_SUBCODEC, subcodec) !=
                        1)
                    {
                        return FALSE;
                    }
                }
            }
            if (preset != -1)
            {
                switch (compression)
                {
                    case COMPRESSION_ADOBE_DEFLATE:
                    case COMPRESSION_DEFLATE:
                        TIFFSetField(out, TIFFTAG_ZIPQUALITY, preset);
                        break;
                    case COMPRESSION_LZMA:
                        TIFFSetField(out, TIFFTAG_LZMAPRESET, preset);
                        break;
                    case COMPRESSION_ZSTD:
                        TIFFSetField(out, TIFFTAG_ZSTD_LEVEL, preset);
                        break;
                    default:
                        break;
                }
            }
            break;
        case COMPRESSION_WEBP:
            if (preset != -1)
            {
                if (preset == 100)
                {
                    TIFFSetField(out, TIFFTAG_WEBP_LOSSLESS, TRUE);
                }
                else
                {
                    TIFFSetField(out, TIFFTAG_WEBP_LEVEL, preset);
                }
            }
            break;
        case COMPRESSION_CCITTFAX3:
        case COMPRESSION_CCITTFAX4:
            if (compression == COMPRESSION_CCITTFAX3)
            {
                if (g3opts != (uint32_t)-1)
                    TIFFSetField(out, TIFFTAG_GROUP3OPTIONS, g3opts);
                else if (input_compression == COMPRESSION_CCITTFAX3)
                    CopyField(TIFFTAG_GROUP3OPTIONS, g3opts);
            }
            else if (input_compression == COMPRESSION_CCITTFAX4)
                CopyTag(TIFFTAG_GROUP4OPTIONS, 1, TIFF_LONG);
            if (input_compression == COMPRESSION_CCITTFAX3 ||
                input_compression == COMPRESSION_CCITTFAX4)
            {
                CopyTag(TIFFTAG_BADFAXLINES, 1, TIFF_LONG);
                CopyTag(TIFFTAG_CLEANFAXDATA, 1, TIFF_LONG);
                CopyTag(TIFFTAG_CONSECUTIVEBADFAXLINES, 1, TIFF_LONG);
            }
            CopyTag(TIFFTAG_FAXRECVPARAMS, 1, TIFF_LONG);
            CopyTag(TIFFTAG_FAXRECVTIME, 1, TIFF_LONG);
            CopyTag(TIFFTAG_FAXSUBADDRESS, 1, TIFF_ASCII);
            break;
        default:
            break;
    }
    {
        uint32_t len32;
        void **data;
        if (TIFFGetField(in, TIFFTAG_ICCPROFILE, &len32, &data))
            TIFFSetField(out, TIFFTAG_ICCPROFILE, len32, data);
    }
    {
        uint16_t ninks;
        const char *inknames;
        if (TIFFGetField(in, TIFFTAG_NUMBEROFINKS, &ninks))
        {
            TIFFSetField(out, TIFFTAG_NUMBEROFINKS, ninks);
            if (TIFFGetField(in, TIFFTAG_INKNAMES, &inknames))
            {
                size_t inknameslen = strlen(inknames) + 1;
                const char *cp = inknames;
                while (ninks > 1)
                {
                    cp = strchr(cp, '\0');
                    cp++;
                    inknameslen += (strlen(cp) + 1);
                    ninks--;
                }
                if (inknameslen <= INT_MAX)
                    TIFFSetField(out, TIFFTAG_INKNAMES, (int)inknameslen,
                                 inknames);
                else
                    TIFFError(TIFFFileName(in),
                              "Error, length of inknames= %" PRIu64
                              " exceeds size of int ",
                              (uint64_t)inknameslen);
            }
        }
    }
    {
        unsigned short pg0, pg1;

        if (pageInSeq == 1)
        {
            if (pageNum < 0) /* only one input file */
            {
                if (TIFFGetField(in, TIFFTAG_PAGENUMBER, &pg0, &pg1))
                    TIFFSetField(out, TIFFTAG_PAGENUMBER, pg0, pg1);
            }
            else
                TIFFSetField(out, TIFFTAG_PAGENUMBER, pageNum++, 0);
        }
        else
        {
            if (TIFFGetField(in, TIFFTAG_PAGENUMBER, &pg0, &pg1))
            {
                if (pageNum < 0) /* only one input file */
                    TIFFSetField(out, TIFFTAG_PAGENUMBER, pg0, pg1);
                else
                    TIFFSetField(out, TIFFTAG_PAGENUMBER, pageNum++, 0);
            }
        }
    }

    for (p = tags; p < &tags[NTAGS]; p++)
    {
        CopyTag(p->tag, p->count, p->type);
    }

    cf = pickCopyFunc(in, out, bitspersample, samplesperpixel);
    return (cf ? (*cf)(in, out, length, width, samplesperpixel) : FALSE);
}

/*
 * Copy Functions.
 */
#define DECLAREcpFunc(x)                                                       \
    static int x(TIFF *in, TIFF *out, uint32_t imagelength,                    \
                 uint32_t imagewidth, tsample_t spp)

#define DECLAREreadFunc(x)                                                     \
    static int x(TIFF *in, uint8_t *buf, uint32_t imagelength,                 \
                 uint32_t imagewidth, tsample_t spp)
typedef int (*readFunc)(TIFF *, uint8_t *, uint32_t, uint32_t, tsample_t);

#define DECLAREwriteFunc(x)                                                    \
    static int x(TIFF *out, uint8_t *buf, uint32_t imagelength,                \
                 uint32_t imagewidth, tsample_t spp)
typedef int (*writeFunc)(TIFF *, uint8_t *, uint32_t, uint32_t, tsample_t);

/*
 * Contig -> contig by scanline for rows/strip change.
 */
DECLAREcpFunc(cpContig2ContigByRow)
{
    tsize_t scanlinesize = TIFFScanlineSize(in);
    tdata_t buf;
    uint32_t row;

    buf = limitMalloc(scanlinesize);
    if (!buf)
        return 0;
    _TIFFmemset(buf, 0, scanlinesize);
    (void)imagewidth;
    (void)spp;
    for (row = 0; row < imagelength; row++)
    {
        if (TIFFReadScanline(in, buf, row, 0) < 0 && !ignore)
        {
            TIFFError(TIFFFileName(in), "Error, can't read scanline %" PRIu32,
                      row);
            goto bad;
        }
        if (TIFFWriteScanline(out, buf, row, 0) < 0)
        {
            TIFFError(TIFFFileName(out), "Error, can't write scanline %" PRIu32,
                      row);
            goto bad;
        }
    }
    _TIFFfree(buf);
    return 1;
bad:
    _TIFFfree(buf);
    return 0;
}

typedef void biasFn(void *image, void *bias, uint32_t pixels);

#define subtract(bits)                                                         \
    static void subtract##bits(void *i, void *b, uint32_t pixels)              \
    {                                                                          \
        uint##bits##_t *image = (uint##bits##_t *)i;                           \
        uint##bits##_t *biasx = (uint##bits##_t *)b;                           \
        while (pixels--)                                                       \
        {                                                                      \
            *image = (uint##bits##_t)(*image > *biasx ? *image - *biasx : 0);  \
            image++, biasx++;                                                  \
        }                                                                      \
    }

subtract(8) subtract(16) subtract(32)

    static biasFn *lineSubtractFn(unsigned bits)
{
    switch (bits)
    {
        case 8:
            return subtract8;
        case 16:
            return subtract16;
        case 32:
            return subtract32;
        default:
            break;
    }
    return NULL;
}

/*
 * Contig -> contig by scanline while subtracting a bias image.
 */
DECLAREcpFunc(cpBiasedContig2Contig)
{
    if (spp == 1)
    {
        tsize_t biasSize = TIFFScanlineSize(bias);
        tsize_t bufSize = TIFFScanlineSize(in);
        tdata_t buf, biasBuf;
        uint32_t biasWidth = 0, biasLength = 0;
        TIFFGetField(bias, TIFFTAG_IMAGEWIDTH, &biasWidth);
        TIFFGetField(bias, TIFFTAG_IMAGELENGTH, &biasLength);
        if (biasSize == bufSize && imagelength == biasLength &&
            imagewidth == biasWidth)
        {
            uint16_t sampleBits = 0;
            biasFn *subtractLine;
            TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &sampleBits);
            subtractLine = lineSubtractFn(sampleBits);
            if (subtractLine)
            {
                uint32_t row;
                buf = limitMalloc(bufSize);
                biasBuf = limitMalloc(bufSize);
                if (!buf || !biasBuf)
                    goto bad;
                for (row = 0; row < imagelength; row++)
                {
                    if (TIFFReadScanline(in, buf, row, 0) < 0 && !ignore)
                    {
                        TIFFError(TIFFFileName(in),
                                  "Error, can't read scanline %" PRIu32, row);
                        goto bad;
                    }
                    if (TIFFReadScanline(bias, biasBuf, row, 0) < 0 && !ignore)
                    {
                        TIFFError(TIFFFileName(in),
                                  "Error, can't read biased scanline %" PRIu32,
                                  row);
                        goto bad;
                    }
                    subtractLine(buf, biasBuf, imagewidth);
                    if (TIFFWriteScanline(out, buf, row, 0) < 0)
                    {
                        TIFFError(TIFFFileName(out),
                                  "Error, can't write scanline %" PRIu32, row);
                        goto bad;
                    }
                }

                _TIFFfree(buf);
                _TIFFfree(biasBuf);
                TIFFSetDirectory(bias, TIFFCurrentDirectory(bias)); /* rewind */
                return 1;
            bad:
                if (buf)
                    _TIFFfree(buf);
                if (biasBuf)
                    _TIFFfree(biasBuf);
                return 0;
            }
            else
            {
                TIFFError(TIFFFileName(in),
                          "No support for biasing %" PRIu16 " bit pixels\n",
                          sampleBits);
                return 0;
            }
        }
        TIFFError(TIFFFileName(in),
                  "Bias image %s,%" PRIu32
                  "\nis not the same size as %s,%" PRIu32 "\n",
                  TIFFFileName(bias), TIFFCurrentDirectory(bias),
                  TIFFFileName(in), TIFFCurrentDirectory(in));
        return 0;
    }
    else
    {
        TIFFError(TIFFFileName(in),
                  "Can't bias %s,%" PRIu32 " as it has >1 Sample/Pixel\n",
                  TIFFFileName(in), TIFFCurrentDirectory(in));
        return 0;
    }
}

/*
 * Strip -> strip for change in encoding.
 */
DECLAREcpFunc(cpDecodedStrips)
{
    tsize_t stripsize = TIFFStripSize(in);
    tdata_t buf = limitMalloc(stripsize);

    (void)imagewidth;
    (void)spp;
    if (buf)
    {
        tstrip_t s, ns = TIFFNumberOfStrips(in);
        uint32_t row = 0;
        _TIFFmemset(buf, 0, stripsize);
        for (s = 0; s < ns && row < imagelength; s++)
        {
            tsize_t cc = (row + rowsperstrip > imagelength)
                             ? TIFFVStripSize(in, imagelength - row)
                             : stripsize;
            if (TIFFReadEncodedStrip(in, s, buf, cc) < 0 && !ignore)
            {
                TIFFError(TIFFFileName(in), "Error, can't read strip %" PRIu32,
                          s);
                goto bad;
            }
            if (TIFFWriteEncodedStrip(out, s, buf, cc) < 0)
            {
                TIFFError(TIFFFileName(out),
                          "Error, can't write strip %" PRIu32, s);
                goto bad;
            }
            row += rowsperstrip;
        }
        _TIFFfree(buf);
        return 1;
    }
    else
    {
        TIFFError(
            TIFFFileName(in),
            "Error, can't allocate memory buffer of size %" TIFF_SSIZE_FORMAT
            " to read strips",
            stripsize);
        return 0;
    }

bad:
    _TIFFfree(buf);
    return 0;
}

/*
 * Separate -> separate by row for rows/strip change.
 */
DECLAREcpFunc(cpSeparate2SeparateByRow)
{
    tsize_t scanlinesize = TIFFScanlineSize(in);
    tdata_t buf;
    uint32_t row;
    tsample_t s;

    (void)imagewidth;
    buf = limitMalloc(scanlinesize);
    if (!buf)
        return 0;
    _TIFFmemset(buf, 0, scanlinesize);
    for (s = 0; s < spp; s++)
    {
        for (row = 0; row < imagelength; row++)
        {
            if (TIFFReadScanline(in, buf, row, s) < 0 && !ignore)
            {
                TIFFError(TIFFFileName(in),
                          "Error, can't read scanline %" PRIu32, row);
                goto bad;
            }
            if (TIFFWriteScanline(out, buf, row, s) < 0)
            {
                TIFFError(TIFFFileName(out),
                          "Error, can't write scanline %" PRIu32, row);
                goto bad;
            }
        }
    }
    _TIFFfree(buf);
    return 1;
bad:
    _TIFFfree(buf);
    return 0;
}

/*
 * Contig -> separate by row.
 */
DECLAREcpFunc(cpContig2SeparateByRow)
{
    tsize_t scanlinesizein = TIFFScanlineSize(in);
    tsize_t scanlinesizeout = TIFFScanlineSize(out);
    tdata_t inbuf;
    tdata_t outbuf;
    uint8_t *inp, *outp;
    uint32_t n;
    uint32_t row;
    tsample_t s;
    uint16_t bps = 0;

    (void)TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &bps);
    if (bps != 8)
    {
        TIFFError(TIFFFileName(in),
                  "Error, can only handle BitsPerSample=8 in %s",
                  "cpContig2SeparateByRow");
        return 0;
    }

    inbuf = limitMalloc(scanlinesizein);
    outbuf = limitMalloc(scanlinesizeout);
    if (!inbuf || !outbuf)
        goto bad;
    _TIFFmemset(inbuf, 0, scanlinesizein);
    _TIFFmemset(outbuf, 0, scanlinesizeout);
    /* unpack channels */
    for (s = 0; s < spp; s++)
    {
        for (row = 0; row < imagelength; row++)
        {
            if (TIFFReadScanline(in, inbuf, row, 0) < 0 && !ignore)
            {
                TIFFError(TIFFFileName(in),
                          "Error, can't read scanline %" PRIu32, row);
                goto bad;
            }
            inp = ((uint8_t *)inbuf) + s;
            outp = (uint8_t *)outbuf;
            for (n = imagewidth; n-- > 0;)
            {
                *outp++ = *inp;
                inp += spp;
            }
            if (TIFFWriteScanline(out, outbuf, row, s) < 0)
            {
                TIFFError(TIFFFileName(out),
                          "Error, can't write scanline %" PRIu32, row);
                goto bad;
            }
        }
    }
    if (inbuf)
        _TIFFfree(inbuf);
    if (outbuf)
        _TIFFfree(outbuf);
    return 1;
bad:
    if (inbuf)
        _TIFFfree(inbuf);
    if (outbuf)
        _TIFFfree(outbuf);
    return 0;
}

/*
 * Separate -> contig by row.
 */
DECLAREcpFunc(cpSeparate2ContigByRow)
{
    tsize_t scanlinesizein = TIFFScanlineSize(in);
    tsize_t scanlinesizeout = TIFFScanlineSize(out);
    tdata_t inbuf;
    tdata_t outbuf;
    uint8_t *inp, *outp;
    uint32_t n;
    uint32_t row;
    tsample_t s;
    uint16_t bps = 0;

    (void)TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &bps);
    if (bps != 8)
    {
        TIFFError(TIFFFileName(in),
                  "Error, can only handle BitsPerSample=8 in %s",
                  "cpSeparate2ContigByRow");
        return 0;
    }

    inbuf = limitMalloc(scanlinesizein);
    outbuf = limitMalloc(scanlinesizeout);
    if (!inbuf || !outbuf)
        goto bad;
    _TIFFmemset(inbuf, 0, scanlinesizein);
    _TIFFmemset(outbuf, 0, scanlinesizeout);
    for (row = 0; row < imagelength; row++)
    {
        /* merge channels */
        for (s = 0; s < spp; s++)
        {
            if (TIFFReadScanline(in, inbuf, row, s) < 0 && !ignore)
            {
                TIFFError(TIFFFileName(in),
                          "Error, can't read scanline %" PRIu32, row);
                goto bad;
            }
            inp = (uint8_t *)inbuf;
            outp = ((uint8_t *)outbuf) + s;
            for (n = imagewidth; n-- > 0;)
            {
                *outp = *inp++;
                outp += spp;
            }
        }
        if (TIFFWriteScanline(out, outbuf, row, 0) < 0)
        {
            TIFFError(TIFFFileName(out), "Error, can't write scanline %" PRIu32,
                      row);
            goto bad;
        }
    }
    if (inbuf)
        _TIFFfree(inbuf);
    if (outbuf)
        _TIFFfree(outbuf);
    return 1;
bad:
    if (inbuf)
        _TIFFfree(inbuf);
    if (outbuf)
        _TIFFfree(outbuf);
    return 0;
}

static void cpStripToTile(uint8_t *out, uint8_t *in, uint32_t rows,
                          uint32_t cols, int64_t outskew, int64_t inskew)
{
    while (rows-- > 0)
    {
        uint32_t j = cols;
        while (j-- > 0)
            *out++ = *in++;
        out += outskew;
        in += inskew;
    }
}

static void cpContigBufToSeparateBuf(uint8_t *out, uint8_t *in, uint32_t rows,
                                     uint32_t cols, int64_t outskew,
                                     int64_t inskew, tsample_t spp,
                                     int bytes_per_sample)
{
    while (rows-- > 0)
    {
        uint32_t j = cols;
        while (j-- > 0)
        {
            int n = bytes_per_sample;

            while (n--)
            {
                *out++ = *in++;
            }
            in += (spp - 1) * bytes_per_sample;
        }
        out += outskew;
        in += inskew;
    }
}

static void cpSeparateBufToContigBuf(uint8_t *out, uint8_t *in, uint32_t rows,
                                     uint32_t cols, int64_t outskew,
                                     int64_t inskew, tsample_t spp,
                                     int bytes_per_sample)
{
    while (rows-- > 0)
    {
        uint32_t j = cols;
        while (j-- > 0)
        {
            int n = bytes_per_sample;

            while (n--)
            {
                *out++ = *in++;
            }
            out += (spp - 1) * bytes_per_sample;
        }
        out += outskew;
        in += inskew;
    }
}

static int cpImage(TIFF *in, TIFF *out, readFunc fin, writeFunc fout,
                   uint32_t imagelength, uint32_t imagewidth, tsample_t spp)
{
    int status = 0;
    tdata_t buf = NULL;
    tsize_t scanlinesize = TIFFRasterScanlineSize(in);

    if (scanlinesize && imagelength)
    {
        tsize_t bytes =
            _TIFFMultiplySSize(in, scanlinesize, imagelength, "image buffer");
        if (bytes == 0)
            return 0;
        buf = limitMalloc(bytes);
        if (buf)
        {
            if ((*fin)(in, (uint8_t *)buf, imagelength, imagewidth, spp))
            {
                status =
                    (*fout)(out, (uint8_t *)buf, imagelength, imagewidth, spp);
            }
            _TIFFfree(buf);
        }
        else
        {
            TIFFError(TIFFFileName(in),
                      "Error, can't allocate space for image buffer");
        }
    }
    else
    {
        TIFFError(TIFFFileName(in),
                  "Error, no space for image buffer - integer overflow");
    }

    return status;
}

DECLAREreadFunc(readContigStripsIntoBuffer)
{
    tsize_t scanlinesize = TIFFScanlineSize(in);
    uint8_t *bufp = buf;
    uint32_t row;

    (void)imagewidth;
    (void)spp;
    for (row = 0; row < imagelength; row++)
    {
        if (TIFFReadScanline(in, (tdata_t)bufp, row, 0) < 0 && !ignore)
        {
            TIFFError(TIFFFileName(in), "Error, can't read scanline %" PRIu32,
                      row);
            return 0;
        }
        bufp += scanlinesize;
    }

    return 1;
}

DECLAREreadFunc(readSeparateStripsIntoBuffer)
{
    int status = 1;
    tsize_t scanlinesize = TIFFScanlineSize(in);
    tsize_t rowstride;
    tdata_t scanline;
    if (!scanlinesize)
        return 0;
    rowstride =
        _TIFFMultiplySSize(in, scanlinesize, spp, "separate row stride");
    if (rowstride == 0)
        return 0;

    scanline = limitMalloc(scanlinesize);
    if (!scanline)
        return 0;
    _TIFFmemset(scanline, 0, scanlinesize);
    (void)imagewidth;
    if (scanline)
    {
        uint8_t *bufp = (uint8_t *)buf;
        uint32_t row;
        tsample_t s;
        for (row = 0; row < imagelength; row++)
        {
            /* merge channels */
            for (s = 0; s < spp; s++)
            {
                uint8_t *bp = bufp + s;
                tsize_t n = scanlinesize;
                uint8_t *sbuf = (uint8_t *)scanline;

                if (TIFFReadScanline(in, scanline, row, s) < 0 && !ignore)
                {
                    TIFFError(TIFFFileName(in),
                              "Error, can't read scanline %" PRIu32, row);
                    status = 0;
                    goto done;
                }
                while (n-- > 0)
                    *bp = *sbuf++, bp += spp;
            }
            bufp += rowstride;
        }
    }

done:
    _TIFFfree(scanline);
    return status;
}

/* This is a helper function. */
static uint32_t _TIFFCastSSizeToUInt32(tmsize_t val, const char *module)
{
    if (val < 0)
    {
        TIFFError(module, "Unsigned integer underflow (negative)");
        return 0;
    }
    /* sizeof(tmsize_t) is determined by SIZEOF_SIZE_T */
#ifdef SIZEOF_SIZE_T
#if SIZEOF_SIZE_T > 4
    if (val > UINT32_MAX)
    {
        TIFFError(module, "Integer overflow");
        return 0;
    }
#endif
#else
#pragma message(                                                               \
    "---- Error: SIZEOF_SIZE_T not defined. Generate a compile error. ----")
    SIZEOF_SIZE_T
#endif
    return (uint32_t)val;
}

DECLAREreadFunc(readContigTilesIntoBuffer)
{
    int status = 1;
    tsize_t tilesize = TIFFTileSize(in);
    tdata_t tilebuf;
    uint32_t imagew = _TIFFCastSSizeToUInt32(TIFFScanlineSize(in),
                                             "readContigTilesIntoBuffer");
    uint32_t tilew = _TIFFCastSSizeToUInt32(TIFFTileRowSize(in),
                                            "readContigTilesIntoBuffer");
    int64_t iskew = (int64_t)imagew - (int64_t)tilew;
    uint8_t *bufp = (uint8_t *)buf;
    uint32_t tw, tl;
    uint32_t row;

    (void)spp;
    tilebuf = limitMalloc(tilesize);
    if (tilebuf == 0)
        return 0;
    _TIFFmemset(tilebuf, 0, tilesize);
    (void)TIFFGetField(in, TIFFTAG_TILEWIDTH, &tw);
    (void)TIFFGetField(in, TIFFTAG_TILELENGTH, &tl);
    if (tw == 0 || tl == 0)
    {
        status = 0;
        goto done;
    }

    for (row = 0; row < imagelength; row += tl)
    {
        uint32_t nrow = (row + tl > imagelength) ? imagelength - row : tl;
        uint32_t colb = 0;
        uint32_t col;

        for (col = 0; col < imagewidth && colb < imagew; col += tw)
        {
            if (TIFFReadTile(in, tilebuf, col, row, 0, 0) < 0 && !ignore)
            {
                TIFFError(TIFFFileName(in),
                          "Error, can't read tile at %" PRIu32 " %" PRIu32, col,
                          row);
                status = 0;
                goto done;
            }
            if (tilew > imagew - colb)
            {
                uint32_t width = imagew - colb;
                int64_t oskew = (int64_t)tilew - (int64_t)width;
                cpStripToTile(bufp + colb, (uint8_t *)tilebuf, nrow, width,
                              oskew + iskew, oskew);
            }
            else
                cpStripToTile(bufp + colb, (uint8_t *)tilebuf, nrow, tilew,
                              iskew, 0);
            if (tilew > UINT32_MAX - colb)
            {
                status = 0;
                goto done;
            }
            colb += tilew;
        }
        {
            tmsize_t row_advance =
                _TIFFComputeRowOffset(in, imagew, nrow, "image row offset");
            if (row_advance == 0 && nrow != 0 && imagew != 0)
            {
                status = 0;
                goto done;
            }
            bufp += row_advance;
        }
    }
done:
    _TIFFfree(tilebuf);
    return status;
}

DECLAREreadFunc(readSeparateTilesIntoBuffer)
{
    int status = 1;
    uint32_t imagew = _TIFFCastSSizeToUInt32(TIFFRasterScanlineSize(in),
                                             "readSeparateTilesIntoBuffer");
    uint32_t tilew = _TIFFCastSSizeToUInt32(TIFFTileRowSize(in),
                                            "readSeparateTilesIntoBuffer");
    int64_t iskew;
    tsize_t tilesize = TIFFTileSize(in);
    tdata_t tilebuf;
    uint8_t *bufp = (uint8_t *)buf;
    uint32_t tw, tl;
    uint32_t row;
    uint16_t bps = 0, bytes_per_sample;
    uint32_t tilew_spp;
    uint32_t sample_stride;

    {
        uint64_t tilew_spp64 =
            _TIFFMultiply64(in, tilew, spp, "tile row stride");
        tilew_spp = _TIFFCastUInt64ToUInt32(in, tilew_spp64, "tile row stride");
        if ((tilew_spp64 == 0 && tilew != 0 && spp != 0) ||
            (tilew_spp == 0 && tilew_spp64 != 0))
        {
            TIFFError(TIFFFileName(in),
                      "Error, cannot handle that much samples per tile row "
                      "(Tile Width * Samples/Pixel)");
            return 0;
        }
    }

    if (tilew_spp > imagew || (imagew - tilew_spp) > INT_MAX)
    {
        TIFFError(TIFFFileName(in),
                  "Error, image raster scan line size is too large");
        return 0;
    }

    iskew = (int64_t)imagew - (int64_t)tilew_spp;
    tilebuf = limitMalloc(tilesize);
    if (tilebuf == 0)
        return 0;
    _TIFFmemset(tilebuf, 0, tilesize);
    (void)TIFFGetField(in, TIFFTAG_TILEWIDTH, &tw);
    (void)TIFFGetField(in, TIFFTAG_TILELENGTH, &tl);
    (void)TIFFGetField(in, TIFFTAG_BITSPERSAMPLE, &bps);
    if (tw == 0 || tl == 0)
    {
        status = 0;
        goto done;
    }
    if (bps == 0)
    {
        TIFFError(TIFFFileName(in), "Error, cannot read BitsPerSample");
        status = 0;
        goto done;
    }
    if ((bps % 8) != 0)
    {
        TIFFError(
            TIFFFileName(in),
            "Error, cannot handle BitsPerSample that is not a multiple of 8");
        status = 0;
        goto done;
    }
    bytes_per_sample = bps / 8;
    if (spp == 0 || bytes_per_sample == 0)
    {
        TIFFError(TIFFFileName(in), "Error, invalid sample size");
        status = 0;
        goto done;
    }
    {
        uint64_t sample_stride64 =
            _TIFFMultiply64(in, spp, bytes_per_sample, "sample stride");
        sample_stride =
            _TIFFCastUInt64ToUInt32(in, sample_stride64, "sample stride");
        if ((sample_stride64 == 0 && spp != 0) || tilew_spp == 0 ||
            sample_stride == 0)
        {
            status = 0;
            goto done;
        }
    }

    for (row = 0; row < imagelength; row += tl)
    {
        uint32_t nrow = (row + tl > imagelength) ? imagelength - row : tl;
        uint32_t colb = 0;
        uint32_t col;

        for (col = 0; col < imagewidth; col += tw)
        {
            tsample_t s;

            if (colb >= imagew)
                break;
            for (s = 0; s < spp; s++)
            {
                if (TIFFReadTile(in, tilebuf, col, row, 0, s) < 0 && !ignore)
                {
                    TIFFError(TIFFFileName(in),
                              "Error, can't read tile at %" PRIu32 " %" PRIu32
                              ", "
                              "sample %" PRIu16,
                              col, row, s);
                    status = 0;
                    goto done;
                }
                /*
                 * Tile is clipped horizontally.  Calculate
                 * visible portion and skewing factors.
                 */
                if (tilew_spp > imagew - colb)
                {
                    uint32_t width = imagew - colb;
                    int64_t oskew = (int64_t)tilew_spp - (int64_t)width;
                    uint64_t sample_offset64 = _TIFFMultiply64(
                        in, s, bytes_per_sample, "sample offset");
                    tmsize_t sample_offset = _TIFFCastUInt64ToSSize(
                        in, sample_offset64, "sample offset");
                    tmsize_t input_offset =
                        _TIFFAddSSize(in, colb, sample_offset, "input offset");
                    if ((sample_offset64 == 0 && s != 0) ||
                        (sample_offset == 0 && sample_offset64 != 0) ||
                        (input_offset == 0 &&
                         (colb != 0 || sample_offset != 0)))
                    {
                        status = 0;
                        goto done;
                    }
                    cpSeparateBufToContigBuf(
                        bufp + input_offset, (uint8_t *)tilebuf, nrow,
                        width / sample_stride, oskew + iskew, oskew / spp, spp,
                        bytes_per_sample);
                }
                else
                {
                    uint64_t sample_offset64 = _TIFFMultiply64(
                        in, s, bytes_per_sample, "sample offset");
                    tmsize_t sample_offset = _TIFFCastUInt64ToSSize(
                        in, sample_offset64, "sample offset");
                    tmsize_t input_offset =
                        _TIFFAddSSize(in, colb, sample_offset, "input offset");
                    if ((sample_offset64 == 0 && s != 0) ||
                        (sample_offset == 0 && sample_offset64 != 0) ||
                        (input_offset == 0 &&
                         (colb != 0 || sample_offset != 0)))
                    {
                        status = 0;
                        goto done;
                    }
                    cpSeparateBufToContigBuf(bufp + input_offset,
                                             (uint8_t *)tilebuf, nrow, tw,
                                             iskew, 0, spp, bytes_per_sample);
                }
            }
            if (tilew_spp > UINT32_MAX - colb)
            {
                status = 0;
                goto done;
            }
            colb += tilew_spp;
        }
        {
            tmsize_t row_advance =
                _TIFFComputeRowOffset(in, imagew, nrow, "image row offset");
            if (row_advance == 0 && nrow != 0 && imagew != 0)
            {
                status = 0;
                goto done;
            }
            bufp += row_advance;
        }
    }
done:
    _TIFFfree(tilebuf);
    return status;
}

DECLAREwriteFunc(writeBufferToContigStrips)
{
    uint32_t row;
    tstrip_t strip = 0;

    (void)imagewidth;
    (void)spp;
    (void)TIFFGetFieldDefaulted(out, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
    for (row = 0; row < imagelength; row += rowsperstrip)
    {
        uint32_t nrows = (row + rowsperstrip > imagelength) ? imagelength - row
                                                            : rowsperstrip;
        tsize_t stripsize = TIFFVStripSize(out, nrows);
        if (TIFFWriteEncodedStrip(out, strip++, buf, stripsize) < 0)
        {
            TIFFError(TIFFFileName(out), "Error, can't write strip %" PRIu32,
                      strip - 1u);
            return 0;
        }
        buf += stripsize;
    }
    return 1;
}

DECLAREwriteFunc(writeBufferToSeparateStrips)
{
    tmsize_t rowsize;
    uint64_t rowsize64;
    tsize_t stripsize = TIFFStripSize(out);
    tdata_t obuf;
    tstrip_t strip = 0;
    tsample_t s;
    uint16_t bps = 0, bytes_per_sample;

    obuf = limitMalloc(stripsize);
    if (obuf == NULL)
        return (0);
    _TIFFmemset(obuf, 0, stripsize);
    (void)TIFFGetFieldDefaulted(out, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
    (void)TIFFGetField(out, TIFFTAG_BITSPERSAMPLE, &bps);
    if (bps == 0)
    {
        TIFFError(TIFFFileName(out), "Error, cannot read BitsPerSample");
        _TIFFfree(obuf);
        return 0;
    }
    if ((bps % 8) != 0)
    {
        TIFFError(
            TIFFFileName(out),
            "Error, cannot handle BitsPerSample that is not a multiple of 8");
        _TIFFfree(obuf);
        return 0;
    }
    bytes_per_sample = bps / 8;
    rowsize64 = _TIFFMultiply64(out, imagewidth, spp, "separate row stride");
    if (rowsize64 == 0 && imagewidth != 0 && spp != 0)
    {
        _TIFFfree(obuf);
        return 0;
    }
    rowsize = _TIFFCastUInt64ToSSize(out, rowsize64, "separate row stride");
    if (rowsize == 0 && rowsize64 != 0)
    {
        _TIFFfree(obuf);
        return 0;
    }
    for (s = 0; s < spp; s++)
    {
        uint32_t row;
        for (row = 0; row < imagelength; row += rowsperstrip)
        {
            uint32_t nrows = (row + rowsperstrip > imagelength)
                                 ? imagelength - row
                                 : rowsperstrip;
            stripsize = TIFFVStripSize(out, nrows);

            {
                tmsize_t row_offset;
                tmsize_t in_offset;
                if (row == 0 || rowsize == 0)
                    row_offset = 0;
                else
                    row_offset =
                        _TIFFComputeRowOffset(out, rowsize, row, "row offset");
                if (row_offset == 0 && row != 0 && rowsize != 0)
                {
                    _TIFFfree(obuf);
                    return 0;
                }
                in_offset =
                    _TIFFAddSSize(out, row_offset, s, "input buffer offset");
                if (in_offset == 0 && (row_offset != 0 || s != 0))
                {
                    _TIFFfree(obuf);
                    return 0;
                }
                cpContigBufToSeparateBuf(
                    (uint8_t *)obuf, (uint8_t *)buf + in_offset, nrows,
                    imagewidth, 0, 0, spp, bytes_per_sample);
            }
            if (TIFFWriteEncodedStrip(out, strip++, obuf, stripsize) < 0)
            {
                TIFFError(TIFFFileName(out),
                          "Error, can't write strip %" PRIu32, strip - 1u);
                _TIFFfree(obuf);
                return 0;
            }
        }
    }
    _TIFFfree(obuf);
    return 1;
}

DECLAREwriteFunc(writeBufferToContigTiles)
{
    uint32_t imagew = _TIFFCastSSizeToUInt32(TIFFScanlineSize(out),
                                             "writeBufferToContigTiles");
    uint32_t tilew = _TIFFCastSSizeToUInt32(TIFFTileRowSize(out),
                                            "writeBufferToContigTiles");
    int64_t iskew = (int64_t)imagew - (int64_t)tilew;
    tsize_t tilesize = TIFFTileSize(out);
    tdata_t obuf;
    uint8_t *bufp = (uint8_t *)buf;
    uint32_t tl, tw;
    uint32_t row;

    (void)spp;

    obuf = limitMalloc(TIFFTileSize(out));
    if (obuf == NULL)
        return 0;
    _TIFFmemset(obuf, 0, tilesize);
    (void)TIFFGetField(out, TIFFTAG_TILELENGTH, &tl);
    (void)TIFFGetField(out, TIFFTAG_TILEWIDTH, &tw);
    if (tw == 0 || tl == 0)
    {
        _TIFFfree(obuf);
        return 0;
    }
    for (row = 0; row < imagelength; row += tilelength)
    {
        uint32_t nrow = (row + tl > imagelength) ? imagelength - row : tl;
        uint32_t colb = 0;
        uint32_t col;

        for (col = 0; col < imagewidth && colb < imagew; col += tw)
        {
            /*
             * Tile is clipped horizontally.  Calculate
             * visible portion and skewing factors.
             */
            if (tilew > imagew - colb)
            {
                uint32_t width = imagew - colb;
                int64_t oskew = (int64_t)tilew - (int64_t)width;
                cpStripToTile((uint8_t *)obuf, bufp + colb, nrow, width, oskew,
                              oskew + iskew);
            }
            else
                cpStripToTile((uint8_t *)obuf, bufp + colb, nrow, tilew, 0,
                              iskew);
            if (TIFFWriteTile(out, obuf, col, row, 0, 0) < 0)
            {
                TIFFError(TIFFFileName(out),
                          "Error, can't write tile at %" PRIu32 " %" PRIu32,
                          col, row);
                _TIFFfree(obuf);
                return 0;
            }
            if (tilew > UINT32_MAX - colb)
            {
                _TIFFfree(obuf);
                return 0;
            }
            colb += tilew;
        }
        {
            tmsize_t row_advance =
                _TIFFComputeRowOffset(out, imagew, nrow, "image row offset");
            if (row_advance == 0 && nrow != 0 && imagew != 0)
            {
                _TIFFfree(obuf);
                return 0;
            }
            bufp += row_advance;
        }
    }
    _TIFFfree(obuf);
    return 1;
}

DECLAREwriteFunc(writeBufferToSeparateTiles)
{
    uint32_t imagew = _TIFFCastSSizeToUInt32(TIFFScanlineSize(out),
                                             "writeBufferToSeparateTiles");
    uint32_t tilew = _TIFFCastSSizeToUInt32(TIFFTileRowSize(out),
                                            "writeBufferToSeparateTiles");
    uint32_t iimagew = _TIFFCastSSizeToUInt32(TIFFRasterScanlineSize(out),
                                              "writeBufferToSeparateTiles");
    int64_t iskew;
    tsize_t tilesize = TIFFTileSize(out);
    tdata_t obuf;
    uint8_t *bufp = (uint8_t *)buf;
    uint32_t tl, tw;
    uint32_t row;
    uint16_t bps = 0, bytes_per_sample;
    uint64_t tilew_spp64;

    tilew_spp64 = _TIFFMultiply64(out, tilew, spp, "tile row stride");
    if ((tilew_spp64 == 0 && tilew != 0 && spp != 0) ||
        tilew_spp64 > (uint64_t)INT64_MAX)
        return 0;
    iskew = (int64_t)iimagew - (int64_t)tilew_spp64;
    obuf = limitMalloc(TIFFTileSize(out));
    if (obuf == NULL)
        return 0;
    _TIFFmemset(obuf, 0, tilesize);
    (void)TIFFGetField(out, TIFFTAG_TILELENGTH, &tl);
    (void)TIFFGetField(out, TIFFTAG_TILEWIDTH, &tw);
    (void)TIFFGetField(out, TIFFTAG_BITSPERSAMPLE, &bps);
    if (tw == 0 || tl == 0)
    {
        _TIFFfree(obuf);
        return 0;
    }
    if (bps == 0)
    {
        TIFFError(TIFFFileName(out), "Error, cannot read BitsPerSample");
        _TIFFfree(obuf);
        return 0;
    }
    if ((bps % 8) != 0)
    {
        TIFFError(
            TIFFFileName(out),
            "Error, cannot handle BitsPerSample that is not a multiple of 8");
        _TIFFfree(obuf);
        return 0;
    }
    bytes_per_sample = bps / 8;

    for (row = 0; row < imagelength; row += tl)
    {
        uint32_t nrow = (row + tl > imagelength) ? imagelength - row : tl;
        uint32_t colb = 0;
        uint32_t col;

        for (col = 0; col < imagewidth; col += tw)
        {
            tsample_t s;
            if (colb >= imagew)
                break;
            for (s = 0; s < spp; s++)
            {
                uint64_t col_offset64 =
                    _TIFFMultiply64(out, colb, spp, "input buffer offset");
                uint64_t input_offset64 =
                    _TIFFAdd64(out, col_offset64, s, "input buffer offset");
                tmsize_t input_offset = _TIFFCastUInt64ToSSize(
                    out, input_offset64, "input buffer offset");
                if ((col_offset64 == 0 && colb != 0 && spp != 0) ||
                    (input_offset64 == 0 && (col_offset64 != 0 || s != 0)) ||
                    (input_offset == 0 && input_offset64 != 0))
                {
                    _TIFFfree(obuf);
                    return 0;
                }
                /*
                 * Tile is clipped horizontally.  Calculate
                 * visible portion and skewing factors.
                 */
                if (tilew > imagew - colb)
                {
                    uint32_t width = (imagew - colb);
                    int64_t oskew = (int64_t)tilew - (int64_t)width;
                    uint64_t oskew_spp64 =
                        _TIFFMultiply64(out, (uint64_t)oskew, spp, "tile skew");
                    int64_t clipped_iskew;
                    if ((oskew_spp64 == 0 && oskew != 0 && spp != 0) ||
                        oskew_spp64 > (uint64_t)INT64_MAX)
                    {
                        _TIFFfree(obuf);
                        return 0;
                    }
                    if (iskew > 0 && (int64_t)oskew_spp64 > INT64_MAX - iskew)
                    {
                        _TIFFfree(obuf);
                        return 0;
                    }
                    clipped_iskew = (int64_t)oskew_spp64 + iskew;

                    cpContigBufToSeparateBuf(
                        (uint8_t *)obuf, bufp + input_offset, nrow,
                        width / bytes_per_sample, oskew, clipped_iskew, spp,
                        bytes_per_sample);
                }
                else
                    cpContigBufToSeparateBuf(
                        (uint8_t *)obuf, bufp + input_offset, nrow, tilewidth,
                        0, iskew, spp, bytes_per_sample);
                if (TIFFWriteTile(out, obuf, col, row, 0, s) < 0)
                {
                    TIFFError(TIFFFileName(out),
                              "Error, can't write tile at %" PRIu32 " %" PRIu32
                              " sample %" PRIu16,
                              col, row, s);
                    _TIFFfree(obuf);
                    return 0;
                }
            }
            if (tilew > UINT32_MAX - colb)
            {
                _TIFFfree(obuf);
                return 0;
            }
            colb += tilew;
        }
        {
            tmsize_t row_advance =
                _TIFFComputeRowOffset(out, iimagew, nrow, "image row offset");
            if (row_advance == 0 && nrow != 0 && iimagew != 0)
            {
                _TIFFfree(obuf);
                return 0;
            }
            bufp += row_advance;
        }
    }
    _TIFFfree(obuf);
    return 1;
}

/*
 * Contig strips -> contig tiles.
 */
DECLAREcpFunc(cpContigStrips2ContigTiles)
{
    return cpImage(in, out, readContigStripsIntoBuffer,
                   writeBufferToContigTiles, imagelength, imagewidth, spp);
}

/*
 * Contig strips -> separate tiles.
 */
DECLAREcpFunc(cpContigStrips2SeparateTiles)
{
    return cpImage(in, out, readContigStripsIntoBuffer,
                   writeBufferToSeparateTiles, imagelength, imagewidth, spp);
}

/*
 * Separate strips -> contig tiles.
 */
DECLAREcpFunc(cpSeparateStrips2ContigTiles)
{
    return cpImage(in, out, readSeparateStripsIntoBuffer,
                   writeBufferToContigTiles, imagelength, imagewidth, spp);
}

/*
 * Separate strips -> separate tiles.
 */
DECLAREcpFunc(cpSeparateStrips2SeparateTiles)
{
    return cpImage(in, out, readSeparateStripsIntoBuffer,
                   writeBufferToSeparateTiles, imagelength, imagewidth, spp);
}

/*
 * Contig strips -> contig tiles.
 */
DECLAREcpFunc(cpContigTiles2ContigTiles)
{
    return cpImage(in, out, readContigTilesIntoBuffer, writeBufferToContigTiles,
                   imagelength, imagewidth, spp);
}

/*
 * Contig tiles -> separate tiles.
 */
DECLAREcpFunc(cpContigTiles2SeparateTiles)
{
    return cpImage(in, out, readContigTilesIntoBuffer,
                   writeBufferToSeparateTiles, imagelength, imagewidth, spp);
}

/*
 * Separate tiles -> contig tiles.
 */
DECLAREcpFunc(cpSeparateTiles2ContigTiles)
{
    return cpImage(in, out, readSeparateTilesIntoBuffer,
                   writeBufferToContigTiles, imagelength, imagewidth, spp);
}

/*
 * Separate tiles -> separate tiles (tile dimension change).
 */
DECLAREcpFunc(cpSeparateTiles2SeparateTiles)
{
    return cpImage(in, out, readSeparateTilesIntoBuffer,
                   writeBufferToSeparateTiles, imagelength, imagewidth, spp);
}

/*
 * Contig tiles -> contig tiles (tile dimension change).
 */
DECLAREcpFunc(cpContigTiles2ContigStrips)
{
    return cpImage(in, out, readContigTilesIntoBuffer,
                   writeBufferToContigStrips, imagelength, imagewidth, spp);
}

/*
 * Contig tiles -> separate strips.
 */
DECLAREcpFunc(cpContigTiles2SeparateStrips)
{
    return cpImage(in, out, readContigTilesIntoBuffer,
                   writeBufferToSeparateStrips, imagelength, imagewidth, spp);
}

/*
 * Separate tiles -> contig strips.
 */
DECLAREcpFunc(cpSeparateTiles2ContigStrips)
{
    return cpImage(in, out, readSeparateTilesIntoBuffer,
                   writeBufferToContigStrips, imagelength, imagewidth, spp);
}

/*
 * Separate tiles -> separate strips.
 */
DECLAREcpFunc(cpSeparateTiles2SeparateStrips)
{
    return cpImage(in, out, readSeparateTilesIntoBuffer,
                   writeBufferToSeparateStrips, imagelength, imagewidth, spp);
}

/*
 * Select the appropriate copy function to use.
 */
static copyFunc pickCopyFunc(TIFF *in, TIFF *out, uint16_t bitspersample,
                             uint16_t samplesperpixel)
{
    uint16_t shortv;
    uint32_t w, l, tw, tl;
    int bychunk;

    (void)TIFFGetFieldDefaulted(in, TIFFTAG_PLANARCONFIG, &shortv);
    if (shortv != config && bitspersample != 8 && samplesperpixel > 1)
    {
        fprintf(stderr,
                "%s: Cannot handle different planar configuration w/ "
                "bits/sample != 8\n",
                TIFFFileName(in));
        return 0;
    }
    TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &w);
    TIFFGetField(in, TIFFTAG_IMAGELENGTH, &l);
    if (!(TIFFIsTiled(out) || TIFFIsTiled(in)))
    {
        uint32_t irps = (uint32_t)-1L;
        TIFFGetField(in, TIFFTAG_ROWSPERSTRIP, &irps);
        /* if biased, force decoded copying to allow image subtraction */
        bychunk = !bias && (rowsperstrip == irps);
    }
    else
    { /* either in or out is tiled */
        if (bias)
        {
            fprintf(stderr,
                    "%s: Cannot handle tiled configuration w/bias image\n",
                    TIFFFileName(in));
            return 0;
        }
        if (TIFFIsTiled(out))
        {
            if (!TIFFGetField(in, TIFFTAG_TILEWIDTH, &tw))
                tw = w;
            if (!TIFFGetField(in, TIFFTAG_TILELENGTH, &tl))
                tl = l;
            bychunk = (tw == tilewidth && tl == tilelength);
        }
        else
        { /* out's not, so in must be tiled */
            TIFFGetField(in, TIFFTAG_TILEWIDTH, &tw);
            TIFFGetField(in, TIFFTAG_TILELENGTH, &tl);
            bychunk = (tw == w && tl == rowsperstrip);
        }
    }
#define T 1
#define F 0
#define pack(a, b, c, d, e)                                                    \
    ((long)(((a) << 11) | ((b) << 3) | ((c) << 2) | ((d) << 1) | (e)))
    switch (pack(shortv, config, TIFFIsTiled(in), TIFFIsTiled(out), bychunk))
    {
        /* Strips -> Tiles */
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, F, T, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, F, T, T):
            return cpContigStrips2ContigTiles;
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, F, T, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, F, T, T):
            return cpContigStrips2SeparateTiles;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, F, T, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, F, T, T):
            return cpSeparateStrips2ContigTiles;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, F, T, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, F, T, T):
            return cpSeparateStrips2SeparateTiles;
        /* Tiles -> Tiles */
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, T, T, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, T, T, T):
            return cpContigTiles2ContigTiles;
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, T, T, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, T, T, T):
            return cpContigTiles2SeparateTiles;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, T, T, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, T, T, T):
            return cpSeparateTiles2ContigTiles;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, T, T, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, T, T, T):
            return cpSeparateTiles2SeparateTiles;
        /* Tiles -> Strips */
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, T, F, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, T, F, T):
            return cpContigTiles2ContigStrips;
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, T, F, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, T, F, T):
            return cpContigTiles2SeparateStrips;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, T, F, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, T, F, T):
            return cpSeparateTiles2ContigStrips;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, T, F, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, T, F, T):
            return cpSeparateTiles2SeparateStrips;
        /* Strips -> Strips */
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, F, F, F):
            return bias ? cpBiasedContig2Contig : cpContig2ContigByRow;
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_CONTIG, F, F, T):
            return cpDecodedStrips;
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, F, F, F):
        case pack(PLANARCONFIG_CONTIG, PLANARCONFIG_SEPARATE, F, F, T):
            return cpContig2SeparateByRow;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, F, F, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_CONTIG, F, F, T):
            return cpSeparate2ContigByRow;
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, F, F, F):
        case pack(PLANARCONFIG_SEPARATE, PLANARCONFIG_SEPARATE, F, F, T):
            return cpSeparate2SeparateByRow;
        default:
            break;
    }
#undef pack
#undef F
#undef T
    fprintf(stderr, "tiffcp: %s: Don't know how to copy/convert image.\n",
            TIFFFileName(in));
    return 0;
}
