/*
 * $Id: imtransform.cxx,v 4.2 2004/05/23 23:13:51 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  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 program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */


/*------------------------------------------------------------------------
 *
 * imtransform.C
 *
 * This file contains code to tranform the raw data in place
 * e.g: Rotations, shears, etc. 
 *
 * This is a harder jobs than it seems, because we'll want to
 * remember the transforms.
 *
 * Hugues Talbot	
 *      
 *-----------------------------------------------------------------------*/

#include "imnmspc.hxx" // namespace etc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "imunistd.h"
#include "imview.hxx"
#include "pointfile.hxx"
#include "imageIO.hxx"
#include "imageViewer.hxx"

extern pointfile             *PtFileMngr;
extern imageIO               *IOBlackBox;

// this method rotates the buffers by 90 to the right
// must also work on 3d,  multispectral images
// Multicomponent or time series are handled somewhere else
void imageIO::rotateRight()
{
    if (imdata != 0) { // do we have an image?
	if (overlay) { // we still need to rotate the overlay if present
	    unsigned char *p, *q;
	    int             x, y;

	    dbgprintf("Rotating the overlay to the right\n");
	    p = overlay;
	    if (ovlspp == 1) {
		q = new uchar[currImgWidth * currImgHeight];
		for (y = 0 ; y < currImgHeight ; y++) 
		    for (x = 0 ; x < currImgWidth ; x++)
			q[(currImgHeight-y-1)+ currImgHeight*x] = *p++;
	    } else { // ovlspp == 3
		q = new uchar[3 * currImgWidth * currImgHeight];
		for (y = 0 ; y < currImgHeight ; y++) 
		    for (x = 0 ; x < currImgWidth ; x++) {
			int idx = 3 * ((currImgHeight-y-1)+ currImgHeight*x);
			q[idx++] = *p++;
			q[idx++] = *p++;
			q[idx] = *p++;
		    }
	    }
		
	    delete[] overlay;
	    overlay = q;
	}
	
	if (currBuffp != 0)  {
	    // this is going to be much slower than below
	    int            sz=typeSize(currPixType);
	    unsigned char *p, *q;
	    int            i, x, y, z, swp;
	    void         **ncurrBuffp = 0;
	    
	    if (dontFreeBuffers) {
		// its a lot more complicated
		ncurrBuffp = (void **)malloc(currImgNbSamples * sizeof(void*));
		if (ncurrBuffp == 0)
		    errprintf("This image can't be rotated due to memory problems\n");
	    }
	    
	    for (i = 0 ; i < currImgNbSamples ; i++) {
		p = (uchar *)currBuffp[i];
		// the currBuffps are malloced, not newed
		if ((q = (uchar *) malloc(currImgWidth * currImgHeight * currImgThickness * sz *sizeof(char))) != NULL) {
		    for (z = 0 ; z <  currImgThickness ; z++) {
			for (y = 0 ; y < currImgHeight ; y++) {
			    for (x = 0 ; x < currImgWidth ; x++) {
				memcpy(q + sz*(currImgHeight*currImgWidth*z + ((currImgHeight-y-1)+currImgHeight*x)),
				       p, sz);
				p += sz;
			    }
			}
		    }
		    if (!dontFreeBuffers) {
			free(currBuffp[i]);
			currBuffp[i] = q;
		    } else {
			ncurrBuffp[i] = q; // save to a different buffer
		    }
		} else {
		    int l; 
		    errprintf("Not enough memory to rotate image\n");
		    if (dontFreeBuffers) {
			for (l = 0 ; l < i ; l++)
			    free(ncurrBuffp[l]);
			free(ncurrBuffp);
		    }
		    // nothing further can be done for the other case
		    // as the images have been rotated already.
			
		    return;
		}
	    }
	    swp = currImgWidth;
	    currImgWidth = currImgHeight;
	    currImgHeight = swp;
	    if (dontFreeBuffers) {
		currBuffp = ncurrBuffp; // don't worry, currBuffp is not lost.
		dontFreeBuffers = false; // these new buffers, we can free.
	    }
	} else {
	    // much simpler case
	    unsigned char *p, *q;
	    int            x, y, c, swp;
	    // we only need to rotate the display buffer.
	    dbgprintf("2D CHAR image: only rotating the display buffer\n");
	    p = imdata;
	    // on the other hand, imdata is newed, not malloced!
	    q = new uchar[currImgWidth * currImgHeight * currImgNbSamples];
	    for (y = 0 ; y < currImgHeight ; y++) 
		for (x = 0 ; x < currImgWidth ; x++)
		    for (c = 0 ; c < currImgNbSamples ; c++) 
			q[currImgNbSamples * ((currImgHeight-y-1)+currImgHeight*x) + c] = *p++;
	    delete[] imdata; // this gets rid of the old buffer
	    imdata = q;
	    // swaps height and width of the buffer
	    swp = currImgWidth;
	    currImgWidth = currImgHeight;
	    currImgHeight = swp;
	}

	// rotate the points, if any
	PtFileMngr->rotate90m();
    }
}

