/*
 *  PTcommon.c
 *
 *  Many of the routines are based on the program PTStitcher by Helmut
 *  Dersch.
 * 
 *  Copyright Helmut Dersch and Daniel M. German
 *  
 *  Dec 2005
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this software; see the file COPYING.  If not, a copy
 *  can be downloaded from http://www.gnu.org/licenses/gpl.html, or
 *  obtained by writing to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *
 *  Author: Daniel M German dmgerman at uvic doooot ca
 * 
 */

#include "filter.h"
#include "PTcommon.h"
#include "ColourBrightness.h"
#include "pttiff.h"
#include "file.h"
#include "PTcommon.h"
#include "ptstitch.h" 

#include <assert.h>
#include <float.h>
#include <math.h>

//#include <stdio.h>
//#include <stdlib.h>
//#include <sys/types.h>
//#include <dirent.h>
//#include <unistd.h>
//#include <stdint.h>
//#include <math.h>

#ifndef _MSC_VER
#include <unistd.h>
#else
#if _MSC_VER<1900
#define isnan _isnan
#endif
#include "tools/compat_win32/getopt.h"
#endif


// Uncomment following line to enable testing of inverses in getROI
//#define PANO_TEST_INVERSE


int panoFlattenTIFF(fullPath * fullPathImages, int counterImageFiles,
                    fullPath * outputFileName, int removeOriginals);


//declare functions
void getCropInformationFromTiff(TIFF * tif, CropInfo * c);
void getROI(TrformStr * TrPtr, aPrefs * aP, PTRect * ROIRect);

int ptQuietFlag = 0;

void InsertFileName(fullPath * fp, char *fname)
{
    char *c = strrchr((char *) (fp->name), PATH_SEP);
    if (c != NULL)
        c++;
    else
        c = fp->name;
    strcpy(c, fname);
}


int panoPSDCreate(fullPath * fullPathImages, int numberImages,
                  fullPath * outputFileName, pano_flattening_parms *flatteningParms)
{
    Image *ptrImage;
    int i;
    stBuf stitchInfo;
    fullPath tempFile;
    char tempString[128];
    Image image;
    Boolean bBig = FALSE;


    assert(numberImages > 0);
    assert(fullPathImages != NULL);
    assert(outputFileName != NULL);

    if (ptQuietFlag == 0) {
        Progress(_initProgress, "Converting TIFF to PSD");
        snprintf(tempString, sizeof(tempString)-1, "%d", 100 / numberImages);
        Progress(_setProgress, tempString);
    }

    // Process background of PSD
    SetImageDefaults(&image);

    if (panoTiffRead(&image, fullPathImages[0].name) == 0) {

      PrintError("Could not read TIFF image No 0 %s", fullPathImages[0].name);
        if (ptQuietFlag == 0)
            Progress(_disposeProgress, tempString);

        return -1;
    }

    // Check to see if we need to create PSB instead of PSD file
    if(image.height > 30000 || image.width > 30000 || flatteningParms->forceBig == 1)
      bBig = TRUE;

    if (!(image.bitsPerPixel == 64 || image.bitsPerPixel == 32)) {
        PrintError("Image type not supported (%d bits per pixel)\n",
                   image.bitsPerPixel);
        return -1;
    }

    // New versions of Photoshop can handle multilayer 16bit files
    // Add an option to down sample to 8bit only if user request
    if (numberImages > 1 && image.bitsPerPixel != 32) {
        if (image.bitsPerPixel == 64 && flatteningParms->force8bit == 1) {
            //PrintError
            //    ("Panotools is not able to save 16bit PSD images. Downsampling to 8 bit");
            TwoToOneByte(&image);       //we need to downsample to 8 bit if we are provided 16 bit images
        }
    }

    if (numberImages == 1) {
        if (writePS(&image, outputFileName, bBig) != 0) {
            PrintError("Could not write PSD-file");
            if (ptQuietFlag != 0)
            Progress(_disposeProgress, tempString);
            return -1;
        }
        return 0;
    }

    //////////////////////////////////////////////////////////////////////
    //current algorithm is O(n2) which means it will take _forever_ to  create 
    // a huge Photoshop files with many layers.

    //Write out the first image as the base layer in the PSD file

    if (writePSwithLayer(&image, outputFileName,bBig) != 0) {
        PrintError("Could not write PSD-file");
        if (ptQuietFlag != 0)
            Progress(_disposeProgress, tempString);
        return -1;
    }
    panoImageDispose(&image);

    ptrImage = &image;

    //Now iterate over all other images and add them as layers to the PSD file
    for (i = 1; i < numberImages; i++) {

        if (ptQuietFlag == 0) {
            snprintf(tempString, sizeof(tempString)-1, "%d", i * 100 / numberImages);
            if (Progress(_setProgress, tempString) == 0) {
                remove(outputFileName->name);
                return -1;
            }
        }

        if (panoTiffRead(ptrImage, fullPathImages[i].name) == 0) {

            PrintError("Could not read TIFF image No &d", i);
            if (ptQuietFlag == 0)
                Progress(_disposeProgress, tempString);
            return -1;
        }

        // We can't process 16 bit TIFFs. We have to downsample to 8 bit if necessary
        if (image.bitsPerPixel == 64 && flatteningParms->force8bit == 1)
            TwoToOneByte(ptrImage);

        // Create a new file with the result PSD, then delete the current one

        strcpy(tempFile.name, outputFileName->name);

        if (panoFileMakeTemp(&tempFile) == 0) {
            PrintError("Could not make Tempfile");
            return -1;

        }

        stitchInfo.seam = 1;
        stitchInfo.feather = 0;
        if (flatteningParms->stacked) 
          stitchInfo.psdOpacity = (unsigned char) (255.0/ (i + 1));
        else
          stitchInfo.psdOpacity = 255;

        stitchInfo.psdBlendingMode = flatteningParms->psdBlendingMode;

        if (addLayerToFile(ptrImage, outputFileName, &tempFile, &stitchInfo) != 0) {
            PrintError("Could not write Panorama File");
            return -1;
        }

        remove(outputFileName->name);
        rename(tempFile.name, outputFileName->name);

        panoImageDispose(ptrImage);
    }

    if (!ptQuietFlag) {
        Progress(_setProgress, "100");
        Progress(_disposeProgress, tempString);
    }

    return 0;
}



#ifdef __Win__
//void InsertFileName( fullPath *fp, char *fname ){
// char *c = strrchr((char*)(fp->name), PATH_SEP);
// if(c != NULL) c++;
// else c = fp->name;
// strcpy( c, fname );
//}   
#endif


void ARGtoRGBAImage(Image * im)
{
    int right;
    int left;
    int bottom;
    int top;
    int width;
    int i;


    if (im->selection.bottom == 0 && im->selection.right == 0) {

        top = 0;
        left = 0;
        bottom = im->height;
        right = im->width;


    }
    else {

        top = im->selection.top;
        bottom = im->selection.bottom;
        left = im->selection.left;
        right = im->selection.right;
    }

    width = right - left;

    //fprintf(stderr, "\nWidth %10d Top: %10d bottom %10d Right %10d Left %10d-------", width, top, bottom, right, left);

    assert(width >= 0);
    assert(bottom >= top);

    for (i = 0; i < bottom - top; i++) {

        ARGBtoRGBA(*(im->data) + i * im->bytesPerLine, width,
                   im->bitsPerPixel);

    }                           // for 

}



