/* ----------------------------------------------------------------------
 *
 * Create a single image stereogram from a height map.
 * by Scott Pakin <scott+pbm@pakin.org>
 * Adapted to Netbpm conventions by Bryan Henderson.
 * Revised by Scott Pakin.
 *
 * The core of this program is a simple adaptation of the code in
 * "Displaying 3D Images: Algorithms for Single Image Random Dot
 * Stereograms" by Harold W. Thimbleby, Stuart Inglis, and Ian
 * H. Witten in IEEE Computer, 27(10):38-48, October 1994.  See that
 * paper for a thorough explanation of what's going on here.
 *
 * ----------------------------------------------------------------------
 *
 * Copyright (C) 2006 Scott Pakin <scott+pbm@pakin.org>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ----------------------------------------------------------------------
 */

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

#include "pam.h"
#include "shhopt.h"
#include "mallocvar.h"

/* Define a few helper macros. */
#define round2int(X) ((int)((X)+0.5))      /* Nonnegative numbers only */

typedef sample *(*COORD2COLOR)(int, int);
    /* A type to use for functions that map a 2-D coordinate to a color. */


enum outputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR};

/* ---------------------------------------------------------------------- */

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *inputFilespec;  /* '-' if stdin */
    bool verbose;               /* true=verbose diagnostic output; false=minimal */
    unsigned int crosseyed;     /* 1=produce a crosseyed stereogram */
    unsigned int makemask;      /* 1=output a mask to use for making patterns; 0=normal behavior */
    unsigned int dpi;           /* Resolution of the output device in dots per inch */
    float eyesep;               /* Eye separation in inches */
    float depthOfField;         /* Depth of field (fraction of viewing distance) */
    pixval maxval;              /* 0 = unspecified */
    int guidesize;              /* 0 = don't display guides, <0 = display at top; >0 = display at bottom */
    unsigned int magnifypat;    /* Magnification factor of pattern or random colors */
    unsigned int xshift;        /* Number of pixels to shift pattern image right */
    unsigned int yshift;        /* Number of pixels to shift pattern image down */
    const char *patFilespec;    /* Name of pattern file; NULL = none */
    enum outputType outputType; /* Type of output file */
};



