/*
 * Copyright (c) 1990-1997 Sam Leffler
 * Copyright (c) 1991-1997 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.
 */

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

#include <ctype.h>
#include <gl.h>

#include "tiffio.h"

typedef unsigned char unsigned char;
typedef unsigned long uint32_t;

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

uint32_t rowsperstrip = (uint32_t)-1;
uint16_t compression = COMPRESSION_PACKBITS;
uint16_t config = PLANARCONFIG_CONTIG;
uint16_t predictor = 0;
int xmaxscreen;
int ymaxscreen;
uint16_t photometric = PHOTOMETRIC_RGB;
int jpegcolormode = JPEGCOLORMODE_RGB;
int quality = 75; /* JPEG quality */

static void usage(void);
static void tiffsv(char *, int, int, int, int);

int main(int argc, char *argv[])
{
    int c;
#if !HAVE_DECL_OPTARG
    extern int optind;
    extern char *optarg;
#endif

    while ((c = getopt(argc, argv, "c:p:r:")) != -1)
        switch (c)
        {
            case 'b': /* save as b&w */
                photometric = PHOTOMETRIC_MINISBLACK;
                break;
            case 'c': /* compression scheme */
                if (streq(optarg, "none"))
                    compression = COMPRESSION_NONE;
                else if (streq(optarg, "packbits"))
                    compression = COMPRESSION_PACKBITS;
                else if (strneq(optarg, "jpeg", 4))
                {
                    char *cp = strchr(optarg, ':');
                    if (cp && isdigit(cp[1]))
                        quality = atoi(cp + 1);
                    if (cp && strchr(cp, 'r'))
                        jpegcolormode = JPEGCOLORMODE_RAW;
                    compression = COMPRESSION_JPEG;
                }
                else if (strneq(optarg, "lzw", 3))
                {
                    char *cp = strchr(optarg, ':');
                    if (cp)
                        predictor = atoi(cp + 1);
                    compression = COMPRESSION_LZW;
                }
                else
                    usage();
                break;
            case 'p': /* planar configuration */
                if (streq(optarg, "separate"))
                    config = PLANARCONFIG_SEPARATE;
                else if (streq(optarg, "contig"))
                    config = PLANARCONFIG_CONTIG;
                else
                    usage();
                break;
            case 'r': /* rows/strip */
                rowsperstrip = atoi(optarg);
                break;
            case '?':
                usage();
                /*NOTREACHED*/
        }
    if (argc - optind != 1 && argc - optind != 5)
        usage();
    xmaxscreen = getgdesc(GD_XPMAX) - 1;
    ymaxscreen = getgdesc(GD_YPMAX) - 1;
    foreground();
    noport();
    winopen("tiffsv");
    if (argc - optind == 5)
        tiffsv(argv[optind], atoi(argv[optind + 1]), atoi(argv[optind + 2]),
               atoi(argv[optind + 3]), atoi(argv[optind + 4]));
    else
        tiffsv(argv[optind], 0, xmaxscreen, 0, ymaxscreen);
    return (0);
}

char *stuff[] = {
    "usage: tiffsv [options] outimage.tif [x1 x2 y1 y2] [-b]",
    "where options are:",
    " -p contig	pack samples contiguously (e.g. RGBRGB...)",
    " -p separate	store samples separately (e.g. RRR...GGG...BBB...)",
    "",
    " -r #		make each strip have no more than # rows",
    "",
    " -c lzw[:opts]	compress output with Lempel-Ziv & Welch encoding",
    " -c jpeg[:opts]compress output with JPEG encoding",
    " -c packbits	compress output with packbits encoding",
    " -c none	use no compression algorithm on output",
    "",
    "JPEG options:",
    " #		set compression quality level (0-100, default 75)",
    " r		output color image as RGB rather than YCbCr",
    "",
    "LZW options:",
    " #		set predictor value for Lempel-Ziv & Welch encoding",
    "For example, -c lzw:2 to get LZW-encoded data with horizontal "
    "differencing",
    NULL};

static void usage(void)
{
    char buf[BUFSIZ];
    int i;

    setbuf(stderr, buf);
    for (i = 0; stuff[i] != NULL; i++)
        fprintf(stderr, "%s\n", stuff[i]);
    exit(-1);
}