void Clear_Area_Outside_Selected_Region(Image * image)
{
    // This function clears (i.e. sets to zero) the area outside the 
    // selection region 

    int right;
    int left;
    int bottom;
    int top;
    //  int width;
    //  int var24;
    int bytesPerPixel;          // 32
    unsigned char *dataPtr;
    unsigned char *pixelPtr;

    int currentRow;
    int currentColumn;

    // Only works for 8/16 bit per channel (32/64 bits per pixel) images
    assert(image->bitsPerPixel == 32 || image->bitsPerPixel == 64);

    top = image->selection.top;
    bottom = image->selection.bottom;
    left = image->selection.left;
    right = image->selection.right;

    if (bottom == 0)
        bottom = image->height;

    if (right == 0)
        right = image->width;

    if (image->bitsPerPixel == 32) {
        bytesPerPixel = 4;
    }
    else if (image->bitsPerPixel == 64) {
        bytesPerPixel = 8;
    }
    else {
        PrintError("Invalid bits per pixel in image %d", image->bitsPerPixel);
        exit(1);
    }


    if (image->format == _fisheye_circ || image->format == _thoby) {

        // TODO
        // This routine works only in fisheyes in portrait mode
        // it probably fails in landscape mode
    
        int horCenter, verCenter;
        int horRadious;
        int horRadious2;

        horCenter = (left + right) / 2;
        verCenter = (top + bottom) / 2;

        // Compute the horizontal width divided by 2, 
        // let us call it horizontal radios

        horRadious = (right - left) / 2;
        // Square it, so we don't have to compute this 
        // every time
        horRadious2 = horRadious * horRadious;

        dataPtr = *(image->data);

        // Scan the image from top to bottom
        for (currentRow = 0; currentRow < image->height; currentRow++) {
            int verDistance;
            int verDistance2;

            // The algorith it simple. Find the distance of each from from the 
            // center of the image. If the point is farther than horRadious
            // then set mask to zero

            currentColumn = 0;
            pixelPtr = dataPtr;

            // Compute the square of the vertical distance to this row from center
            verDistance = (currentRow - verCenter);
            verDistance2 = verDistance * verDistance;

            for (currentColumn = 0; currentColumn < image->width; currentColumn ++) {
                int horDistance;
                int horDistance2;

                // Compute square of distance of this point to center 
                // the old Pythagoras way
                // distance^2 = horDistance^2  + verDistance^2

                horDistance = (currentColumn - horCenter);
                horDistance2 = horDistance * horDistance;

                if (horDistance2 + verDistance2 > horRadious2) {

                    // Point falls outside the circle defined its horizontal maximum distance

                    // Set mask to zero
                    if (bytesPerPixel == 4)
                        *pixelPtr = 0;     
                    else if (bytesPerPixel == 8) {
                        *pixelPtr = 0;     
                        *(pixelPtr+1) = 0;     
                    }

                }
                pixelPtr +=  bytesPerPixel;

            } // for column
            dataPtr += image->bytesPerLine;
        }  // for row
        return;
    }



    // Clear the area at above the image
    dataPtr = *(image->data);

    for (currentRow = 0; currentRow < top; currentRow++) {
        pixelPtr = dataPtr;

        for (currentColumn = 0; currentColumn < image->width; currentColumn++) {
            assert(sizeof(int) == bytesPerPixel);
            memset(pixelPtr, 0, bytesPerPixel);
            pixelPtr += bytesPerPixel;
        }

        dataPtr += image->bytesPerLine;
    }

    // Clear area below the picture
    dataPtr = bottom * image->bytesPerLine + *(image->data);

    for (currentRow = bottom; currentRow < image->height; currentRow++) {
        pixelPtr = dataPtr;
        for (currentColumn = 0; currentColumn < image->width; currentColumn++) {
            memset(pixelPtr, 0, bytesPerPixel);
            pixelPtr += bytesPerPixel;
        }

        dataPtr += image->bytesPerLine;

    }                           //  for (    ;  %currentColumn < image->width ; currentColumn++,pixelPtr += bytesPerPixel) {


    /* Clear the area to the left of the picture */

    dataPtr = *(image->data);
    for (currentRow = 0; currentRow < image->height; currentRow++) {

        pixelPtr = dataPtr;
        for (currentColumn = 0; currentColumn < left; currentColumn++) {
            memset(pixelPtr, 0, bytesPerPixel);
            pixelPtr += bytesPerPixel;
        }

        dataPtr += image->bytesPerLine;
    }

    /* Clear the area to the right of the picture */

    dataPtr = *(image->data);

    for (currentRow = 0; currentRow < image->height; currentRow++) {

        pixelPtr = dataPtr + bytesPerPixel * right;

        for (currentColumn = right; currentColumn < image->width;
             currentColumn++) {

            memset(pixelPtr, 0, bytesPerPixel);

            pixelPtr += bytesPerPixel;

        }

        dataPtr += image->bytesPerLine;

    }

    return;

}


/**
 * This function computes the minimal rectangle needed to encompass
 * the region of the output image (TrPtr->dest) that will be populated with 
 * data from the input image (TrPtr->src) using the options specified
 * in aP.  The ROIRect is populated with the left/right/top/bottom values
 * that define this ROI within the output image
 */