static void
parseCommandLine(int                 argc,
                 char **             argv,
                 struct cmdlineInfo *cmdlineP ) {
/*----------------------------------------------------------------------------
   Parse program command line described in Unix standard form by argc
   and argv.  Return the information in the options as *cmdlineP.

   If command line is internally inconsistent (invalid options, etc.),
   issue error message to stderr and abort program.

   Note that the strings we return are stored in the storage that
   was passed to us as the argv array.  We also trash *argv.
-----------------------------------------------------------------------------*/
    optEntry *option_def;
        /* Instructions to optParseOptions3 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;

    unsigned int patfileSpec, dpiSpec, eyesepSpec, depthSpec,
        maxvalSpec, guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec;

    unsigned int blackandwhite, grayscale, color;


    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
    OPTENT3(0, "verbose",         OPT_FLAG,   NULL,
            (unsigned int *)&cmdlineP->verbose,       0);
    OPTENT3(0, "crosseyed",       OPT_FLAG,   NULL,
            &cmdlineP->crosseyed,     0);
    OPTENT3(0, "makemask",        OPT_FLAG,   NULL,
            &cmdlineP->makemask,      0);
    OPTENT3(0, "blackandwhite",   OPT_FLAG,   NULL,
            &blackandwhite,           0);
    OPTENT3(0, "grayscale",       OPT_FLAG,   NULL,
            &grayscale,               0);
    OPTENT3(0, "color",           OPT_FLAG,   NULL,
            &color,                   0);
    OPTENT3(0, "dpi",             OPT_UINT,   &cmdlineP->dpi,
            &dpiSpec,                 0);
    OPTENT3(0, "eyesep",          OPT_FLOAT,  &cmdlineP->eyesep,
            &eyesepSpec,              0);
    OPTENT3(0, "depth",           OPT_FLOAT,  &cmdlineP->depthOfField,
            &depthSpec,               0);
    OPTENT3(0, "maxval",          OPT_UINT,   &cmdlineP->maxval,
            &maxvalSpec,              0);
    OPTENT3(0, "guidesize",       OPT_INT,    &cmdlineP->guidesize,
            &guidesizeSpec,           0);
    OPTENT3(0, "magnifypat",      OPT_UINT,   &cmdlineP->magnifypat,
            &magnifypatSpec,          0);
    OPTENT3(0, "xshift",          OPT_UINT,   &cmdlineP->xshift,
            &xshiftSpec,              0);
    OPTENT3(0, "yshift",          OPT_UINT,   &cmdlineP->yshift,
            &yshiftSpec,              0);
    OPTENT3(0, "patfile",         OPT_STRING, &cmdlineP->patFilespec,
            &patfileSpec,             0);

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */

    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */


    if (blackandwhite + grayscale + color == 0)
        cmdlineP->outputType = OUTPUT_BW;
    else if (blackandwhite + grayscale + color > 1)
        pm_error("You may specify only one of -blackandwhite, -grayscale, "
                 "and -color");
    else {
        if (blackandwhite)
            cmdlineP->outputType = OUTPUT_BW;
        else if (grayscale)
            cmdlineP->outputType = OUTPUT_GRAYSCALE;
        else {
            assert(color);
            cmdlineP->outputType = OUTPUT_COLOR;
        }
    }
    if (!patfileSpec)
        cmdlineP->patFilespec = NULL;

    if (!dpiSpec)
        cmdlineP->dpi = 96;
    else if (cmdlineP->dpi < 1)
        pm_error("The argument to -dpi must be a positive integer");

    if (!eyesepSpec)
        cmdlineP->eyesep = 2.5;
    else if (cmdlineP->eyesep <= 0.0)
        pm_error("The argument to -eyesep must be a positive number");

    if (!depthSpec)
        cmdlineP->depthOfField = (1.0/3.0);
    else if (cmdlineP->depthOfField < 0.0 || cmdlineP->depthOfField > 1.0)
        pm_error("The argument to -depth must be a number from 0.0 to 1.0");

    if (!maxvalSpec)
        cmdlineP->maxval = 0;

    if (!guidesizeSpec)
        cmdlineP->guidesize = 0;

    if (!magnifypatSpec)
        cmdlineP->magnifypat = 1;
    else if (cmdlineP->magnifypat < 1)
        pm_error("The argument to -magnifypat must be a positive integer");

    if (!xshiftSpec)
        cmdlineP->xshift = 0;

    if (!yshiftSpec)
        cmdlineP->yshift = 0;

    if (xshiftSpec && !cmdlineP->patFilespec)
        pm_error("-xshift is valid only with -patfile");
    if (yshiftSpec && !cmdlineP->patFilespec)
        pm_error("-yshift is valid only with -patfile");

    if (cmdlineP->makemask && cmdlineP->patFilespec)
        pm_error("You may not specify both -makemask and -patfile");

    if (cmdlineP->patFilespec && blackandwhite)
        pm_error("-blackandwhite is not valid with -patfile");
    if (cmdlineP->patFilespec && grayscale)
        pm_error("-grayscale is not valid with -patfile");
    if (cmdlineP->patFilespec && color)
        pm_error("-color is not valid with -patfile");
    if (cmdlineP->patFilespec && maxvalSpec)
        pm_error("-maxval is not valid with -patfile");


    if (argc-1 < 1)
        cmdlineP->inputFilespec = "-";
    else if (argc-1 == 1)
        cmdlineP->inputFilespec = argv[1];
    else
        pm_error("Too many non-option arguments: %d.  Only argument is "
                 "input file name", argc-1);
}


/* Return a separation in pixels which corresponds to a 3-D distance
 * between the viewer's eyes and a point on an object. */
