/*
 * Copyright (c) 1991-1996 Sam Leffler
 * Copyright (c) 1991-1996 Silicon Graphics, Inc.
 *
 * 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.
 */

/*
 * TIFF Library
 *
 * Written by Conrad J. Poelman, PL/WSAT, Kirtland AFB, NM on 26 Mar 96.
 *
 * This file contains code to allow a calling program to "iterate" over each
 * pixels in an image as it is read from the file. The iterator takes care of
 * reading strips versus (possibly clipped) tiles, decoding the information
 * according to the decoding method, and so on, so that calling program can
 * ignore those details. The calling program does, however, need to be
 * conscious of the type of the pixel data that it is receiving.
 *
 * For reasons of efficiency, the callback function actually gets called for
 * "blocks" of pixels rather than for individual pixels. The format of the
 * callback arguments is given below.
 *
 * This code was taken from TIFFReadRGBAImage() in tif_getimage.c of the
 * original TIFF distribution, and simplified and generalized to provide this
 * general iteration capability. Those routines could certainly be
 * re-implemented in terms of a TIFFImageIter if desired.
 *
 */
#include "tif_imageiter.h"
#include "tiffiop.h"
#include <assert.h>
#include <stdio.h>

static int gtTileContig(TIFFImageIter *, void *udata, uint32_t, uint32_t);
static int gtTileSeparate(TIFFImageIter *, void *udata, uint32_t, uint32_t);
static int gtStripContig(TIFFImageIter *, void *udata, uint32_t, uint32_t);
static int gtStripSeparate(TIFFImageIter *, void *udata, uint32_t, uint32_t);

static const char photoTag[] = "PhotometricInterpretation";

static int isCCITTCompression(TIFF *tif)
{
    uint16_t compress;
    TIFFGetField(tif, TIFFTAG_COMPRESSION, &compress);
    return (compress == COMPRESSION_CCITTFAX3 ||
            compress == COMPRESSION_CCITTFAX4 ||
            compress == COMPRESSION_CCITTRLE ||
            compress == COMPRESSION_CCITTRLEW);
}