void getROI(TrformStr * TrPtr, aPrefs * aP, PTRect * ROIRect)
{
    struct MakeParams mpinv;
    fDesc invstack[15], finvD;
    struct MakeParams mp;
    fDesc stack[15], fD;
    int color = 0;

    int x, y, x_jump;
    double x_d, y_d;            // Cartesian Coordinates of point in source (i.e. input) image
    double Dx, Dy;              // Coordinates of corresponding point in destination (i.e. output) image
    double Dx2, Dy2;            // Coordinates of corresponding point in destination (i.e. output) image

    double w2 = (double) TrPtr->dest->width / 2.0 - 0.5;        //half destination image width
    double h2 = (double) TrPtr->dest->height / 2.0 - 0.5;       //half destination image height
    double sw2 = (double) TrPtr->src->width / 2.0 - 0.5;        //half source image width
    double sh2 = (double) TrPtr->src->height / 2.0 - 0.5;       //half source image height

    //Set initial values for ROI to be adjusted during this function
    ROIRect->left = TrPtr->dest->width - 1;
    ROIRect->right = 0;
    ROIRect->top = TrPtr->dest->height - 1;
    ROIRect->bottom = 0;

    //The "forward" transform allows us to map pixel
    //coordinates in the output image to their location in the source image.
    // We use it to test the inverse functions of libpano
    printf("-------------------------------------------\n");
    SetMakeParams( stack, &mp, &(aP->im) , &(aP->pano), color );
    fD.func = execute_stack_new; 
    fD.param = stack;

    //The "inverse" transform allows us to map pixel coordinates in each source image
    //to their location in the output image.
    //    printf("INV-------------------------------------------\n");
    SetInvMakeParams(invstack, &mpinv, &(aP->im), &(aP->pano), color);

    //    printf("-------------------------------------------\n");
    finvD.func = execute_stack_new;
    finvD.param = invstack;

    //iterate over edges of input image and compute left/right/top/bottom-most coordinate
    //in output image
    //For equirectangular output projection covering 360/180, iterating over the 
    //edges of each input image isn't sufficient to determine ROI because an 
    //an interior point in an input image can be at the edge of ROI.  More research 
    //needed here, but for now include some representative interior points as well.
    for (y = 0; y <= TrPtr->src->height; y += 1) {
        
        x_jump = (y==0 || y==TrPtr->src->height || abs(y - TrPtr->src->height/2)<=5) ? 1 : TrPtr->src->width/2; 

                for (x = 0; x <= TrPtr->src->width; x += x_jump) {
                        //convert source coordinates to cartesian coordinates (i.e. origin at center of image)
                        x_d = (double) x - sw2 ;
                        y_d = (double) y - sh2 ;

                        //Map the source image cartesian coordinate to the destination image cartesian coordinate
                        finvD.func( x_d, y_d, &Dx, &Dy, finvD.param);
                        
#ifdef PANO_TEST_INVERSE
                        fD.func( Dx, Dy, &Dx2, &Dy2, fD.param);
                        {
                            int newX, newY;
                            if (!isnan(Dx2) && !isnan(Dy2)) {

                                newX = (int)(Dx2 + 0.5 + sw2);
                                newY = (int)(Dy2 + 0.5 + sh2);

                                #ifdef XXXXX
                                if (newX != x || newY != y) {
                                    printf("  X,Y: %7.1f,%7.1f (%5d,%5d) -> OUT: %9.1f, %9.1f inv -> %9.1f %9.1f (%5d, %5d) -- error %5d,%5d\n", 
                                           x_d, y_d, 
                                           x,y,
                                           Dx, Dy, 
                                           Dx2, Dy2, 
                                           newX, newY,
                                           newX - x,
                                           newY - y
                                           );
                                }
                                #endif
                                // If this assertion fails, there is an error. The question is, how big? See the values above.
                                // it is possible that the error is so small that it does not matter.
/*
                                assert(fabs(newX-x) <= 20.0);
                                assert(fabs(newY-y) <= 20.0);
*/
                            }

                        }
                          //printf("  IN1: %f,%f -> OUT: %f, %f inv -> %f %f    (%d, %d)\n", x_d, y_d, Dx, Dy, Dx2, Dy2, (int)(Dx +0.5), (int)(Dy+0.5));
                          //printf("  IN3: %d,%d -> OUT: %f, %f   (%d, %d)\n", x, y, Dx, Dy, (int)(Dx +0.5), (int)(Dy+0.5));
#endif
                        
                        //Convert destination cartesian coordinate back to destination "screen" coordinates (i.e. origin at top left of image)
                        Dx += w2;
                        Dy += h2;

                        //Expand ROI if necessary
                        //I've observed that in some cases, the mapping function returns
                        //a value of "-1.#IND00".  This is not a number, and probably indicates
                        //a divide by zero error somewhere in the mapping function.  This should
                        //be solved, but, for now, discard this value and keep going
                        if (!isnan(Dx)) {
                            if ((int32_t)Dx < ROIRect->left) ROIRect->left = (int32_t)(Dx + 0.5);
                            if ((int32_t)Dx > ROIRect->right) ROIRect->right = (int32_t)(Dx + 0.5);
                        }
                        if (!isnan(Dy)){                
                            if ((int32_t)Dy < ROIRect->top) ROIRect->top = (int32_t)(Dy + 0.5);
                            if ((int32_t)Dy > ROIRect->bottom) ROIRect->bottom = (int32_t)(Dy + 0.5);
                        }
                }
        }
        
    // printf("ROI1: %ld,%ld - %ld, %ld\n", ROIRect->left, ROIRect->top, ROIRect->right, ROIRect->bottom);

        // If the destination is wrapped and the ROI is at least 95% of the width
        // Then ensure it is of the full width so no missing data is removed from either side.
        if ( (TrPtr->mode & _wrapX) &&
             ( (ROIRect->right-ROIRect->left) > (TrPtr->dest->width * 0.95) ) )
        {
          ROIRect->left  = 0;
          ROIRect->right = TrPtr->dest->width-1;
        }

        // Test for a zenith shot
        if(ROIRect->top != 0)
        {
          Dx = 0;
          Dy = - h2;
          fD.func( Dx, Dy, &Dx2, &Dy2, fD.param);
          {
            if (!isnan(Dx2) && !isnan(Dy2)) {

              x_d = (int)(Dx2 + 0.5 + sw2);
              y_d = (int)(Dy2 + 0.5 + sh2);
              // if x_d, y_d is inside the image then this is a zenith shot and should extent to the top
              if (y_d >= 0 && y_d <= TrPtr->src->height &&
                  x_d >= 0 && x_d <= TrPtr->src->width )
              {
                ROIRect->top = 0;
              }
            }
          }
        }

        // Test for a nadir shot
        if(ROIRect->bottom != TrPtr->dest->height-1)
        {
          Dx = 0;
          Dy = h2;
          fD.func( Dx, Dy, &Dx2, &Dy2, fD.param);
          {
            if (!isnan(Dx2) && !isnan(Dy2)) {

              x_d = (int)(Dx2 + 0.5 + sw2);
              y_d = (int)(Dy2 + 0.5 + sh2);
              // if x_d, y_d is inside the image then this is a zenith shot and should extent to the top
              if (y_d >= 0 && y_d <= TrPtr->src->height &&
                  x_d >= 0 && x_d <= TrPtr->src->width )
              {
                ROIRect->bottom = TrPtr->dest->height-1;
              }
            }
          }
        }

        //Reduce ROI if it extends beyond boundaries of final panorama region
        if (ROIRect->left    < 0) ROIRect->left =0;
        if (ROIRect->top     < 0) ROIRect->top  =0;
        if (ROIRect->right   > (TrPtr->dest->width-1))  ROIRect->right    = TrPtr->dest->width-1;  
        if (ROIRect->bottom  > (TrPtr->dest->height-1)) ROIRect->bottom   = TrPtr->dest->height-1;
        
        //        printf("ROI2: %ld,%ld - %ld, %ld\n", ROIRect->left, ROIRect->top, ROIRect->right, ROIRect->bottom);
}


// NO LONGER NEEDED

/**
 * Populates the CropInfo struct with data about cropping of 
 * the TIFF file specified by filename
 */
void getCropInformation(char *filename, CropInfo * c)
{

    TIFF *tif = TIFFOpen(filename, "r");
    if (tif == NULL) {
        PrintError("getCropInformation: Could not open TIFF file");
    }
    else {
        getCropInformationFromTiff(tif, c);
        TIFFClose(tif);
    }

}


#if 0 

void setFullSizeImageParameters(pt_tiff_parms * imageParameters,
                                CropInfo * crop_info)
{
    // Update the imageParameters so that the dimensions reflect the
    // the size of the full-sized output image, (recorded in the crop_info struct)
    imageParameters->imageLength = crop_info->full_height;
    imageParameters->imageWidth = crop_info->full_width;
    imageParameters->bytesPerLine =
        imageParameters->imageWidth * (imageParameters->bitsPerPixel / 8);
}

#endif