static int
separation(double             const  dist,
           struct cmdlineInfo const *cmdlineP) {

    int pixelEyesep = round2int(cmdlineP->eyesep*cmdlineP->dpi);
    double dof = cmdlineP->depthOfField;

    return round2int((1.0-dof*dist)*pixelEyesep/(2.0-dof*dist));
}



static struct {
    /* The state of the randomColor generator.  This is private to
       randomColor() and initRandomColor().
    */
    const struct pam * pamP;
    unsigned int magnifypat;
    tuple * currentRow;
    unsigned int prevy;
} randomState;



/* Return a random RGB value. */
static tuple
randomColor(int const x,
            int const y) {

    /* Every time we start a new row, we select a new sequence of random
       colors.
    */
    if (y/randomState.magnifypat != randomState.prevy/randomState.magnifypat) {
        int col;
        unsigned int const modulus = randomState.pamP->maxval + 1;

        for (col = 0; col < randomState.pamP->width; ++col) {
            tuple * const thisTuple =
                &randomState.currentRow[col];

            unsigned int plane;

            for (plane = 0; plane < randomState.pamP->depth; ++plane)
                (*thisTuple)[plane] = random() % modulus;
        }
    }

    /* Return the appropriate column from the pregenerated color row. */
    randomState.prevy = y;
    return randomState.currentRow[x/randomState.magnifypat];
}



static COORD2COLOR
initRandomColor(const struct pam * const pamP,
                const struct cmdlineInfo * const cmdlineP) {

    randomState.currentRow = pnm_allocpamrow(pamP);
    randomState.pamP = pamP;
    randomState.magnifypat = cmdlineP->magnifypat;
    randomState.prevy = -cmdlineP->magnifypat;

    srandom(time(NULL));
    return randomColor;
}



static struct {
    /* This is the state of the patternPixel generator.  It is
       private to functions of the patternPixel generator.
    */
    const struct pam * pamP;
    tuple **           patTuples;
    unsigned int       xshift;
    unsigned int       yshift;
    unsigned int       magnifypat;
} patternPixelState;



/* Return a pixel from the pattern file. */
static sample *
patternPixel(int const x,
             int const y) {

#define pp patternPixelState
    int patx, paty;

    paty = ((y - pp.yshift) / pp.magnifypat) % pp.pamP->height;

    if (paty < 0)
        paty += pp.pamP->height;

    patx = ((x - pp.xshift) / pp.magnifypat) % pp.pamP->width;

    if (patx < 0)
        patx += pp.pamP->width;

    return pp.patTuples[paty][patx];
#undef pp
}


static COORD2COLOR
initPatternPixel(const struct pam * const pamP,
                 tuple **           const patTuples,
                 const struct cmdlineInfo * const cmdlineP) {

    patternPixelState.pamP = pamP;
    patternPixelState.patTuples = patTuples;
    patternPixelState.xshift = cmdlineP->xshift;
    patternPixelState.yshift = cmdlineP->yshift;
    patternPixelState.magnifypat = cmdlineP->magnifypat;
    return patternPixel;
}



/* ---------------------------------------------------------------------- */


static void
makeWhiteRow(const struct pam * const pamP,
             tuple *            const tuplerow) {

    int col;

    for (col = 0; col < pamP->width; ++col) {
        unsigned int plane;
        for (plane = 0; plane < pamP->depth; ++plane)
            tuplerow[col][plane] = pamP->maxval;
    }
}



static void
writeRowCopies(const struct pam *  const outPamP,
               const tuple *       const outrow,
               unsigned int        const copyCount) {

    unsigned int i;
    for (i = 0; i < copyCount; ++i)
        pnm_writepamrow(outPamP, outrow);
}