// This method rotates the buffers by 90 to the left
// must also work on 3d multispectral images.
void imageIO::rotateLeft()
{
    if (imdata != 0) {
	if (overlay) {
	    unsigned char *p, *q;
	    int             x, y;

	    dbgprintf("Rotating the overlay to the left\n");
	    p = overlay;
	    if (ovlspp == 1) {
		q = new uchar[currImgWidth * currImgHeight];
		for (y = 0 ; y < currImgHeight ; y++) 
		    for (x = 0 ; x < currImgWidth ; x++)
			q[y + currImgHeight*(currImgWidth-x-1)] = *p++;
	    } else { //ovlspp == 3
		q = new uchar[3 * currImgWidth * currImgHeight];
		for (y = 0 ; y < currImgHeight ; y++) 
		    for (x = 0 ; x < currImgWidth ; x++) {
			int idx = 3 * (y + currImgHeight*(currImgWidth-x-1));
			q[idx++] = *p++;
			q[idx++] = *p++;
			q[idx] = *p++;
		    }
	    }

	    delete[] overlay;
	    overlay = q;
	}
	
	if (currBuffp != 0) {
	    // this is going to be much slower than below
	    int            sz=typeSize(currPixType);
	    unsigned char *p, *q;
	    int            i, x, y, z, swp;
	    void         **ncurrBuffp = 0;

	    if (dontFreeBuffers) {
		// its a lot more complicated
		ncurrBuffp = (void **)malloc(currImgNbSamples * sizeof(void*));
		if (ncurrBuffp == 0)
		    errprintf("This image can't be rotated due to memory problems\n");
	    }
	    
	    for (i = 0 ; i < currImgNbSamples ; i++) {
		p = (uchar *)currBuffp[i];
		// the currBuffps are malloced, not newed
		if ((q = (uchar *) malloc(currImgWidth * currImgHeight * currImgThickness * sz)) != NULL) {
		    for (z = 0 ; z <  currImgThickness ; z++) {
			for (y = 0 ; y < currImgHeight ; y++) {
			    for (x = 0 ; x < currImgWidth ; x++) {
				memcpy(q + sz*(currImgHeight*currImgWidth*z + (y + currImgHeight*(currImgWidth-x-1))),
				       p, sz);
				p += sz;
			    }
			}
		    }
		    if (!dontFreeBuffers) {
			free(currBuffp[i]);
			currBuffp[i] = q;
		    } else {
			ncurrBuffp[i] = q; // save to a different buffer
		    }
		} else {
		    int l; 
		    errprintf("Not enough memory to rotate image\n");
		    if (dontFreeBuffers) {
			for (l = 0 ; l < i ; l++)
			    free(ncurrBuffp[l]);
			free(ncurrBuffp);
		    }
		    // nothing further can be done for the other case
		    // as the images have been rotated already.
		    return;
		}
	    }
	    swp = currImgWidth;
	    currImgWidth = currImgHeight;
	    currImgHeight = swp;
	    if (dontFreeBuffers) {
		currBuffp = ncurrBuffp; // don't worry, currBuffp is not lost.
		dontFreeBuffers = false; // these new buffers, we can free.
	    }
	} else  {
	    unsigned char *p, *q;
	    int            x, y, c, swp;
	    // we only need to rotate the display buffer.
	    dbgprintf("2D CHAR image: only rotating the display buffer\n");
	    p = imdata;
	    // on the other hand, imdata is newed, not malloced!
	    q = new uchar[currImgWidth * currImgHeight * currImgNbSamples];
	    for (y = 0 ; y < currImgHeight ; y++) 
		for (x = 0 ; x < currImgWidth ; x++)
		    for (c = 0 ; c < currImgNbSamples ; c++)
			q[currImgNbSamples * (y + currImgHeight*(currImgWidth-x-1)) + c] = *p++;
	    delete[] imdata; // this gets rid of the old buffer
	    imdata = q;
	    // swaps height and width of the buffer
	    swp = currImgWidth;
	    currImgWidth = currImgHeight;
	    currImgHeight = swp;	
	}

	// rotate the points, if any
	PtFileMngr->rotate90p();
    }
}