int TIFFImageIterBegin(TIFFImageIter *img, TIFF *tif, int stop, char emsg[1024])
{
    uint16_t *sampleinfo;
    uint16_t extrasamples;
    uint16_t planarconfig;
    int colorchannels;

    img->tif = tif;
    img->stoponerr = stop;
    TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &img->bitspersample);
    img->alpha = 0;
    TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &img->samplesperpixel);
    TIFFGetFieldDefaulted(tif, TIFFTAG_EXTRASAMPLES, &extrasamples,
                          &sampleinfo);
    if (extrasamples == 1)
        switch (sampleinfo[0])
        {
            case EXTRASAMPLE_ASSOCALPHA: /* data is pre-multiplied */
            case EXTRASAMPLE_UNASSALPHA: /* data is not pre-multiplied */
                img->alpha = sampleinfo[0];
                break;
        }
    colorchannels = img->samplesperpixel - extrasamples;
    TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planarconfig);
    if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &img->photometric))
    {
        switch (colorchannels)
        {
            case 1:
                if (isCCITTCompression(tif))
                    img->photometric = PHOTOMETRIC_MINISWHITE;
                else
                    img->photometric = PHOTOMETRIC_MINISBLACK;
                break;
            case 3:
                img->photometric = PHOTOMETRIC_RGB;
                break;
            default:
                sprintf(emsg, "Missing needed %s tag", photoTag);
                return (0);
        }
    }
    switch (img->photometric)
    {
        case PHOTOMETRIC_PALETTE:
            if (!TIFFGetField(tif, TIFFTAG_COLORMAP, &img->redcmap,
                              &img->greencmap, &img->bluecmap))
            {
                TIFFErrorExtR(tif, TIFFFileName(tif),
                              "Missing required \"Colormap\" tag");
                return (0);
            }
            /* fall through... */
        case PHOTOMETRIC_MINISWHITE:
        case PHOTOMETRIC_MINISBLACK:
            /* This should work now so skip the check - BSR
                    if (planarconfig == PLANARCONFIG_CONTIG &&
               img->samplesperpixel != 1) { sprintf(emsg, "Sorry, can not handle
               contiguous data with %s=%d, and %s=%d", photoTag,
               img->photometric, "Samples/pixel", img->samplesperpixel); return
               (0);
                    }
             */
            break;
        case PHOTOMETRIC_YCBCR:
            if (planarconfig != PLANARCONFIG_CONTIG)
            {
                sprintf(emsg, "Sorry, can not handle YCbCr images with %s=%d",
                        "Planarconfiguration", planarconfig);
                return (0);
            }
            /* It would probably be nice to have a reality check here. */
            {
                uint16_t compress;
                TIFFGetField(tif, TIFFTAG_COMPRESSION, &compress);
                if (compress == COMPRESSION_JPEG &&
                    planarconfig == PLANARCONFIG_CONTIG)
                {
                    /* can rely on libjpeg to convert to RGB */
                    /* XXX should restore current state on exit */
                    TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
                    img->photometric = PHOTOMETRIC_RGB;
                }
            }
            break;
        case PHOTOMETRIC_RGB:
            if (colorchannels < 3)
            {
                sprintf(emsg, "Sorry, can not handle RGB image with %s=%d",
                        "Color channels", colorchannels);
                return (0);
            }
            break;
        case PHOTOMETRIC_SEPARATED:
        {
            uint16_t inkset;
            TIFFGetFieldDefaulted(tif, TIFFTAG_INKSET, &inkset);
            if (inkset != INKSET_CMYK)
            {
                sprintf(emsg,
                        "Sorry, can not handle separated image with %s=%d",
                        "InkSet", inkset);
                return (0);
            }
            if (img->samplesperpixel != 4)
            {
                sprintf(emsg,
                        "Sorry, can not handle separated image with %s=%d",
                        "Samples/pixel", img->samplesperpixel);
                return (0);
            }
            break;
        }
        default:
            sprintf(emsg, "Sorry, can not handle image with %s=%d", photoTag,
                    img->photometric);
            return (0);
    }
    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &img->width);
    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &img->height);

    TIFFGetFieldDefaulted(tif, TIFFTAG_ORIENTATION, &img->orientation);
    switch (img->orientation)
    {
        case ORIENTATION_BOTRIGHT:
        case ORIENTATION_RIGHTBOT: /* XXX */
        case ORIENTATION_LEFTBOT:  /* XXX */
            TIFFWarning(TIFFFileName(tif), "using bottom-left orientation");
            img->orientation = ORIENTATION_BOTLEFT;
            /* fall through... */
        case ORIENTATION_BOTLEFT:
            break;
        case ORIENTATION_TOPRIGHT:
        case ORIENTATION_RIGHTTOP: /* XXX */
        case ORIENTATION_LEFTTOP:  /* XXX */
        default:
            TIFFWarning(TIFFFileName(tif), "using top-left orientation");
            img->orientation = ORIENTATION_TOPLEFT;
            /* fall through... */
        case ORIENTATION_TOPLEFT:
            break;
    }

    img->isContig =
        !(planarconfig == PLANARCONFIG_SEPARATE && colorchannels > 1);
    if (img->isContig)
    {
        img->get = TIFFIsTiled(tif) ? gtTileContig : gtStripContig;
    }
    else
    {
        img->get = TIFFIsTiled(tif) ? gtTileSeparate : gtStripSeparate;
    }
    return (1);
}

int TIFFImageIterGet(TIFFImageIter *img, void *udata, uint32_t w, uint32_t h)
{
    if (img->get == NULL)
    {
        TIFFErrorExtR(img->tif, TIFFFileName(img->tif),
                      "No \"get\" routine setup");
        return (0);
    }
    if (img->callback.any == NULL)
    {
        TIFFErrorExtR(
            img->tif, TIFFFileName(img->tif),
            "No \"put\" routine setupl; probably can not handle image format");
        return (0);
    }
    return (*img->get)(img, udata, w, h);
}

TIFFImageIterEnd(TIFFImageIter *img) { /* Nothing to free... ? */ }

/*
 * Read the specified image into an ABGR-format raster.
 */
int TIFFReadImageIter(TIFF *tif, uint32_t rwidth, uint32_t rheight,
                      uint8_t *raster, int stop)
{
    char emsg[1024];
    TIFFImageIter img;
    int ok;

    if (TIFFImageIterBegin(&img, tif, stop, emsg))
    {
        /* XXX verify rwidth and rheight against width and height */
        ok = TIFFImageIterGet(&img, raster, rwidth, img.height);
        TIFFImageIterEnd(&img);
    }
    else
    {
        TIFFErrorExtR(tif, TIFFFileName(tif), emsg);
        ok = 0;
    }
    return (ok);
}

/*
 * Get an tile-organized image that has
 *	PlanarConfiguration contiguous if SamplesPerPixel > 1
 * or
 *	SamplesPerPixel == 1
 */