int panoCreatePanorama(fullPath ptrImageFileNames[], int counterImageFiles,
                       fullPath * panoFileName, fullPath * scriptFileName)
{

    Image *currentImagePtr;
    aPrefs *prefs;
    int var01;
    int var00;
    int colourCorrection;

    int lines;
    fullPath *fullPathImages;
    int loopCounter;
    char var40[8];
    char *tempString;           // It looks like a char *temp;          
    char outputFileName[512];
#if 0
    VRPanoOptions defaultVRPanoOptions;
#endif
    char tmpStr[64];            // string
    fullPath currentFullPath;
    fullPath panoName;          // according to documention: QTVR, PNG, PICT, TIFF, etc plus options...*/
    fullPath tempScriptFile;
    char output_file_format[256];
    Image resultPanorama;       //Output Image
    Image image1;               //Input Image

    FILE *regFile;
    char *regScript;
    unsigned int regLen;
    unsigned int regWritten;

    pano_Tiff *tiffFile;             //Output file...will be written during this function
    TrformStr transform;        //structure holds pointers to input and output images and misc other info

    int ebx;

    int croppedTIFFIntermediate = 0;
    int croppedWidth = 0, croppedHeight = 0;
    PTRect ROIRect;
    unsigned int outputScanlineNumber = 0;

    pano_ImageMetadata metadata;

    /* Variables */
    colourCorrection = 0;       // can have values of 1 2 or 3
    var00 = 0;
    var01 = 0;

    //Copy script line for line into a new temporary file
    memcpy(&tempScriptFile, scriptFileName, sizeof(fullPath));
    if (panoFileMakeTemp(&tempScriptFile) == 0) {
        PrintError("Unable to create temporary file");
        goto mainError;
    }

    panoTiffSetErrorHandler();

    if ((regFile = fopen(tempScriptFile.name, "w")) == NULL) {
        PrintError("Could not open temporary Scriptfile");
        goto mainError;
    }

    if ((regScript = LoadScript(scriptFileName)) == 0) {
        PrintError("Could not load ScriptFile");
        fclose(regFile);
        goto mainError;
    }

    regLen = strlen(regScript);

    // Write script to temp file
    regWritten = fwrite(regScript, 1, regLen, regFile);

    // Make sure script was written completely
    if (regWritten != strlen(regScript)) {
        PrintError("Could not write temporary script");
        fclose(regFile);
        goto mainError;
    }
    
    fclose(regFile);

    //Initialize members to zero
    SetImageDefaults(&image1);
    SetImageDefaults(&resultPanorama);

    //transform structure holds input and output images, and some miscellaneous other information
    memset(&transform, 0, sizeof(TrformStr));
    transform.src = &image1;    // Input image
    transform.dest = &resultPanorama;   // Output image
    transform.mode = _honor_valid;      // How to run transformation
    transform.success = 1;      // 1 success 0 failure

    //Allocate space to hold fully qualified names of input images
    if ((fullPathImages = malloc(counterImageFiles * 512)) == NULL) {
        PrintError("Not enough memory");
        goto mainError;
    }

    // This is the main processing loop...it iterates over each input image
    // and maps the pixels in these input images into the output image(s)
    for (loopCounter = 0; loopCounter < counterImageFiles; loopCounter++) {

        // TODO 

        // the original PTstitcher logic  is strange

        // It processes a lot of data more than once. This part should really be done once for all images


        currentImagePtr = &image1;

        // Read the next adjust line (contains yaw, pitch, roll and other information)
        // for one input image from the script file
        if ((prefs = readAdjustLine(&tempScriptFile)) == 0) {
            PrintError("Could not read Scriptfile");
            goto mainError;
        }

        // printf("*********cut frame: should be zero for S and no crop %d\n",  prefs->im.cP.cutFrame);

        //New for PTMender...PTMender uses "cropped" TIFFs as its intermediate file 
        //format for all processing.  In contrast, PTStitcher used full-size TIFF
        //images for all intermediate processing.  PTMender can still write "uncropped" 
        //final as needed.
        //
        //To the end user, PTMender appears to behave similarly to PTStitcher for 
        //TIFF_m and TIFF_mask formats, outputting a one "full-size" TIFF for each 
        //layer.  However, the internal processing is done on cropped TIFFs which
        //speeds things up considerably.
        //
        //An important improvement over PTStitcher is that the user can also explicitly 
        //requests cropped output for multi layer TIFF output by inlcluding 
        //"r:CROP" as part of the "p" line (e.g. n"TIFF_m r:CROP")
        //
        //Using cropped TIFF as the intermediate format significantly speeds up 
        //processing, with larger panos showing more dramatic increases in speed.  
        //It should also mean that the creation of the "flattened" formats will 
        //be significantly more memory-friendly, as the masking steps and PSD 
        //assembly steps won't need to load images the size of the output file 
        //into memory at once.  Unless the PTMender is fed extremely large input 
        //images, all memory constraints should now be a thing of the past (MRDL - May 2006).

        //croppedTIFFIntermediate determines if all intermediate processing is done
        //with cropped or full size TIFF.  There probably isn't much of a reason
        //to ever disable this feature, other than for testing/debugging purposes.



        colourCorrection = prefs->sBuf.colcorrect;
        // This is a strange value:
        // colourCorrection == (i & 3) + (i+1)*4;
        // where i is the number of the reference image
        
        assert(colourCorrection >= 0
               && colourCorrection < (counterImageFiles + 1) * 4);
        if (prefs->pano.cP.radial != 0) {
            
            var00 = prefs->pano.cP.radial_params[0][2]; // what is this for, I have NO idea.
            var00++;

        }                       // begins 804a00e


        if (prefs->pano.cP.horizontal != 0) {

          // Colour correction in color only, not brightness

            var01 = prefs->pano.cP.horizontal_params[0];        // 0x75c //[3] 3 colours x horizontal shift value
            // at this point var01 contains the index to the image to use as anchor fo

            var01++;

        }

        // Copy output pano name to panoName
        memcpy(&panoName, &prefs->pano.name, sizeof(fullPath));
        //memcpy(&global5640, &prefs->sBuf, sizeof(stBuf));
        
        //panoName.name contains the n"XXX" value from the script "p" lines (e.g. n"TIFF_m" or n"QTVR w400 h300 c1")
        tempString = panoName.name;
        --tempString;           /* nextWord does ++ before testing anything, this guarantess proper execution */
        nextWord(output_file_format, &tempString);
        
        if (strcmp(output_file_format, "TIFF_m") == 0 ) {
            // CHeck if we are suppose to do cropped or uncropped
            croppedTIFFIntermediate = 1;
            if(strcmp(tempString, "") != 0) {
              nextWord(output_file_format, &tempString);
              if (strcmp(output_file_format, "") == 0 || 
                  strcmp(output_file_format, "r:CROP") == 0
                  ) {
                  // DO uncropped by default unless we are fisheye
                  if (prefs->im.format == _fisheye_circ) {
                      PrintError("Cropped output is unsupported for circular fisheye lenses. Ignored");
                  } 
              } else if (strcmp(output_file_format, "r:UNCROP") == 0) {
                  croppedTIFFIntermediate = 0;
              } else {
                  PrintError("Unsupported option in TIFF_m output (%s). Ignored", output_file_format);
              }
            }
            // DO uncropped by default unless we are fisheye
            if (prefs->im.format == _fisheye_circ) {
                croppedTIFFIntermediate = 0;
            } 
        } else {
            PrintError("No support for this output image format (%s). Output will be TIFF_m", output_file_format);
        }
        // enable this to avoid cropped tiffs. usually for testing
        //croppedTIFFIntermediate = 0;



        transform.interpolator  = prefs->interpolator;
        transform.gamma         = prefs->gamma;
        transform.fastStep      = prefs->fastStep;
        if( prefs->pano.hfov == 360.0 )
          transform.mode       |= _wrapX;
        
        if (ptQuietFlag == 0) {
            snprintf(tmpStr, sizeof(tmpStr)-1, "Converting Image %d / %d", (loopCounter + 1),
                    counterImageFiles);
            Progress(_initProgress, tmpStr);
        }

        //Read input image into transform.src
        if (panoImageRead(currentImagePtr, &ptrImageFileNames[loopCounter]) == 0) {
            PrintError("Could not read input image [%s]", ptrImageFileNames[loopCounter].name);
            goto mainError;
        }

        // printf("Ended reading INPUT image\n");

        //This "masks" the input image so that some pixels are excluded from 
        //transformation routine during pixel remapping/interpolation 

        if (prefs->im.cP.cutFrame != 0) {       // remove frame? 0 - no; 1 - yes
            // THIS CODE is executed in crop C type only, but not in S type
            //    printf("To crop image\n");
            if (CropImage(currentImagePtr, &(prefs->im.selection)) == 0) {
                prefs->im.selection.left = 0;
                prefs->im.selection.right = 0;
                prefs->im.selection.bottom = 0;
                prefs->im.selection.top = 0;
            }
        }
        //setup width/height of input image
        prefs->im.width = image1.width;
        prefs->im.height = image1.height;

        //Try to set reasonable values for output pano width and/or height if not 
        //specified as part of input (Do this only when processing first image in script)
        if (loopCounter == 0) {

            if (prefs->pano.width == 0) {
                // if the pano did not set the width, then try to set it
                if (prefs->im.hfov != 0.0) {
                    prefs->pano.width = prefs->im.width * prefs->pano.hfov / prefs->im.hfov;
                    prefs->pano.width /= 10;    // Round to multiple of 10
                    prefs->pano.width *= 10;
                }
            }

            if (prefs->pano.height == 0)
                prefs->pano.height = prefs->pano.width / 2;
            
            resultPanorama.height = prefs->pano.height;
            resultPanorama.width = prefs->pano.width;
            
            if (resultPanorama.height == 0 || resultPanorama.width == 0) {
                PrintError("Please set Panorama width/height");
                goto mainError;
            }
        }                       //End attempt at setting reasonable values for pano width/height
        
        
        //printf("to set metadata\n");
        
        //////////////////////////////////////////////////////////////////////
        // Set metadata for output file

        panoMetadataCopy(&metadata, &image1.metadata);
        
        // The size of the image will change, so we have to update all the
        // fields accordingly.
        panoMetadataResetSize(&metadata,
                              resultPanorama.width,
                              resultPanorama.height);

        metadata.imageNumber = loopCounter;
        metadata.imageTotalNumber = counterImageFiles;
        metadata.imageDescription = strdup(regScript);


        // Set output width/height for output file 
        if (croppedTIFFIntermediate) {
            getROI(&transform, prefs, &ROIRect);
            //Dimensions determine size of TIFF file
            croppedWidth = (ROIRect.right - ROIRect.left) + 1;
            croppedHeight = (ROIRect.bottom - ROIRect.top) + 1;
            
            panoMetadataSetAsCropped(&metadata, 
                                     croppedWidth, croppedHeight,
                                     ROIRect.left, ROIRect.top);
        }
        
        panoMetadataSetCompression(&metadata, prefs->pano.name);

        //The resultPanorama.selection determines which region of the output image
        //is iterated over during the main pixel-remapping processing logic.  Much
        //of the image will be empty (black space) for any given input image.  However,
        //if cropped output is selected, then only the region of interest (ROI) into
        //which this input image will be mapped is processed...this significantly
        //speeds up processing
        if (croppedTIFFIntermediate) {
            resultPanorama.selection.left = ROIRect.left;
            resultPanorama.selection.right = ROIRect.right + 1; // the right edge is actually the pixel NOT in the pano
            resultPanorama.selection.top = ROIRect.top;
        }
        else {
            resultPanorama.selection.left = 0;
            resultPanorama.selection.right = resultPanorama.width;
            resultPanorama.selection.top = 0;
        }

        resultPanorama.bitsPerPixel = image1.bitsPerPixel;
        resultPanorama.bytesPerLine = metadata.bytesPerLine;

        panoMetadataCopy(&resultPanorama.metadata, &metadata);

        panoMetadataFree(&metadata);

        //////End of set metadata


        ///  CREATE OUTPUT FILE

        // Copy the current output file name to he fullPathImages[loopCounter]
        memcpy(&fullPathImages[loopCounter], panoFileName, sizeof(fullPath));

        // Create temporary file where output data wil be written
        if (panoFileMakeTemp(&fullPathImages[loopCounter]) == 0) {
            PrintError("Could not make Tempfile");
            goto mainError;
        }

        // Populate currentFullPath.name with output file name
        GetFullPath(&fullPathImages[loopCounter], currentFullPath.name);

        // Open up output file for writing...data will be written in TIFF format

        if ((tiffFile = panoTiffCreate(currentFullPath.name, 
                       &resultPanorama.metadata)) == 0) {
            PrintError("Could not open %s for writing", currentFullPath.name);
            goto mainError;
        }

        if (ptQuietFlag == 0) {
            if (Progress(_setProgress, "5") == 0) {
                panoTiffClose(tiffFile);
                remove(fullPathImages[loopCounter].name);
                return (-1);
            }
        }

        //The output image is generated a few lines at a time to make efficient use
        //of limited memory...compute a reasonable number of lines to process (must
        //be at least 1, but no more than output height)
        lines = 500000 / resultPanorama.bytesPerLine;

        if (lines == 0)
            lines = 1;

        //Don't process more lines than are available
        if (lines >
            (croppedTIFFIntermediate ? croppedHeight : resultPanorama.height))
            lines =
                (croppedTIFFIntermediate ? croppedHeight : resultPanorama.
                 height);

        if ((resultPanorama.data =
             (unsigned char **) mymalloc(lines *
                                         resultPanorama.bytesPerLine)) ==
            NULL) {
            PrintError("Not enough memory for output panorama buffer");
            exit(1);
        }
        //NB resultPanorama.selection.bottom is actually one pixel beyond last row with data.
        resultPanorama.selection.bottom =
            resultPanorama.selection.top + lines;

        //    printf("bits per pixel %d\n", resultPanorama.bitsPerPixel);
        //    printf("cropped %d\n", croppedTIFFIntermediate);

        if (resultPanorama.bitsPerPixel != image1.bitsPerPixel) {
            PrintError
                ("All source images must have the same number of bits per pixel.");
            exit(1);
        }

        //Copy all position related data (yaw, pitch, roll, etc) for input image to currentImagePtr
        CopyPosition(currentImagePtr, &(prefs->im));

        //image1.selection determines how much of the input image to be 
        //included during main pixel remapping logic
        image1.selection.top = prefs->im.selection.top;
        image1.selection.bottom = prefs->im.selection.bottom;
        image1.selection.left = prefs->im.selection.left;
        image1.selection.right = prefs->im.selection.right;

    /*
        printf("****** Image selection hfov %f, %d %d %d %d \n", image1.hfov, image1.selection.top,
               image1.selection.bottom,
               image1.selection.left,
               image1.selection.right);
    */

        CopyPosition(&resultPanorama, &(prefs->pano));

        //Set image data outside selection region to zeros

        Clear_Area_Outside_Selected_Region(currentImagePtr);

        //pano.width and height must be equal to the full canvas size (not the 
        //size of the cropped output image...if selected) in order for the pixel 
        //remapping logic to work correctly.
        prefs->pano.width = resultPanorama.width;
        prefs->pano.height = resultPanorama.height;

        //Iterate over the output image multiple lines at a time, remapping pixels
        //from the input image into the output image, and writing data to an
        //output TIFF file.  Finish iterating when we reach the bottom of the 
        //output image (or, in the case of a cropped file, the bottom of the 
        //output ROI).
        outputScanlineNumber = 0;
        while (resultPanorama.selection.top <
               (croppedTIFFIntermediate ? ROIRect.bottom +
                1 : resultPanorama.height)) {

            // Call the main pixel remapping routine...all the interpolation happens here

        /*
            printf("Prefs: %f\n", prefs->pano.hfov);
            printf("Prefs im: hvof %f, yaw %f pitch %f, roll %f\n", prefs->im.hfov, prefs->im.yaw, prefs->im.pitch, prefs->im.roll);
            printf("Prefs pano: hvof %f, vfov %f pitch %f, roll %f\n", prefs->pano.hfov, prefs->pano.yaw, prefs->pano.pitch, prefs->pano.roll);
            printf("Prefs Interpolator %d:\n", prefs->interpolator);
            printf("Prefs Gamma %d:\n", prefs->gamma);
            printf("Prefs FastT %d:/n,  prefs->fastStep);
        */

            MyMakePano(&transform, prefs, loopCounter);

            if (transform.success == 0) {       // Error 
                PrintError("Error converting image");
                goto mainError;
            }

            //Reverse byte order before writing out to TIFF file
            ARGtoRGBAImage(&resultPanorama);

            //Write calculated data rows to TIFF file one row (aka "scanline") at a time
            for (ebx = 0;
                 ebx <
                 resultPanorama.selection.bottom -
                 resultPanorama.selection.top; ebx++) {
                if (TIFFWriteScanline(tiffFile->tiff, 
                      *resultPanorama.data + (resultPanorama.bytesPerLine * ebx),
                      outputScanlineNumber, 1) != 1) {
                    PrintError("Unable to write to TIFF file\n");
                    return -1;
                }

                outputScanlineNumber++;
            }

            if (ptQuietFlag == 0) {

                //Update progress bar
                if (croppedTIFFIntermediate)
                    snprintf(tmpStr, sizeof(tmpStr)-1, "%d",
                            (int) ((resultPanorama.selection.bottom -
                                    ROIRect.top) * 100 / croppedHeight));
                else
                    snprintf(tmpStr, sizeof(tmpStr)-1, "%d",
                            (int) (resultPanorama.selection.bottom * 100 /
                                   resultPanorama.height));

                if (Progress(_setProgress, tmpStr) == 0) {
                    // Cancelled by the user
                    panoTiffClose(tiffFile);
                    remove(tempScriptFile.name);
                    remove(fullPathImages[loopCounter].name);
                    return (-1);
                }
            }

            //specify the next batch of rows to be processed 
            resultPanorama.selection.top = resultPanorama.selection.bottom;
            resultPanorama.selection.bottom =
                resultPanorama.selection.top + lines;

            //Be careful at boundary...end of image
            if (resultPanorama.selection.bottom >
                (croppedTIFFIntermediate ? ROIRect.bottom +
                 1 : resultPanorama.height))
                resultPanorama.selection.bottom =
                    (croppedTIFFIntermediate ? ROIRect.bottom +
                     1 : resultPanorama.height);
        }

        panoTiffClose(tiffFile);

#ifdef UNCROP_FISHEYES
        if (croppedTIFFIntermediate == 0) {
            // We can't process (yet) all files in cropped mode
            // To quite the roar from the masses let them think we
            // do. I wonder how long it will take for them to notice. Placebo effect?
            pano_cropping_parms croppingParms;
            bzero(&croppingParms, sizeof(croppingParms));

            if (panoTiffCrop(currentFullPath.name, currentFullPath.name, &croppingParms) == 0) {
                PrintError("Unable to write output file %s", currentFullPath.name);
                remove(tempScriptFile.name);
                return (-1);
            }
        }
#endif

        //////////////////////////////////////////////////////////////////////
        panoImageDispose(&image1);

        // The memory for td and ts was allocated in morpher.c with malloc 
        // (not myMalloc), so we need to use free (not myFree)
        if (prefs->td != NULL) {
            free((void **) prefs->td);
        }

        if (prefs->ts != NULL) {
            free((void **) prefs->ts);
        }
        free(prefs);

        panoImageDispose(&resultPanorama);
        
    }                           //End of main image processing loop
    
    if (!ptQuietFlag)
        Progress(_disposeProgress, "");

    // This is the end of the pixel remapping for all input images.
    // At this point we should have a collection of TIFF files containing
    // the warped input images.  For TIFF_m format this is all we need.  For
    // other formats, we may need to do extra work (feathering, flattening, etc.)

    //----------------------------------------------------------------------

    remove(tempScriptFile.name);

    panoImageDispose(&resultPanorama);
    panoImageDispose(&image1);

#if 0 
    // NO LONGER SUPPORTED IN THIS FUNCTION. IT SHOULD BE REMOVED IN THE FUTURE

    // These functions are to correct and/or brightness.  They are not required for 
    // panoramas that do not need any brightness adjustments.  Moreover, Dersch
    // was not fully satisfied with the quality of results obtained from
    // using these functions, and knew that they could be significantly
    // improved.  In general, I think it best to avoid using these features, 
    // and doing any color/brightness adjustments manually either before
    // or after stitching.  While these functions work OK for some images, some 
    // of the time, they can produce some obviously wrong results in some
    // circumstances...perhaps an area for future improvement, but probably not 
    // as important a feature (now that we have multi-resolution splining 
    // software like Enblend) as when Desrch first added these (MRDL).

    if (var00 != 0) {
        ColourBrightness(fullPathImages, fullPathImages, counterImageFiles,
                         var00 - 1, 1, 0);
    }

    if (var01 != 0) {           //
      //      fprintf(stderr, "This type of correction... 1\n");
        ColourBrightness(fullPathImages, fullPathImages, counterImageFiles,
                         var01 - 1, 2, 0);
    }                           // 

    if (colourCorrection != 0) {
      //      fprintf(stderr, "This type of correction... 2\n");
        ColourBrightness(fullPathImages, fullPathImages, counterImageFiles,
                         (colourCorrection / 4) - 1, 0, 0);
    }

    SetVRPanoOptionsDefaults(&defaultVRPanoOptions);

    /* Soo, at this point we have skipped the first word of the panorama:
       # n"QTVR w400 h300 c1"           additional viewer options in a quoted string together with format
       #              the following options are recognized:
       #                  w(width) and h(height) of viewer window (only QTVR on Macs)
       #                  c(codec: 0-JPEG, 1-Cinepak, 2-Sorenson) (only QTVR on Macs)
       #                  q(codec quality):
       #                     0-high,1-normal,2-low    QTVR on Macs
       #                     0-100(highest)           on other jpeg-formats (PAN, IVR, IVR_java, VRML)
       #                  g  progressive jpeg (0-no, 1-yes) (PAN, IVR, IVR_java, VRML)
       #                     Optimized JPEG (0-on(default), 2-disabled), (3-progressive with optimized disabled)
       #                  p  initial pan angle ( QTVR on Macs, VRML, IVR)
       #                  v  field of view (QTVR, VRML, IVR)
       #                  Many more options can be set by editing the viewer scripts
     */
    //int getVRPanoOptions( VRPanoOptions *v, char *line )

    getVRPanoOptions(&defaultVRPanoOptions, tempString);
#endif

    // We have to add "masks" to the images before finishing...

    if (ptQuietFlag == 0)
        Progress(_initProgress, "Writing Output Images");

    for (loopCounter = 0; loopCounter < counterImageFiles; loopCounter++) {
        
        if (ptQuietFlag == 0) {
            snprintf(tmpStr, sizeof(tmpStr)-1, "%d",
                    (100 * loopCounter) / counterImageFiles);
            if (Progress(_setProgress, tmpStr) == 0) {
                return (1);
            }
        }

        strcpy(outputFileName, panoFileName->name);
        snprintf(var40, 8, "%04d", loopCounter);
        strcat(outputFileName, var40);
        panoReplaceExt(outputFileName, ".tif");
        // remove output file, if it exists. 
        if (panoSingleFileExists(outputFileName)) {
            remove(outputFileName);
        }
        rename(fullPathImages[loopCounter].name, outputFileName);

    }
    free(fullPathImages);


    if (ptQuietFlag == 0) {
        Progress(_setProgress, "100%");
        Progress(_disposeProgress, "");
    }

    free(regScript);
    return (0);

    // FUNCTION ENDS HERE

  mainError:
    free(regScript);

    return (-1);
}