// This method rotates the buffers by 180 degree. It is very efficient.
// must also work on 3d multispectral images.
void imageIO::rotate180()
{
    if (imdata != 0) {

	if (overlay) {
	    unsigned char *p, *q, swp;

	    dbgprintf("Rotating the overlay 180 degrees\n");
	    p = overlay;
	    if (ovlspp == 1) {
		q = p + currImgHeight*currImgWidth - 1;
		while (p < q) {
		    swp = *p;
		    *p++ =*q;
		    *q-- = swp;
		}
	    } else { // ovlspp == 3
		q = p + 3 * currImgHeight*currImgWidth - 3;
		while (p < q) {
		    // R
		    swp = *p;
		    *p++ =*q;
		    *q++ = swp;
		    // G
		    swp = *p;
		    *p++ =*q;
		    *q++ = swp;
		    // B
		    swp = *p;
		    *p++ =*q;
		    *q = swp;
		    //
		    q -= 5; // would be 6 if we had done *q++ 2 lines earlier...
		}
	    }
	    // in place rotation
	}
	
	if (currBuffp != 0) {
	    // this is going to be much slower than below
	    int            sz=typeSize(currPixType);
	    unsigned char *p, *q, swp;
	    int            i, z, c;
	    // no buffer reallocation here!
	    for (i = 0 ; i < currImgNbSamples ; i++) {
		for (z = 0 ; z <  currImgThickness ; z++) {
		    p = (uchar *)currBuffp[i] + currImgWidth * currImgHeight * sz * z;
		    q = p + (currImgHeight*currImgWidth - 1) * sz;
		    while (p < q) {
			for (c = 0 ; c < sz ; c++) {
			    swp = *p;
			    *p++ =*q;
			    *q++ = swp;
			}
			q -= 2*sz;
		    }
		}
	    }
	} else {
	    unsigned char *p, *q, swp;
	    int            c;
	    // we only need to rotate the display buffer.
	    dbgprintf("2D CHAR image: only rotating the display buffer\n");
	    p = imdata;
	    // no buffer reallocation here!
	    q = p + (currImgHeight*currImgWidth - 1) * currImgNbSamples;
	    while (p < q) {
		for (c = 0 ; c < currImgNbSamples ; c++) {
		    swp = *p;
		    *p++ =*q;
		    *q++ = swp;
		}
		q -= 2*currImgNbSamples;
	    }
	} 
    }
}

// This method performs an horizontal flip. It is very efficient.
// must also work on 3d multispectral images.
void imageIO::fliph()
{
    if (imdata != 0) {
	if (overlay) {
	    unsigned char *p, *q, *swp;
	    int             y;
	    
	    swp = new uchar[ovlspp * currImgWidth];
	    p = overlay;
	    q = p + ovlspp * currImgWidth * (currImgHeight - 1);
	    dbgprintf("Flipping the overlay buffer horizontally\n");
	    for (y = 0 ; y < currImgHeight/2 ; y++) {
		memcpy(swp, p, ovlspp * currImgWidth);
		memcpy(p, q, ovlspp * currImgWidth);
		memcpy(q, swp, ovlspp * currImgWidth);
		p += ovlspp * currImgWidth;
		q -= ovlspp * currImgWidth;
	    }
	    delete[] swp;
	}
	if (currBuffp != 0) {
	    // this is going to be much slower than below
	    int            sz=typeSize(currPixType);
	    unsigned char *p, *q, *swp;
	    int            i, y, z;
	    // this is just a temp buffer, it can be newed.
	    swp = new uchar[currImgWidth*sz];
	    for (i = 0 ; i < currImgNbSamples ; i++) {
		for (z = 0 ; z <  currImgThickness ; z++) {
		    p = (uchar *)currBuffp[i] + currImgWidth * currImgHeight * sz * z;
		    q = p + ((currImgHeight-1)*currImgWidth) * sz;
		    for (y = 0 ; y < currImgHeight/2 ; y++) {
			memcpy(swp, p, currImgWidth*sz);
			memcpy(p, q, currImgWidth*sz);
			memcpy(q, swp, currImgWidth*sz);
			p += currImgWidth*sz;
			q -= currImgWidth*sz;
		    }
		}
	    }
	    // swap buffer life ends here
	    delete[] swp;
	} else {
	    unsigned char *p, *q, *swp;
	    int            y;
	    // we only need to rotate the display buffer.
	    dbgprintf("2D CHAR image: only flipping horizontally the display buffer\n");
	    p = imdata;
	    q = p + (currImgWidth * (currImgHeight - 1)) * currImgNbSamples;
	    // same story as above with respect to temp buff.
	    swp = new uchar[currImgWidth*currImgNbSamples];
	    for (y = 0 ; y < currImgHeight/2 ; y++) {
		memcpy(swp, p, currImgWidth*currImgNbSamples);
		memcpy(p, q, currImgWidth*currImgNbSamples);
		memcpy(q, swp, currImgWidth*currImgNbSamples);
		p += currImgWidth*currImgNbSamples;
		q -= currImgWidth*currImgNbSamples;
	    }
	    delete[] swp;
	}

	// flip the points
	PtFileMngr->fliph();
    }
}