static int gtTileContig(TIFFImageIter *img, void *udata, uint32_t w, uint32_t h)
{
    TIFF *tif = img->tif;
    ImageIterTileContigRoutine callback = img->callback.contig;
    uint16_t orientation;
    uint32_t col, row;
    uint32_t tw, th;
    u_char *buf;
    int32_t fromskew;
    uint32_t nrow;

    buf = (u_char *)_TIFFmalloc(TIFFTileSize(tif));
    if (buf == 0)
    {
        TIFFErrorExtR(tif, TIFFFileName(tif), "No space for tile buffer");
        return (0);
    }
    TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tw);
    TIFFGetField(tif, TIFFTAG_TILELENGTH, &th);
    orientation = img->orientation;
    for (row = 0; row < h; row += th)
    {
        nrow = (row + th > h ? h - row : th);
        for (col = 0; col < w; col += tw)
        {
            if (TIFFReadTile(tif, buf, col, row, 0, 0) < 0 && img->stoponerr)
                break;
            if (col + tw > w)
            {
                /*
                 * Tile is clipped horizontally.  Calculate
                 * visible portion and skewing factors.
                 */
                uint32_t npix = w - col;
                fromskew = tw - npix;
                (*callback)(img, udata, col, row, npix, nrow, fromskew, buf);
            }
            else
            {
                (*callback)(img, udata, col, row, tw, nrow, 0, buf);
            }
        }
    }
    _TIFFfree(buf);
    return (1);
}

/*
 * Get an tile-organized image that has
 *	 SamplesPerPixel > 1
 *	 PlanarConfiguration separated
 * We assume that all such images are RGB.
 */
static int gtTileSeparate(TIFFImageIter *img, void *udata, uint32_t w,
                          uint32_t h)
{
    TIFF *tif = img->tif;
    ImageIterTileSeparateRoutine callback = img->callback.separate;
    uint16_t orientation;
    uint32_t col, row;
    uint32_t tw, th;
    u_char *buf;
    u_char *r;
    u_char *g;
    u_char *b;
    u_char *a;
    tsize_t tilesize;
    int32_t fromskew;
    int alpha = img->alpha;
    uint32_t nrow;

    tilesize = TIFFTileSize(tif);
    buf = (u_char *)_TIFFmalloc(4 * tilesize);
    if (buf == 0)
    {
        TIFFErrorExtR(tif, TIFFFileName(tif), "No space for tile buffer");
        return (0);
    }
    r = buf;
    g = r + tilesize;
    b = g + tilesize;
    a = b + tilesize;
    if (!alpha)
        memset(a, 0xff, tilesize);
    TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tw);
    TIFFGetField(tif, TIFFTAG_TILELENGTH, &th);
    orientation = img->orientation;
    for (row = 0; row < h; row += th)
    {
        nrow = (row + th > h ? h - row : th);
        for (col = 0; col < w; col += tw)
        {
            if (TIFFReadTile(tif, r, col, row, 0, 0) < 0 && img->stoponerr)
                break;
            if (TIFFReadTile(tif, g, col, row, 0, 1) < 0 && img->stoponerr)
                break;
            if (TIFFReadTile(tif, b, col, row, 0, 2) < 0 && img->stoponerr)
                break;
            if (alpha && TIFFReadTile(tif, a, col, row, 0, 3) < 0 &&
                img->stoponerr)
                break;
            if (col + tw > w)
            {
                /*
                 * Tile is clipped horizontally.  Calculate
                 * visible portion and skewing factors.
                 */
                uint32_t npix = w - col;
                fromskew = tw - npix;
                (*callback)(img, udata, col, row, npix, nrow, fromskew, r, g, b,
                            a);
            }
            else
            {
                (*callback)(img, udata, col, row, tw, nrow, 0, r, g, b, a);
            }
        }
    }
    _TIFFfree(buf);
    return (1);
}

/*
 * Get a strip-organized image that has
 *	PlanarConfiguration contiguous if SamplesPerPixel > 1
 * or
 *	SamplesPerPixel == 1
 */