/* Draw a pair of guide boxes. */
static void
drawguides(int                        const guidesize,
           const struct pam *         const outPamP,
           const struct cmdlineInfo * const cmdlineP) {

    int const far = separation(0, cmdlineP);
        /* Space between the two guide boxes. */
    int const width = outPamP->width;    /* Width of the output image */

    tuple *outrow;             /* One row of output data */
    tuple blackTuple;
    int col;

    pnm_createBlackTuple(outPamP, &blackTuple);

    outrow = pnm_allocpamrow(outPamP);

    /* Leave some blank rows before the guides. */
    makeWhiteRow(outPamP, outrow);
    writeRowCopies(outPamP, outrow, guidesize);

    /* Draw the guides. */
    if ((width - far + guidesize)/2 < 0 ||
        (width + far - guidesize)/2 >= width)
        pm_message("warning: the guide boxes are completely out of bounds "
                   "at %d DPI", cmdlineP->dpi);
    else if ((width - far - guidesize)/2 < 0 ||
             (width + far + guidesize)/2 >= width)
        pm_message("warning: the guide boxes are partially out of bounds "
                   "at %d DPI", cmdlineP->dpi);

    for (col = (width - far - guidesize)/2;
         col < (width - far + guidesize)/2;
         ++col)
        if (col >= 0 && col < width)
            pnm_assigntuple(outPamP, outrow[col], blackTuple);

    for (col = (width + far - guidesize)/2;
         col < (width + far + guidesize)/2;
         ++col)
        if (col >= 0 && col < width)
            pnm_assigntuple(outPamP, outrow[col], blackTuple);

    writeRowCopies(outPamP,outrow, guidesize);

    /* Leave some blank rows after the guides. */
    makeWhiteRow(outPamP, outrow);
    writeRowCopies(outPamP, outrow, guidesize);

    pnm_freerow(outrow);
}



static void
reportImageParameters(const char * const fileDesc,
                      const struct pam * const pamP) {

    pm_message("%s: tuple type \"%s\", %d wide x %d high x %d deep, maxval %lu",
               fileDesc, pamP->tuple_type, pamP->width, pamP->height,
               pamP->depth, pamP->maxval);
}


/* Do the bulk of the work.  See the paper cited above for code
 * comments.  All I (Scott) did was transcribe the code and make
 * minimal changes for Netpbm.  And some style changes by Bryan to
 * match Netpbm style.
 */
static void
makeStereoRow(tuple *                    const inRow,
              int *                      const same,
              const struct pam *         const inPamP,
              const struct cmdlineInfo * const cmdlineP) {

#define Z(X) (1.0-inRow[X][0]/(double)inPamP->maxval)

    int col;
    double dof = cmdlineP->depthOfField;
    int width = inPamP->width;
    int pixelEyesep = round2int(cmdlineP->eyesep*cmdlineP->dpi);  /* Separation in pixels between the viewer's eyes */

    for (col = 0; col < width; ++col)
        same[col] = col;

    for (col = 0; col < width; ++col) {
        int const s = separation(Z(col), cmdlineP);
        int left, right;

        left  = col - s/2;  /* initial value */
        right = left + s;   /* initial value */

        if (0 <= left && right < width) {
            int visible;
            int t;
            double zt;

            t = 1;  /* initial value */

            do {
                zt = Z(col) + 2.0*(2.0 - dof*Z(col))*t/(dof*pixelEyesep);
                visible = Z(col-t) < zt && Z(col+t) < zt;
                t++;
            } while (visible && zt < 1);
            if (visible) {
                int l;

                l = same[left];
                while (l != left && l != right)
                    if (l < right) {
                        left = l;
                        l = same[left];
                    }
                    else {
                        same[left] = right;
                        left = right;
                        l = same[left];
                        right = l;
                    }
                same[left] = right;
            }
        }
    }
}


/* Initialize the output file PAM structure either by copying the
   characteristics of the pattern file or by formatting according to
   the input file and command-line specification. */