// This method performs a ver flip. It is not as efficient as the previous one
// must also work on 3d multispectral images.
void imageIO::flipv()
{
    if (imdata != 0) {
	if (overlay) {
	    unsigned char *p, *q, swp;
	    int             x, y;
	    
	    dbgprintf("Flipping the overlay buffer vertically\n");
	    if (ovlspp == 1) {
		for (y = 0 ; y < currImgHeight ; y++) {
		    p = overlay + currImgWidth * y;
		    q = p + (currImgWidth-1);
		    for (x = 0 ; x < currImgWidth/2 ; x++) {
			swp = *p;
			*p++ = *q;
			*q-- = swp;
		    }
		}
	    } else { // == 3
		for (y = 0 ; y < currImgHeight ; y++) {
		    p = overlay + 3 * currImgWidth * y;
		    q = p + 3 * (currImgWidth - 1);
		    for (x = 0 ; x < currImgWidth/2 ; x++) {
			// R
			swp = *p;
			*p++ = *q;
			*q++ = swp;
			// G
			swp = *p;
			*p++ = *q;
			*q++ = swp;
			// B
			swp = *p;
			*p = *q;
			*q = swp;
			// inc
			q -= 5; // we only did 2 `++'s
		    }
		}
	    }
	}
	
	if (currBuffp != 0) {
	     // this is going to be much slower than below
	    int            sz=typeSize(currPixType);
	    unsigned char *p, *q, swp;
	    int            i, x, y, z, c;
	    
	    for (i = 0 ; i < currImgNbSamples ; i++) {
		for (z = 0 ; z <  currImgThickness ; z++) {
		    for (y = 0 ; y < currImgHeight ; y++) {
			p = (uchar *)currBuffp[i] + currImgWidth * sz * (currImgHeight * z +  y);
			q = p + (currImgWidth-1) * sz;
			for (x = 0 ; x < currImgWidth/2 ; x++) {
			    for (c = 0 ; c < sz; c++) {
				swp = *p;
				*p++ = *q;
				*q++ = swp;
			    }
			    q -= 2*sz;
			}
		    }
		}
	    }
	} else {
	    unsigned char *p, *q, swp;
	    int            x, y, c;
	    // we only need to rotate the display buffer.
	    dbgprintf("2D CHAR image: only flipping vertically the display buffer\n");
	    for (y = 0 ; y < currImgHeight ; y++) {
		p = imdata + currImgWidth * y * currImgNbSamples;
		q = p + (currImgWidth-1) * currImgNbSamples;
		for (x = 0 ; x < currImgWidth/2 ; x++) {
		    for (c = 0 ; c < currImgNbSamples; c++) {
			swp = *p;
			*p++ = *q;
			*q++ = swp;
		    }
		    q -= 2*currImgNbSamples;
		}
	    }
	}
	
	// flip the points
	PtFileMngr->flipv();
    }
}

// all the 3D rotation are more or less the same (optimization are impossible)

// This is what happens when you can't rely on templates...



#define  rotinnerloop(P,Q,R) \
do { for (z = 0 ; z <  currImgThickness ; z++) { \
    for (y = 0 ; y < currImgHeight ; y++) { \
         for (x = 0 ; x < currImgWidth ; x++) { \
              *((Q) + R(x,y,z)) = *(P)++; \
         } \
    } \
} } while (0)