static void svRGBSeparate(TIFF *tif, uint32_t *ss, int xsize, int ysize)
{
    tsize_t stripsize = TIFFStripSize(tif);
    unsigned char *rbuf = (unsigned char *)_TIFFmalloc(3 * stripsize);
    unsigned char *gbuf = rbuf + stripsize;
    unsigned char *bbuf = gbuf + stripsize;
    register int y;

    for (y = 0; y <= ysize; y += rowsperstrip)
    {
        unsigned char *rp, *gp, *bp;
        register int x;
        register uint32_t n;

        n = rowsperstrip;
        if (n > ysize - y + 1)
            n = ysize - y + 1;
        rp = rbuf;
        gp = gbuf;
        bp = bbuf;
        do
        {
            for (x = 0; x <= xsize; x++)
            {
                uint32_t v = ss[x];
                rp[x] = v;
                gp[x] = v >> 8;
                bp[x] = v >> 16;
            }
            rp += xsize + 1, gp += xsize + 1, bp += xsize + 1;
            ss += xsize + 1;
        } while (--n);
        if (TIFFWriteEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), rbuf,
                                  stripsize) < 0)
            break;
        if (TIFFWriteEncodedStrip(tif, TIFFComputeStrip(tif, y, 1), gbuf,
                                  stripsize) < 0)
            break;
        if (TIFFWriteEncodedStrip(tif, TIFFComputeStrip(tif, y, 2), bbuf,
                                  stripsize) < 0)
            break;
    }
    _TIFFfree(rbuf);
}

static void svRGBContig(TIFF *tif, uint32_t *ss, int xsize, int ysize)
{
    register int x, y;
    tsize_t stripsize = TIFFStripSize(tif);
    unsigned char *strip = (unsigned char *)_TIFFmalloc(stripsize);

    for (y = 0; y <= ysize; y += rowsperstrip)
    {
        register unsigned char *pp = strip;
        register uint32_t n;

        n = rowsperstrip;
        if (n > ysize - y + 1)
            n = ysize - y + 1;
        do
        {
            for (x = 0; x <= xsize; x++)
            {
                uint32_t v = ss[x];
                pp[0] = v;
                pp[1] = v >> 8;
                pp[2] = v >> 16;
                pp += 3;
            }
            ss += xsize + 1;
        } while (--n);
        if (TIFFWriteEncodedStrip(tif, TIFFComputeStrip(tif, y, 0), strip,
                                  stripsize) < 0)
            break;
    }
    _TIFFfree(strip);
}

#undef RED
#undef GREEN
#undef BLUE
#define CVT(x) (((x)*255) / 100)
#define RED CVT(28)   /* 28% */
#define GREEN CVT(59) /* 59% */
#define BLUE CVT(11)  /* 11% */

static void svGrey(TIFF *tif, uint32_t *ss, int xsize, int ysize)
{
    register int x, y;
    unsigned char *buf = (unsigned char *)_TIFFmalloc(TIFFScanlineSize(tif));

    for (y = 0; y <= ysize; y++)
    {
        for (x = 0; x <= xsize; x++)
        {
            unsigned char *cp = (unsigned char *)&ss[x];
            buf[x] = (RED * cp[3] + GREEN * cp[2] + BLUE * cp[1]) >> 8;
        }
        if (TIFFWriteScanline(tif, buf, (uint32_t)y, 0) < 0)
            break;
        ss += xsize + 1;
    }
    _TIFFfree(buf);
}

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))

static void tiffsv(char *name, int x1, int x2, int y1, int y2)
{
    TIFF *tif;
    int xsize, ysize;
    int xorg, yorg;
    uint32_t *scrbuf;

    xorg = MIN(x1, x2);
    yorg = MIN(y1, y2);
    if (xorg < 0)
        xorg = 0;
    if (yorg < 0)
        yorg = 0;
    xsize = ABS(x2 - x1);
    ysize = ABS(y2 - y1);
    if (xorg + xsize > xmaxscreen)
        xsize = xmaxscreen - xorg;
    if (yorg + ysize > ymaxscreen)
        ysize = ymaxscreen - yorg;
    tif = TIFFOpen(name, "w");
    TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32_t)(xsize + 1));
    TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint32_t)(ysize + 1));
    TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
    TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL,
                 photometric == PHOTOMETRIC_RGB ? 3 : 1);
    TIFFSetField(tif, TIFFTAG_PLANARCONFIG, config);
    TIFFSetField(tif, TIFFTAG_COMPRESSION, compression);
    switch (compression)
    {
        case COMPRESSION_JPEG:
            if (photometric == PHOTOMETRIC_RGB &&
                jpegcolormode == JPEGCOLORMODE_RGB)
                photometric = PHOTOMETRIC_YCBCR;
            TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality);
            TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode);
            break;
        case COMPRESSION_LZW:
            if (predictor != 0)
                TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor);
            break;
    }
    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric);
    TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_BOTLEFT);
    rowsperstrip = TIFFDefaultStripSize(tif, rowsperstrip);
    TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
    scrbuf =
        (uint32_t *)_TIFFmalloc((xsize + 1) * (ysize + 1) * sizeof(uint32_t));
    readdisplay(xorg, yorg, xorg + xsize, yorg + ysize, scrbuf, RD_FREEZE);
    if (photometric == PHOTOMETRIC_RGB)
    {
        if (config == PLANARCONFIG_SEPARATE)
            svRGBSeparate(tif, scrbuf, xsize, ysize);
        else
            svRGBContig(tif, scrbuf, xsize, ysize);
    }
    else
        svGrey(tif, scrbuf, xsize, ysize);
    (void)TIFFClose(tif);
    _TIFFfree((char *)scrbuf);
}