static void
initOutputFile (struct pam * const outPamP,
                const struct cmdlineInfo * const cmdlineP,
                const struct pam * const inPamP,
                const struct pam * const patPamP) {

    if (cmdlineP->patFilespec)
        /* Blindly copy everything from the pattern file. */
        *outPamP = *patPamP;
    else {
        /* Assign all fields regarding the image type. */
        outPamP->format           = PAM_FORMAT;
        outPamP->plainformat      = 0;
        outPamP->depth            = cmdlineP->outputType==OUTPUT_COLOR ? 3 : 1;
        outPamP->bytes_per_sample = pnm_bytespersample(cmdlineP->maxval);
        switch (cmdlineP->outputType) {
        case OUTPUT_BW:
          strcpy(outPamP->tuple_type, PAM_PBM_TUPLETYPE);
          outPamP->maxval = 1;
          break;
        case OUTPUT_GRAYSCALE:
          strcpy(outPamP->tuple_type, PAM_PGM_TUPLETYPE);
          outPamP->maxval = cmdlineP->maxval ? cmdlineP->maxval : inPamP->maxval;
          break;
        case OUTPUT_COLOR:
          strcpy(outPamP->tuple_type, PAM_PPM_TUPLETYPE);
          outPamP->maxval = cmdlineP->maxval ? cmdlineP->maxval : inPamP->maxval;
          break;
        default:
          pm_error("Internal error: unrecognized output type");
          break;
        }
    }

    /* Assign all of the remaining fields. */
    outPamP->size   = sizeof(struct pam);
    outPamP->len    = PAM_STRUCT_SIZE(tuple_type);
    outPamP->file   = stdout;  /* pamstereogram currently writes only to stdout */
    outPamP->height = inPamP->height + 3*abs(cmdlineP->guidesize);   /* Allow room for guides. */
    outPamP->width  = inPamP->width;
}



static void
makeMaskRow(const struct pam * const outPamP,
            const tuple *      const outRow,
            const int *        const same) {

    int col;
    for (col = outPamP->width-1; col >= 0; --col) {
        unsigned int plane;

        for (plane = 0; plane < outPamP->depth; ++plane)
            outRow[col][plane] =
                same[col] == col ? 0 : outPamP->maxval;
    }
}



static void
makeImageRow(const struct pam * const outPamP,
             const tuple *      const outRow,
             const int *        const same,
             int                const row,
             COORD2COLOR              getTuple) {

    int col;
    for (col = outPamP->width-1; col >= 0; --col) {
        tuple const newtuple =
            same[col] == col ? getTuple(col, row) : outRow[same[col]];

        unsigned int plane;

        for (plane = 0; plane < outPamP->depth; ++plane)
            outRow[col][plane] = newtuple[plane];
    }
}



static void
invertHeightRow(const struct pam * const heightPamP,
                tuple *            const tupleRow) {

    int col;

    for (col = 0; col < heightPamP->width; ++col)
        tupleRow[col][0] = heightPamP->maxval - tupleRow[col][0];
}