void imageIO::rotate3Dgeneric(rot3dtransf *myrotf, long nbplanepix)
{
    int            sz=typeSize(currPixType);
    unsigned char *p, *q;
    int            i, x, y, z;
    void         **ncurrBuffp = 0;
	
    assert(currBuffp != 0);

	    
    if (dontFreeBuffers) {
        // things are a little more complicated
        ncurrBuffp = (void **)malloc(currImgNbSamples * sizeof(void*));
        if (ncurrBuffp == 0)
            errprintf("This image can't be rotated due to memory problems\n");
    }

    for (i = 0 ; i < currImgNbSamples ; i++) {
        p = (uchar *)currBuffp[i];
        // the currBuffps are malloced, not newed
        if ((q = (uchar *) malloc(currImgWidth * currImgHeight * currImgThickness * sz *sizeof(char))) != NULL) {
            switch (sz) {
            case 1: 
                rotinnerloop(p,q,myrotf);
                break;

            case 2: {
                short *ps, *qs;
                    
                ps = (short*)p;
                qs = (short*)q;

                rotinnerloop(ps,qs,myrotf);
            }
                    
                break;
                    
            case 4: {
                int *pi, *qi;
                    
                pi = (int*)p;
                qi = (int*)q;

                rotinnerloop(pi,qi,myrotf);
            }
                break;
                    
            case 8: {
                double *pd, *qd;
                    
                pd = (double*)p;
                qd = (double*)q;

                rotinnerloop(pd,qd,myrotf);
            }
                break;
                    
            default:
                errprintf("Unhandled pixel type in 3D rotation\n");
                break;
            }

            if (!dontFreeBuffers) {
                free(currBuffp[i]);
                currBuffp[i] = q;
            } else {
                ncurrBuffp[i] = q; // save to a different buffer
            }
        } else {
            int l; 
            errprintf("Not enough memory to rotate image\n");
            if (dontFreeBuffers) {
                for (l = 0 ; l < i ; l++)
                    free(ncurrBuffp[l]);
                free(ncurrBuffp);
            }
            // nothing further can be done for the other case
            // as the images have been rotated already.
			
            return;
        }
    }

    if (dontFreeBuffers) {
        currBuffp = ncurrBuffp; // don't worry, currBuffp is not lost.
        dontFreeBuffers = false; // these new buffers, we can free.
    }

    // Rotate the overlay if present
    if (overlay) { 
        // no optimization is possible
        dbgprintf("Rotating the overlay up (in the 3D sense)\n");
        unsigned char *p, *q;
        int             x, y;

        p = overlay;
        if (ovlspp == 1) {
            q = new uchar[currImgWidth * currImgHeight];
            for (z = 0 ; z < currImgThickness ; ++z)
                for (y = 0 ; y < currImgHeight ; ++y) 
                    for (x = 0 ; x < currImgWidth ; ++x)
                        q[myrotf(x,y,z)] = *p++;
        } else { //ovlspp == 3
            q = new uchar[3 * currImgWidth * currImgHeight];
            for (z = 0 ; z < currImgThickness ; ++z)
                for (y = 0 ; y < currImgHeight ; ++y) 
                    for (x = 0 ; x < currImgWidth ; ++x) {
                        int idx = 3 * (myrotf(x,y,z));
                        q[idx++] = *p++;
                        q[idx++] = *p++;
                        q[idx] = *p++;
                    }
        }

        delete[] overlay;
        overlay = q;
    }
}

// this method rotates the buffers by 90 degree `up'
// This *only* works on 3D image.
// must also work multispectral images
// Multicomponent or time series are handled somewhere else

static long transf_rot_3d_up(int x, int y, int z)
{
    return ((IOBlackBox->imageHeight()-y-1) * IOBlackBox->imageWidth() * IOBlackBox->imageThickness() + z * IOBlackBox->imageWidth() + x);
}

void imageIO::rotate3Dup()
{
    if (imdata != 0) {
        int  swp;
        long nbplanepix = IOBlackBox->imageWidth() * IOBlackBox->imageThickness(); 

        rotate3Dgeneric(transf_rot_3d_up, nbplanepix);

        // swapping Y and Z
        swp = currImgHeight;
        currImgHeight = currImgThickness;
        currImgThickness = swp;
        // not sure this is correct...
        swp = yOffset;
        yOffset = zOffset;
        zOffset = swp;
        // to be safe
        currZpos = 0;


        // rotate the points, if any
        PtFileMngr->rotate3dup();
    }
}

static long transf_rot_3d_down(int x, int y, int z)
{
    return (y * IOBlackBox->imageWidth() * IOBlackBox->imageThickness() + (IOBlackBox->imageThickness() - z -1) * IOBlackBox->imageWidth() + x);
}

void imageIO::rotate3Ddown()
{
    if (imdata != 0) {
        int  swp;
        long nbplanepix = IOBlackBox->imageWidth() * IOBlackBox->imageThickness(); 

        rotate3Dgeneric(transf_rot_3d_down, nbplanepix);

        // swapping Y and Z
        swp = currImgHeight;
        currImgHeight = currImgThickness;
        currImgThickness = swp;
        // not sure this is correct...
        swp = yOffset;
        yOffset = zOffset;
        zOffset = swp;
        // to be safe
        currZpos = 0;


        // rotate the points, if any
        PtFileMngr->rotate3ddown();
    }
}