#if 0

    // THIS_CODE_IS_NO_LONGER_SUPPORTED. IT SHOULD BE REMOVED IN THE FUTURE

    if (counterImageFiles > 1) {

      // There is no point in adding stitching masks for just one image 
      //printf("Creating seams******************\n");

        if (panoStitchReplaceMasks(fullPathImages, fullPathImages, counterImageFiles,
                                   feather) != 0) {
            PrintError("Could not create stitching masks");
            goto mainError;
        }
    }



    /************ OUTPUT FORMATS: Multiple TIFF ***************/
    // TIFF_m and TIFF_mask...just rename the intermediate files 
    // that we've already computed with numbers (e.g. img0000.tif, img0001.tif, etc.) 
    // and we are finished processing.
    if (strcmp(output_file_format, "TIFF_m") == 0
        || strcmp(output_file_format, "TIFF_mask") == 0) {


            if ((croppedTIFFIntermediate != 0 && croppedTIFFOutput != 0) ||
                (croppedTIFFIntermediate == 0 && croppedTIFFOutput == 0)) {
                // if intermediate and output formats are the same, then just rename and quit
                rename(fullPathImages[loopCounter].name, outputFileName);
            }
            else if (croppedTIFFIntermediate != 0 && croppedTIFFOutput == 0) {
                // if cropped intermediate, but we want uncropped output, then uncrop
                if (!panoUnCropTiff
                    (fullPathImages[loopCounter].name, outputFileName)) {
                    return (1);
                }
                remove(fullPathImages[loopCounter].name);
            }
            else {
                // only other option is to use uncropped files as intermediate, and want 
                // cropped as output.  This is (a) a waste of time and (b) not supported.
                // Show error, but be nice and rename existing images anyway
                PrintError
                    ("Cropped output files cannot be created from uncropped intermediate files\n\nWriting uncropped output: %s",
                     outputFileName);
                rename(fullPathImages[loopCounter].name, outputFileName);
            }


        }                       // end of for loop
        free(fullPathImages);

        if (ptQuietFlag == 0) {
            Progress(_setProgress, "100%");
            Progress(_disposeProgress, "");
        }
        return (0);
    }

    //printf("To start creating the output files\n");
    
  /************ OUTPUT FORMATS: Layered PSD ***************/
    // Layered PSD is less simple...we need to assemble the existing
    // intermediate files into a layered photoshop document
    if (strcmp(output_file_format, "PSD_nomask") == 0
        || strcmp(output_file_format, "PSD_mask") == 0
        ) {
        panoReplaceExt(panoFileName->name, ".psd");
        
        if (panoCreatePSD(fullPathImages, counterImageFiles, panoFileName, 0) != 0) {
            PrintError("Error creating PSD file");
            return (-1);
        }

        for (loopCounter = 0; loopCounter < counterImageFiles; loopCounter++) {
            remove(fullPathImages[loopCounter].name);
        }

        free(fullPathImages);
        return (0);
    }


  /************ OUTPUT FORMATS: Flattened files ***************/
    // All other formats require us to "flatten" the intermediate layers into
    // one final document...general approach is to flatten to a single TIFF file, 
    // and then convert this to the desired output file format (e.g. JPEG, PNG, etc.)
    if (counterImageFiles > 1) 
    {

    if (!panoFlattenTIFF
        (fullPathImages, counterImageFiles, &fullPathImages[0], TRUE)) 
        {
        PrintError("Error while flattening TIFF-image");
        goto mainError;
    }

    }
    panoReplaceExt(panoFileName->name, ".tif");
    rename(fullPathImages[0].name, panoFileName->name);

    free(fullPathImages);

    //Desired output format is TIFF...no further conversion needed
    if (strcmp(output_file_format, "TIFF") == 0
        || strcmp(output_file_format, "TIF") == 0)
        return (0);


    //Read back in again so we can convert to final desired format
    if (panoImageRead(&resultPanorama, panoFileName) == 0) {
        PrintError("Could not read result image %s", panoFileName->name);
        goto mainError;
    }

    remove(panoFileName->name);

    if (strcmp(output_file_format, "QTVR") == 0)
        return Create_QTVR(&resultPanorama, panoFileName);

    if (strcmp(output_file_format, "IVR_java") == 0) {
        if (panoProjection == 1)
            return Unknown03(&resultPanorama, panoFileName);
        else
            return Unknown02(&resultPanorama, panoFileName);
    }

    if (strcmp(output_file_format, "VRML") == 0)
        return Unknown05(&resultPanorama, panoFileName);

    if (strncmp(output_file_format, "IVR", 3) == 0) {   // compare first 3 characters of it // end at 804ae10
        if (panoProjection == 1)
            return Unknown01(&resultPanorama, panoFileName);
        else
            return Create_LP_ivr(&resultPanorama, panoFileName);
    }

    if (strcmp(output_file_format, "PAN") == 0) {       // 
        return Unknown04(&resultPanorama, panoFileName);
    }                           // 804ae10

    if (strcmp(output_file_format, "JPEG") == 0
        || strcmp(output_file_format, "JPG") == 0) {
        if (!ptQuietFlag) {
            char temp[100];
            
            sprintf(temp, "Creating JPEG (quality %d jpegProgressive %d)\n",
                    defaultVRPanoOptions.cquality,
                    defaultVRPanoOptions.progressive);
                    
            Progress(_initProgress, temp);
        }
        panoReplaceExt(panoFileName->name, ".jpg");
        return writeJPEG(&resultPanorama, panoFileName,
                         defaultVRPanoOptions.cquality,
                         defaultVRPanoOptions.progressive);
    }


    if (strcmp(output_file_format, "PSD") == 0) {
        panoReplaceExt(panoFileName->name, ".psd");
        return (writePSD(&resultPanorama, panoFileName));

    }

    if (strcmp(output_file_format, "PNG") == 0) {
        panoReplaceExt(panoFileName->name, ".PNG");
        return (writePNG(&resultPanorama, panoFileName));
    }

    PrintError("Panorama output format not supported: %s",
               output_file_format);