static void
produceStereogram(struct cmdlineInfo *cmdlineP) {

    struct pam inPam;    /* PAM information for the height-map file */
    struct pam outPam;   /* PAM information for the stereogram file */
    struct pam patPam;   /* PAM information for the pattern file */
    int width;           /* Width in pixels of the input and output files */
    int height;          /* Height in pixels of the input and output files */
    tuple *inRow;        /* One row of pixels read from the height-map file */
    tuple *outRow;       /* One row of pixels to write to the height-map file */
    tuple **patTuples;   /* Entire image read from the pattern file */
    COORD2COLOR getTuple;  /* Map from a height-map (x,y) coordinate to a tuple */
    int *same;           /* Index of a pixel to the right forced to have the same color */
    int row;             /* Current row in the input and output files */

    /* Open the input file, the pattern file (if specified), and output
       file.  Read/write the associated PAM headers. */
    pnm_readpaminit(pm_openr(cmdlineP->inputFilespec), &inPam, sizeof(struct pam));
    if (cmdlineP->patFilespec)
        patTuples = pnm_readpam(pm_openr(cmdlineP->patFilespec), &patPam, sizeof(struct pam));
    initOutputFile(&outPam, cmdlineP, &inPam, cmdlineP->patFilespec ? &patPam : NULL);
    pnm_writepaminit(&outPam);
    if (cmdlineP->verbose) {
        reportImageParameters("Input (height map) file", &inPam);
        if (inPam.depth > 1)
            pm_message("Ignoring all but the first plane of input.");
        if (cmdlineP->patFilespec)
            reportImageParameters("Pattern file", &patPam);
        reportImageParameters("Output (stereogram) file", &outPam);
    }

    /* Allocate space for various row arrays. */
    height = inPam.height;
    width = inPam.width;
    inRow = pnm_allocpamrow(&inPam);
    outRow = pnm_allocpamrow(&outPam);
    MALLOCARRAY_NOFAIL(same, inPam.width);

    /* Specify whether the background pixels should come from the
       pattern file or be generated randomly. */
    if (cmdlineP->patFilespec) {
        initPatternPixel(&patPam, patTuples, cmdlineP);
        getTuple = patternPixel;
    }
    else {
        initRandomColor(&outPam, cmdlineP);
        getTuple = randomColor;
    }

    /* Draw guide boxes at the top, if desired. */
    if (cmdlineP->guidesize < 0)
        drawguides(-cmdlineP->guidesize, &outPam, cmdlineP);

    /* See the paper cited above for code comments.  All I did was
     * transcribe the code and make minimal changes for the PBM
     * utilities. */
    for (row=0; row<height; row++) {
        pnm_readpamrow(&inPam, inRow);
        if (cmdlineP->crosseyed)
          /* Invert heights for cross-eyed (as opposed to wall-eyed) people. */
          invertHeightRow(&inPam, inRow);

        /* Determine color constraints. */
        makeStereoRow(inRow, same, &inPam, cmdlineP);

        if (cmdlineP->makemask)
            makeMaskRow(&outPam, outRow, same);
        else
            makeImageRow(&outPam, outRow, same, row, getTuple);

        /* Write the resulting row. */
        pnm_writepamrow(&outPam, outRow);
    }

    /* Draw guide boxes at the bottom, if desired. */
    if (cmdlineP->guidesize > 0)
        drawguides(cmdlineP->guidesize, &outPam, cmdlineP);

    /* Close all open files and free all allocated memory. */
    pm_close(inPam.file);
    pm_close(outPam.file);
    free(same);
    pnm_freepamrow(outRow);
    pnm_freepamrow(inRow);
}



static void
reportParameters(struct cmdlineInfo const *cmdlineP) {

    unsigned int const pixelEyesep = round2int(cmdlineP->eyesep * cmdlineP->dpi);

    pm_message("Eye separation: %.4g inch * %d DPI = %u pixels",
               cmdlineP->eyesep, cmdlineP->dpi, pixelEyesep);

    if (cmdlineP->magnifypat > 1)
      pm_message("Background magnification: %dX * %dX", cmdlineP->magnifypat, cmdlineP->magnifypat);
    pm_message("\"Optimal\" pattern width: %u / (%d * 2) = %u pixels",
               pixelEyesep, cmdlineP->magnifypat,
               pixelEyesep/(cmdlineP->magnifypat*2));
    pm_message("Unique 3-D depth levels supported: %d",
               separation(0, cmdlineP)-separation(1, cmdlineP)+1);
    if (cmdlineP->patFilespec && (cmdlineP->xshift || cmdlineP->yshift))
        pm_message("Pattern shift: (%d, %d)",
                   cmdlineP->xshift, cmdlineP->yshift);
}


int
main(int argc, char *argv[]) {

  struct cmdlineInfo cmdline;      /* Parsed command line */

  /* Parse the command line. */
  pnm_init(&argc, argv);
  parseCommandLine(argc, argv, &cmdline);
  if (cmdline.verbose)
    reportParameters(&cmdline);

  /* Produce a stereogram. */
  produceStereogram(&cmdline);

  return 0;
}