static int gtStripContig(TIFFImageIter *img, void *udata, uint32_t w,
                         uint32_t h)
{
    TIFF *tif = img->tif;
    ImageIterTileContigRoutine callback = img->callback.contig;
    uint16_t orientation;
    uint32_t row, nrow;
    u_char *buf;
    uint32_t rowsperstrip;
    uint32_t imagewidth = img->width;
    tsize_t scanline;
    int32_t fromskew;

    buf = (u_char *)_TIFFmalloc(TIFFStripSize(tif));
    if (buf == 0)
    {
        TIFFErrorExtR(tif, TIFFFileName(tif), "No space for strip buffer");
        return (0);
    }
    orientation = img->orientation;
    TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
    scanline = TIFFScanlineSize(tif);
    fromskew = (w < imagewidth ? imagewidth - w : 0);
    for (row = 0; row < h; row += rowsperstrip)
    {
        nrow = (row + rowsperstrip > h ? h - row : rowsperstrip);
        if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 0), buf,
                                 nrow * scanline) < 0 &&
            img->stoponerr)
            break;
        (*callback)(img, udata, 0, row, w, nrow, fromskew, buf);
    }
    _TIFFfree(buf);
    return (1);
}

/*
 * Get a strip-organized image with
 *	 SamplesPerPixel > 1
 *	 PlanarConfiguration separated
 * We assume that all such images are RGB.
 */
static int gtStripSeparate(TIFFImageIter *img, void *udata, uint32_t w,
                           uint32_t h)
{
    TIFF *tif = img->tif;
    ImageIterTileSeparateRoutine callback = img->callback.separate;
    uint16_t orientation;
    u_char *buf;
    u_char *r, *g, *b, *a;
    uint32_t row, nrow;
    tsize_t scanline;
    uint32_t rowsperstrip;
    uint32_t imagewidth = img->width;
    tsize_t stripsize;
    int32_t fromskew;
    int alpha = img->alpha;

    stripsize = TIFFStripSize(tif);
    r = buf = (u_char *)_TIFFmalloc(4 * stripsize);
    if (buf == 0)
    {
        TIFFErrorExtR(tif, TIFFFileName(tif), "No space for tile buffer");
        return (0);
    }
    g = r + stripsize;
    b = g + stripsize;
    a = b + stripsize;
    if (!alpha)
        memset(a, 0xff, stripsize);
    orientation = img->orientation;
    TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
    scanline = TIFFScanlineSize(tif);
    fromskew = (w < imagewidth ? imagewidth - w : 0);
    for (row = 0; row < h; row += rowsperstrip)
    {
        nrow = (row + rowsperstrip > h ? h - row : rowsperstrip);
        if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 0), r,
                                 nrow * scanline) < 0 &&
            img->stoponerr)
            break;
        if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 1), g,
                                 nrow * scanline) < 0 &&
            img->stoponerr)
            break;
        if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 2), b,
                                 nrow * scanline) < 0 &&
            img->stoponerr)
            break;
        if (alpha && (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 3),
                                           a, nrow * scanline) < 0 &&
                      img->stoponerr))
            break;
        (*callback)(img, udata, 0, row, w, nrow, fromskew, r, g, b, a);
    }
    _TIFFfree(buf);
    return (1);
}

DECLAREContigCallbackFunc(TestContigCallback)
{
    printf("Contig Callback called with x = %d, y = %d, w = %d, h = %d, "
           "fromskew = %d\n",
           x, y, w, h, fromskew);
}

DECLARESepCallbackFunc(TestSepCallback)
{
    printf("Sep Callback called with x = %d, y = %d, w = %d, h = %d, fromskew "
           "= %d\n",
           x, y, w, h, fromskew);
}

#ifdef MAIN
main(int argc, char **argv)
{
    char emsg[1024];
    TIFFImageIter img;
    int ok;
    int stop = 1;

    TIFF *tif;
    unsigned long nx, ny;
    unsigned short BitsPerSample, SamplesPerPixel;
    int isColorMapped, isPliFile;
    unsigned char *ColorMap;
    unsigned char *data;

    if (argc < 2)
    {
        fprintf(stderr, "usage: %s tiff_file\n", argv[0]);
        exit(1);
    }
    tif = (TIFF *)PLIGetImage(argv[1], (void *)&data, &ColorMap, &nx, &ny,
                              &BitsPerSample, &SamplesPerPixel, &isColorMapped,
                              &isPliFile);
    if (tif != NULL)
    {

        if (TIFFImageIterBegin(&img, tif, stop, emsg))
        {
            /* Here need to set data and callback function! */
            if (img.isContig)
            {
                img.callback = TestContigCallback;
            }
            else
            {
                img.callback = TestSepCallback;
            }
            ok = TIFFImageIterGet(&img, NULL, img.width, img.height);
            TIFFImageIterEnd(&img);
        }
        else
        {
            TIFFErrorExtR(tif, TIFFFileName(tif), emsg);
        }
    }
}
#endif