#endif



/*
 * Because this function can be called with a directory name with a period
 * inside it (e.g. "c:\dir\another.dir\filewithoutextension") then we need to 
 * make sure that the . happens after the last \ otherwise we'd truncate 
 * the directory name rather than replacing the extension
 */
void panoReplaceExt(char *filename, char *extension)
{
    char *dot_pos = strrchr(filename, '.');
    char *path_sep_win = strrchr(filename, '\\');
    char *path_sep_unix = strrchr(filename, '/');
    char *path_sep = (path_sep_unix == NULL ? path_sep_win : path_sep_unix );

    if (dot_pos != NULL && (path_sep == NULL || dot_pos>path_sep)) {
        strcpy(dot_pos, extension);
    }
    else {
        strcat(filename, extension);
    }
    return;
}





int panoFlattenTIFF(fullPath * fullPathImages, int counterImageFiles,
                    fullPath * outputFileName, int removeOriginals)
{

    pano_Tiff **tiffFileHandles;
    pano_Tiff *outputFile;

    unsigned char **imageDataBuffers;
    unsigned char *resultBuffer;


    fullPath tmpFullPath;
    char tmpFilename[512];


    pano_CropInfo *cropInfo;
    unsigned int linesPerPass;
    pano_ImageMetadata *outputMetadata;

    unsigned int i;
    unsigned int offsetBeforeThisPass = 0;
    int linesLeft = 0;
    unsigned int linesToRead;
    int rowInPass;
    int inputImageRowIndex;
    int outputImageRowIndex;
    unsigned char *pixelPtr;

    //Open up all intermediate TIFF files at once

    assert(fullPathImages != NULL);
    assert(counterImageFiles > 1);
    assert(outputFileName != NULL);

    tiffFileHandles = calloc(counterImageFiles, sizeof(pano_Tiff));

    if (tiffFileHandles == NULL) {
        PrintError("Not enough memory");
        return 0;
    }

    for (i = 0; (int) i < counterImageFiles; i++) {

        if (GetFullPath(&fullPathImages[i], tmpFilename) != 0) {
            PrintError("Could not get filename");
            return 0;
        }

        if ((tiffFileHandles[i] = panoTiffOpen(tmpFilename)) == NULL) {
            PrintError("Could not open TIFF-Layer %d", i);
            return 0;
        }

    }

//////////////////////////////////////////////////////////////////////


    //modify "tmpFullPath" to contain the name of a new, empty temp file
    if (panoFileMakeTemp(&tmpFullPath) == 0) {
        PrintError("Could not make Tempfile");
        return 0;
    }

    //copy the name of this new tmpFullPath into a string (tmpFilename)
    if (GetFullPath(&tmpFullPath, tmpFilename) != 0) {
        PrintError("Could not get filename");
        return 0;
    }

    // Because the 0th intermediate TIFF file might be a "cropped" file, we 
    // need to update the imageParameters so that the dimensions reflect the
    // the size of the full-sized output image, rather than one of the 
    // (potentially) cropped intermediate files

    if ((outputFile =
         panoTiffCreateUnCropped(tmpFilename,
                                 &tiffFileHandles[0]->metadata)) == 0) {
        PrintError("Could not create TIFF file");
        return 0;
    }

    // Calculate number of lines to read at a time so that we are reading 
    // approximately 500 KB at a time from each input file.  This could be 
    // memory intensive if we have an awful lot of images and not much memory, 
    // but probably not a big problem for 99.9% of cases on 99.9% of machines.
    linesPerPass = 500000 / outputFile->metadata.bytesPerLine;

    if (linesPerPass == 0)
        linesPerPass = 1;

    outputMetadata = &outputFile->metadata;

    // We dont need to read more lines that the size of the file
    if (outputMetadata->imageHeight < linesPerPass) {
        linesPerPass = outputMetadata->imageHeight;
        if (linesPerPass == 0) {
            PrintError
                ("Invalid image length in TIFF file. It might be corrupted");
            return -1;
        }
    }

    // Create as many image data buffers as we have input files.  Note that the 
    // input buffers are as wide as the final output image, which may be more
    // than we technically need if the input images are cropped...it makes the 
    // code simpler, however.
    imageDataBuffers = calloc(counterImageFiles, sizeof(unsigned char *));

    for (i = 0; (int) i < counterImageFiles; i++) {
        imageDataBuffers[i] =
            calloc(linesPerPass * outputMetadata->bytesPerLine, 1);
        if (imageDataBuffers[i] == NULL) {
            PrintError("Not enough memory to allocate input buffers");
            return -1;
        }
    }

    //we need one buffer to store output result
    resultBuffer = calloc(linesPerPass * outputMetadata->bytesPerLine, 1);

    if (resultBuffer == NULL) {
        PrintError("Not enough memory to allocate output buffer");
        return -1;
    }

    offsetBeforeThisPass = 0;

    if (ptQuietFlag == 0) {
        Progress(_initProgress, "Flattening Image");
    }

    //  printf("To do %d lines\n", outputMetadata->imageHeight);

    linesLeft = outputMetadata->imageHeight;

    // Main flattening loop...iterate over input files, read some data from each, 
    // combine into output buffer, write to file
    while (linesLeft > 0) {

        linesToRead = (linesLeft > (int)linesPerPass) ? linesPerPass : linesLeft;

        // iterate over each input file
        for (i = 0; (int) i < counterImageFiles; i++) {
            cropInfo = &(tiffFileHandles[i]->metadata.cropInfo);

            // Get a few lines of data from this input file one row at a time
            for (rowInPass = 0; rowInPass < (int) linesToRead; rowInPass++) {

                //figure out which row to read/write from input/output images
                outputImageRowIndex = offsetBeforeThisPass + rowInPass;
                inputImageRowIndex = outputImageRowIndex - cropInfo->yOffset;

                //point to first byte on this row of the input buffer
                pixelPtr =
                    imageDataBuffers[i] +
                    (outputMetadata->bytesPerLine * rowInPass);

                //clear out any old data, and fill with empty space (zeros)
                memset(pixelPtr, 0, outputMetadata->bytesPerLine);

                // Only try to read data if we are reading from a row that exists in the 
                // input image        
                if (inputImageRowIndex >= 0
                    && inputImageRowIndex < cropInfo->croppedHeight) {
                    if (TIFFReadScanline
                        (tiffFileHandles[i]->tiff,
                         pixelPtr +
                         (cropInfo->xOffset * outputMetadata->bytesPerPixel),
                         inputImageRowIndex, 0) != 1) {
                        PrintError("Error reading tiff file\n");
                        return 0;
                    }
                }
            }
        }

        //    printf("Passing offsetAfterThisPass [%d] of [%d] linesPerPass  %d \n",offsetAfterThisPass, outputMetadata->imageHeight, linesPerPass);

        if (ptQuietFlag == 0) {
            snprintf(tmpFilename, sizeof(tmpFilename)-1, "%d",
                    (offsetBeforeThisPass +
                     linesToRead) * 100 / outputMetadata->imageHeight);
            if (Progress(_setProgress, tmpFilename) == 0)
                return 0;
        }

        // FlattenImageSection
        panoStitchBlendLayers(imageDataBuffers, counterImageFiles, resultBuffer,
                              linesToRead, outputMetadata->imageWidth,
                              outputMetadata->bitsPerPixel,
                              outputMetadata->bytesPerLine);

        for (i = 0; i < linesToRead; i++) {
            if (TIFFWriteScanline
                (outputFile->tiff,
                 resultBuffer + outputMetadata->bytesPerLine * i,
                 offsetBeforeThisPass + i, 0) != 1) {
                PrintError("Unable to write TIFF to file\n");
                return 0;
            }
        }

        offsetBeforeThisPass += linesToRead;
        linesLeft -= linesToRead;

    }

    if (!ptQuietFlag)
        Progress(_disposeProgress, "Done flattening.");

    //  printf("Lines read %d from %d\n", offsetBeforeThisPass,outputMetadata->imageHeight);

    for (i = 0; (int) i < counterImageFiles; i++) {
        free(imageDataBuffers[i]);
        panoTiffClose(tiffFileHandles[i]);
    }

    panoTiffClose(outputFile);

    if (removeOriginals) {
        for (i = 0; (int) i < counterImageFiles; i++) {
            remove(fullPathImages[i].name);
        }
    }

    rename(tmpFullPath.name, outputFileName->name);

    free(tiffFileHandles);

    free(imageDataBuffers);
    free(resultBuffer);

    return 1;

}




// the functionality of PTcrop and PTuncrop is essentially identical, except for
// the function that they call

int panoCroppingMain(int argc,char *argv[], int operation, char *version, char *usage, char *defaultPrefix)
{
    int opt;
    int ptForceProcessing = 0;
    int filesCount;
    int retVal;
    pano_cropping_parms croppingParms;
    char outputPrefix[MAX_PATH_LENGTH];
    int ptDeleteSources = 0;
    fullPath *ptrInputFiles = NULL;
    fullPath *ptrOutputFiles = NULL;
    int base;
    int i;

    // Set defaults
    strcpy(outputPrefix, defaultPrefix);
    bzero(&croppingParms, sizeof(croppingParms));
    
    printf("%s", version);

    //Need enough space for a message to be returned if something goes wrong

    while ((opt = getopt(argc, argv, "p:fqhx")) != -1) {

        // o overwrite
        // h       -> help
        // q       -> quiet?

        switch(opt) {  // fhoqs        f: 102 h:104  111 113 115  o:f:hsq
        case 'p':
            if (strlen(optarg) < MAX_PATH_LENGTH) {
                strcpy(outputPrefix, optarg);
            } else {
                PrintError("Illegal length for output prefix");
                return -1;
            }
            break;
        case 'f':
            ptForceProcessing = 1;
            break;
        case 'x':
            ptDeleteSources = 1;
            break;
        case 'q':
            ptQuietFlag = 1;
            break;
        case 'h':
            printf("%s",usage);
            exit(0);
        default:
            break;
        }
    }
    filesCount = argc - optind;

    if (filesCount < 1) {
        PrintError("No files specified in the command line");
        printf("%s",usage);
        exit(0);
    }
    // Allocate memory for filenames
    if ((ptrInputFiles = calloc(filesCount, sizeof(fullPath))) == NULL || 
        (ptrOutputFiles = calloc(filesCount, sizeof(fullPath))) == NULL)        {
        PrintError("Not enough memory");
        free(ptrInputFiles);
        free(ptrOutputFiles);
        return -1;
    }

    // GET input file names
    base = optind;
    for (; optind < argc; optind++) {
        char *currentParm;

        currentParm = argv[optind];

        if (StringtoFullPath(&ptrInputFiles[optind-base], currentParm) !=0) { // success
            PrintError("Syntax error: Not a valid pathname");
            return(-1);
        }
    }
        // Generate output file names
    if (panoFileOutputNamesCreate(ptrOutputFiles, filesCount, outputPrefix) == 0) {
        return -1;
    }

    if (!ptForceProcessing) {
        char *temp;
        if ((temp = panoFileExists(ptrOutputFiles, filesCount)) != NULL) {
            PrintError("Output filename exists %s. Use -f to overwrite", temp);
            return -1;
        }
    }
    if (! ptQuietFlag) 
        printf("Cropping %d files\n", filesCount);

    for (i=0; i< filesCount; i++) {

        if (!ptQuietFlag) {
            PrintError("Processing %d reading %s creating %s", i, ptrInputFiles[i].name, ptrOutputFiles[i].name);
        }
        croppingParms.forceProcessing = ptForceProcessing;
        switch (operation) {
        case PANO_CROPPING_CROP:
            retVal = panoTiffCrop(ptrInputFiles[i].name, ptrOutputFiles[i].name, &croppingParms);
            break;
        case PANO_CROPPING_UNCROP:
            retVal = panoTiffUnCrop(ptrInputFiles[i].name, ptrOutputFiles[i].name, &croppingParms);
            break;
        default:
            PrintError("Illegal operation in panoCroppingMain. Programming error");
            exit(0);
        }


        if (! retVal ) {
            PrintError("Error cropping file %s", ptrInputFiles[i].name);
            return -1;
        }
    }
    if (ptDeleteSources) {
        panoFileDeleteMultiple(ptrInputFiles, filesCount);
    }
    if (ptrInputFiles != NULL)
        free(ptrInputFiles);
    if (ptrOutputFiles != NULL)
        free(ptrOutputFiles);

    return 0;
}



void panoPrintImage(char *msg, Image *im)
{
    printf("-------------%s\n", msg);
    if (im != NULL) {
        printf(">>>Image format %d\n", (int)im->format);
        printf(">>>Roll %f\n", im->roll);
        printf(">>>Pitch %f\n", im->pitch);
        printf(">>>Yaw %f\n", im->yaw);

        printf(">>>im->cP.shear %d\n", im->cP.shear);
        printf(">>>im->cP.tilt %d\n", im->cP.tilt);
        printf(">>>im->cP.tilt_x %f\n", im->cP.tilt_x);
        printf(">>>im->cP.tilt_y %f\n", im->cP.tilt_y);
        printf(">>>im->cP.tilt_z %f\n", im->cP.tilt_z);
        printf(">>>im->cP.tilt_scale %f\n", im->cP.tilt_scale);

        printf(">>>im->cP.translation %d\n", im->cP.trans);
        printf(">>>im->cP.trans_x %f\n", im->cP.trans_x);
        printf(">>>im->cP.trans_y %f\n", im->cP.trans_y);
        printf(">>>im->cP.trans_z %f\n", im->cP.trans_z);
        printf(">>>im->cP.trans_yaw %f\n", im->cP.trans_yaw);
        printf(">>>im->cP.trans_pitch %f\n", im->cP.trans_pitch);

        printf(">>>im->cP.test %d\n", im->cP.test);
        printf(">>>im->cP.test parm1 %f\n", im->cP.test_p0);
        printf(">>>im->cP.test parm2 %f\n", im->cP.test_p1);
        printf(">>>im->cP.test parm3 %f\n", im->cP.test_p2);
        printf(">>>im->cP.test parm4 %f\n", im->cP.test_p3);
    }
    printf("\n\n");

}

