/*****************************************************************************
   Major portions of this software are copyrighted by the Medical College
   of Wisconsin, 1994-2000, and are released under the Gnu General Public
   License, Version 2.  See the file README.Copyright for details.
******************************************************************************/

#include "mrilib.h"

/*** NOT 7D SAFE ***/

#define FINS(i,j) (  ( (i)<0 || (j)<0 || (i)>=nx || (j)>=ny ) \
                     ? 0.0 : far[(i)+(j)*nx] )

/** option to precompute cubic interpolation weights **/

#undef USE_CGRID

#ifdef USE_CGRID
#  define CGRID 8192
   static int p_first = 1 ;
   static float p_m1[CGRID+1], p_00[CGRID+1],
                p_p1[CGRID+1], p_p2[CGRID+1] ;
#endif

   /* cubic interpolation polynomials */

#define P_M1(x)  ((x)*(1.0-(x))*((x)-2.0))
#define P_00(x)  (3.0*((x)+1.0)*((x)-1.0)*((x)-2.0))
#define P_P1(x)  (3.0*(x)*((x)+1.0)*(2.0-(x)))
#define P_P2(x)  ((x)*((x)+1.0)*((x)-1.0))

/**-------------------------------------------------------------------
    Rotate and shift an image, using bicubic interpolation:
       aa  = shift in x
       bb  = shift in y
       phi = angle in radians
    Sort of a replacement for mri_rotate in mri_warp.c; supposed
    to be faster.  If the input image is MRI_complex, the output
    will be also; otherwise, the output will be MRI_float.
----------------------------------------------------------------------**/

MRI_IMAGE *mri_rota( MRI_IMAGE *im, float aa, float bb, float phi )
{
   float rot_dx , rot_dy , rot_cph , rot_sph , top,bot,val ;
   MRI_IMAGE *imfl , *newImg ;
   MRI_IMARR *impair ;
   float *far , *nar ;
   float xx,yy , fx,fy ;
   int ii,jj, nx,ny , ix,jy , ifx,jfy ;
   float f_jm1,f_j00,f_jp1,f_jp2 , wt_m1,wt_00,wt_p1,wt_p2 ;

#ifdef USE_CGRID
   if( p_first ){
      p_first = 0 ;
      xx      = 1.0 / CGRID ;
      for( ii=0 ; ii <= CGRID ; ii++ ){
         yy       = ii * xx ;
         p_m1[ii] = P_M1(yy) ;
         p_00[ii] = P_00(yy) ;
         p_p1[ii] = P_P1(yy) ;
         p_p2[ii] = P_P2(yy) ;
      }
   }
#endif

   if( im == NULL || ! MRI_IS_2D(im) ){
      fprintf(stderr,"*** mri_rota only works on 2D images!\n") ; EXIT(1) ;
   }

   /** if complex image, break into pairs, do each separately, put back together **/

   if( im->kind == MRI_complex ){
      MRI_IMARR *impair ;
      MRI_IMAGE * rim , * iim , * tim ;
      impair = mri_complex_to_pair( im ) ;
      if( impair == NULL ){
         fprintf(stderr,"*** mri_complex_to_pair fails in mri_rota!\n") ; EXIT(1) ;
      }
      rim = IMAGE_IN_IMARR(impair,0) ;
      iim = IMAGE_IN_IMARR(impair,1) ;  FREE_IMARR(impair) ;
      tim = mri_rota( rim , aa,bb,phi ) ; mri_free( rim ) ; rim = tim ;
      tim = mri_rota( iim , aa,bb,phi ) ; mri_free( iim ) ; iim = tim ;
      newImg = mri_pair_to_complex( rim , iim ) ;
      mri_free( rim ) ; mri_free( iim ) ;
      MRI_COPY_AUX(newImg,im) ;
      return newImg ;
   }

   /** rotation params **/

   rot_cph = cos(phi) ; rot_sph = sin(phi) ;

   rot_dx  = (0.5 * im->nx) * (1.0-rot_cph) - aa*rot_cph - bb*rot_sph
            -(0.5 * im->ny) * rot_sph ;

   rot_dy  = (0.5 * im->nx) * rot_sph + aa*rot_sph - bb*rot_cph
            +(0.5 * im->ny) * (1.0-rot_cph) ;

   /** other initialization **/

   nx = im->nx ;  /* image dimensions */
   ny = im->ny ;

   if( im->kind == MRI_float ) imfl = im ;
   else                        imfl = mri_to_float( im ) ;

   far = MRI_FLOAT_PTR(imfl) ;              /* access to float data */
   newImg = mri_new( nx , nx , MRI_float ) ;   /* output image */
   nar = MRI_FLOAT_PTR(newImg) ;               /* output image data */

   bot = top = far[0] ;
   for( ii=0 ; ii < nx*ny ; ii++ )
           if( far[ii] < bot ) bot = far[ii] ;
      else if( far[ii] > top ) top = far[ii] ;

   /*** loop over output points and warp to them ***/

   for( jj=0 ; jj < nx ; jj++ ){
      xx = rot_sph * jj + rot_dx - rot_cph ;
      yy = rot_cph * jj + rot_dy + rot_sph ;
      for( ii=0 ; ii < nx ; ii++ ){

         xx += rot_cph ;  /* get x,y in original image */
         yy -= rot_sph ;

         ix = (xx >= 0.0) ? ((int) xx) : ((int) xx)-1 ;  /* floor */
         jy = (yy >= 0.0) ? ((int) yy) : ((int) yy)-1 ;

#ifdef USE_CGRID
         ifx   = (xx-ix)*CGRID + 0.499 ;
         wt_m1 = p_m1[ifx] ; wt_00 = p_00[ifx] ;
         wt_p1 = p_p1[ifx] ; wt_p2 = p_p2[ifx] ;
#else
         fx    = xx-ix ;
         wt_m1 = P_M1(fx) ; wt_00 = P_00(fx) ;
         wt_p1 = P_P1(fx) ; wt_p2 = P_P2(fx) ;
#endif

         if( ix > 0 && ix < nx-2 && jy > 0 && jy < ny-2 ){
            float * fym1, *fy00 , *fyp1 , *fyp2 ;

            fym1 = far + (ix-1 + (jy-1)*nx) ;
            fy00 = fym1 + nx ;
            fyp1 = fy00 + nx ;
            fyp2 = fyp1 + nx ;

            f_jm1 =  wt_m1 * fym1[0] + wt_00 * fym1[1]
                   + wt_p1 * fym1[2] + wt_p2 * fym1[3] ;

            f_j00 =  wt_m1 * fy00[0] + wt_00 * fy00[1]
                   + wt_p1 * fy00[2] + wt_p2 * fy00[3] ;

            f_jp1 =  wt_m1 * fyp1[0] + wt_00 * fyp1[1]
                   + wt_p1 * fyp1[2] + wt_p2 * fyp1[3] ;

            f_jp2 =  wt_m1 * fyp2[0] + wt_00 * fyp2[1]
                   + wt_p1 * fyp2[2] + wt_p2 * fyp2[3] ;

         } else {

            f_jm1 =  wt_m1 * FINS(ix-1,jy-1)
                   + wt_00 * FINS(ix  ,jy-1)
                   + wt_p1 * FINS(ix+1,jy-1)
                   + wt_p2 * FINS(ix+2,jy-1) ;

            f_j00 =   wt_m1 * FINS(ix-1,jy)
                    + wt_00 * FINS(ix  ,jy)
                    + wt_p1 * FINS(ix+1,jy)
                    + wt_p2 * FINS(ix+2,jy) ;

            f_jp1 =   wt_m1 * FINS(ix-1,jy+1)
                    + wt_00 * FINS(ix  ,jy+1)
                    + wt_p1 * FINS(ix+1,jy+1)
                    + wt_p2 * FINS(ix+2,jy+1) ;

            f_jp2 =   wt_m1 * FINS(ix-1,jy+2)
                    + wt_00 * FINS(ix  ,jy+2)
                    + wt_p1 * FINS(ix+1,jy+2)
                    + wt_p2 * FINS(ix+2,jy+2) ;
         }

#define THIRTYSIX 2.7777778e-2  /* 1./36.0, actually */

#ifdef USE_CGRID
         jfy = (yy-jy)*CGRID + 0.499 ;
         val = (  p_m1[jfy] * f_jm1 + p_00[jfy] * f_j00
                + p_p1[jfy] * f_jp1 + p_p2[jfy] * f_jp2 ) * THIRTYSIX ;
#else
         fy  = yy-jy ;
         val = (  P_M1(fy) * f_jm1 + P_00(fy) * f_j00
                + P_P1(fy) * f_jp1 + P_P2(fy) * f_jp2 ) * THIRTYSIX ;
#endif

              if( val < bot ) nar[ii+jj*nx] = bot ;  /* too small! */
         else if( val > top ) nar[ii+jj*nx] = top ;  /* too big!   */
         else                 nar[ii+jj*nx] = val ;  /* just right */

      }
   }

   /*** cleanup and return ***/

   if( im != imfl ) mri_free(imfl) ;  /* throw away unneeded workspace */
   MRI_COPY_AUX(newImg,im) ;
   return newImg ;
}

/**-------------------------------------------------------------------
    Rotate and shift an image, using bilinear interpolation:
       aa  = shift in x
       bb  = shift in y
       phi = angle in radians
    Sort of a replacement for mri_rotate in mri_warp.c; supposed
    to be faster.  If the input image is MRI_complex, the output
    will be also; otherwise, the output will be MRI_float.
----------------------------------------------------------------------**/

MRI_IMAGE *mri_rota_bilinear( MRI_IMAGE *im, float aa, float bb, float phi )
{
   float rot_dx , rot_dy , rot_cph , rot_sph ;
   MRI_IMAGE *imfl , *newImg ;
   MRI_IMARR *impair ;
   float *far , *nar ;
   float xx,yy , fx,fy ;
   int ii,jj, nx,ny , ix,jy ;
   float f_j00,f_jp1 , wt_00,wt_p1 ;

   if( im == NULL || ! MRI_IS_2D(im) ){
      fprintf(stderr,"*** mri_rota_bilinear only works on 2D images!\n") ; EXIT(1) ;
   }

   /** if complex image, break into pairs, do each separately, put back together **/

   if( im->kind == MRI_complex ){
      MRI_IMARR *impair ;
      MRI_IMAGE * rim , * iim , * tim ;
      impair = mri_complex_to_pair( im ) ;
      if( impair == NULL ){
         fprintf(stderr,"*** mri_complex_to_pair fails in mri_rota!\n") ; EXIT(1) ;
      }
      rim = IMAGE_IN_IMARR(impair,0) ;
      iim = IMAGE_IN_IMARR(impair,1) ;  FREE_IMARR(impair) ;
      tim = mri_rota_bilinear( rim , aa,bb,phi ) ; mri_free( rim ) ; rim = tim ;
      tim = mri_rota_bilinear( iim , aa,bb,phi ) ; mri_free( iim ) ; iim = tim ;
      newImg = mri_pair_to_complex( rim , iim ) ;
      mri_free( rim ) ; mri_free( iim ) ;
      MRI_COPY_AUX(newImg,im) ;
      return newImg ;
   }

   /** rotation params **/

   rot_cph = cos(phi) ; rot_sph = sin(phi) ;

   rot_dx  = (0.5 * im->nx) * (1.0-rot_cph) - aa*rot_cph - bb*rot_sph
            -(0.5 * im->ny) * rot_sph ;

   rot_dy  = (0.5 * im->nx) * rot_sph + aa*rot_sph - bb*rot_cph
            +(0.5 * im->ny) * (1.0-rot_cph) ;

   /** other initialization **/

   nx = im->nx ;  /* image dimensions */
   ny = im->ny ;

   if( im->kind == MRI_float ) imfl = im ;
   else                        imfl = mri_to_float( im ) ;

   far = MRI_FLOAT_PTR(imfl) ;              /* access to float data */
   newImg = mri_new( nx , nx , MRI_float ) ;   /* output image */
   nar = MRI_FLOAT_PTR(newImg) ;               /* output image data */

   /*** loop over output points and warp to them ***/

   for( jj=0 ; jj < nx ; jj++ ){
      xx = rot_sph * jj + rot_dx - rot_cph ;
      yy = rot_cph * jj + rot_dy + rot_sph ;
      for( ii=0 ; ii < nx ; ii++ ){

         xx += rot_cph ;  /* get x,y in original image */
         yy -= rot_sph ;

         ix = (xx >= 0.0) ? ((int) xx) : ((int) xx)-1 ;  /* floor */
         jy = (yy >= 0.0) ? ((int) yy) : ((int) yy)-1 ;

         fx = xx-ix ; wt_00 = 1.0 - fx ; wt_p1 = fx ;

         if( ix >= 0 && ix < nx-1 && jy >= 0 && jy < ny-1 ){
            float *fy00 , *fyp1 ;

            fy00 = far + (ix + jy*nx) ; fyp1 = fy00 + nx ;

            f_j00 = wt_00 * fy00[0] + wt_p1 * fy00[1] ;
            f_jp1 = wt_00 * fyp1[0] + wt_p1 * fyp1[1] ;

         } else {
            f_j00 = wt_00 * FINS(ix,jy  ) + wt_p1 * FINS(ix+1,jy  ) ;
            f_jp1 = wt_00 * FINS(ix,jy+1) + wt_p1 * FINS(ix+1,jy+1) ;
         }

         fy  = yy-jy ; nar[ii+jj*nx] = (1.0-fy) * f_j00 + fy * f_jp1 ;

      }
   }

   /*** cleanup and return ***/

   if( im != imfl ) mri_free(imfl) ;  /* throw away unneeded workspace */
   MRI_COPY_AUX(newImg,im) ;
   return newImg ;
}

/*--------------------------------------------------------------------------
   Routines to rotate using FFTs and the shearing transformation
----------------------------------------------------------------------------*/

/*** Shift 2 rows at a time with the FFT ***/

void ft_shift2( int n, int nup, float af, float * f, float ag, float * g )
{
   static int nupold=0 ;
   static complex * row=NULL , * cf=NULL , * cg=NULL ;

   int ii , nby2=nup/2 , n21=nby2+1 ;
   complex fac , gac ;
   float sf , sg , dk ;

   /* make new memory for row storage? */

   if( nup > nupold ){
      if( row != NULL ){ free(row) ; free(cf) ; free(cg) ; }
      row = (complex *) malloc( sizeof(complex) * nup ) ;
      cf  = (complex *) malloc( sizeof(complex) * n21 ) ;
      cg  = (complex *) malloc( sizeof(complex) * n21 ) ;
      nupold = nup ;
   }

   /* FFT the pair of rows */

   for( ii=0 ; ii < n   ; ii++ ){ row[ii].r = f[ii] ; row[ii].i = g[ii] ; }
   for(      ; ii < nup ; ii++ ){ row[ii].r = row[ii].i = 0.0 ; }

   csfft_cox( -1 , nup , row ) ;

   /* untangle FFT coefficients from row into cf,cg */

   cf[0].r = 2.0 * row[0].r ; cf[0].i = 0.0 ;  /* twice too big */
   cg[0].r = 2.0 * row[0].i ; cg[0].i = 0.0 ;
   for( ii=1 ; ii < nby2 ; ii++ ){
      cf[ii].r =  row[ii].r + row[nup-ii].r ;
      cf[ii].i =  row[ii].i - row[nup-ii].i ;
      cg[ii].r =  row[ii].i + row[nup-ii].i ;
      cg[ii].i = -row[ii].r + row[nup-ii].r ;
   }
   cf[nby2].r = 2.0 * row[nby2].r ; cf[nby2].i = 0.0 ;
   cg[nby2].r = 2.0 * row[nby2].i ; cg[nby2].i = 0.0 ;

   /* phase shift both rows (cf,cg) */

   dk = (2.0*PI) / nup ;
   sf = -af * dk ; sg = -ag * dk ;
   for( ii=1 ; ii <= nby2 ; ii++ ){
      fac = CEXPIT(ii*sf) ; cf[ii] = CMULT( fac , cf[ii] ) ;
      gac = CEXPIT(ii*sg) ; cg[ii] = CMULT( gac , cg[ii] ) ;
   }
   cf[nby2].i = 0.0 ; cg[nby2].i = 0.0 ;

   /* re-tangle the coefficients from 2 rows */

   row[0].r = cf[0].r ; row[0].i = cg[0].r ;
   for( ii=1 ; ii < nby2 ; ii++ ){
      row[ii].r     =  cf[ii].r - cg[ii].i ;
      row[ii].i     =  cf[ii].i + cg[ii].r ;
      row[nup-ii].r =  cf[ii].r + cg[ii].i ;
      row[nup-ii].i = -cf[ii].i + cg[ii].r ;
   }
   row[nby2].r = cf[nby2].r ;
   row[nby2].i = cg[nby2].r ;

   /* inverse FFT and store back in output arrays */

   csfft_cox( 1 , nup , row ) ;

   sf = 0.5 / nup ;              /* 0.5 to allow for twice too big above */
   for( ii=0 ; ii < n ; ii++ ){
      f[ii] = sf * row[ii].r ; g[ii] = sf * row[ii].i ;
   }

   return ;
}

/**-------------------------------------------------------------------**/
/*** Shear in the x-direction ***/

void ft_xshear( float a , float b , int nx , int ny , float * f )
{
   int jj , nxup ;
   float * fj0 , * fj1 , * zz=NULL ;
   float a0 , a1 ;

   if( a == 0.0 && b == 0.0 ) return ;          /* nothing to do */
   if( nx < 2 || ny < 1 || f == NULL ) return ; /* nothing to operate on */

   if( ny%2 == 1 ){                               /* we work in pairs, so */
      zz = (float *) malloc( sizeof(float)*nx ) ; /* if not an even number */
      for( jj=0 ; jj < nx ; jj++ ) zz[jj] = 0.0 ; /* of rows, make an extra */
   }

   nxup = nx ;                                 /* min FFT length */
   jj   = 2 ; while( jj < nxup ){ jj *= 2 ; }  /* next power of 2 larger */
   nxup = jj ;

   for( jj=0 ; jj < ny ; jj+=2 ){              /* shear rows in pairs */
      fj0 = f + (jj*nx) ;                      /* row 0 */
      fj1 = (jj < ny-1) ? (fj0 + nx) : zz ;    /* row 1 */
      a0  = a*(jj-0.5*ny) + b ;                /* phase ramp for row 0 */
      a1  = a0 + a ;                           /* phase ramp for row 1 */
      ft_shift2( nx , nxup , a0 , fj0 , a1 , fj1 ) ;
   }

   if( zz != NULL ) free(zz) ; /* toss the trash */
   return ;
}

/**-------------------------------------------------------------------**/
/*** Shear in the y direction ***/

void ft_yshear( float a , float b , int nx , int ny , float * f )
{
   int jj , nyup , ii ;
   float * fj0 , * fj1 ;
   float a0 , a1 ;

   if( a == 0.0 && b == 0.0 ) return ;          /* nothing to do */
   if( ny < 2 || nx < 1 || f == NULL ) return ; /* nothing to operate on */

   /* make memory for a pair of columns */

   fj0 = (float *) malloc( sizeof(float) * 2*ny ) ; fj1 = fj0 + ny ;

   nyup = ny ;                                 /* min FFT length */
   jj   = 2 ; while( jj < nyup ){ jj *= 2 ; }  /* next power of 2 larger */
   nyup = jj ;

   for( jj=0 ; jj < nx ; jj+=2 ){              /* shear rows in pairs */

      if( jj < nx-1 ){
         for( ii=0; ii < ny; ii++ ){ fj0[ii] = f[jj+ii*nx]; fj1[ii] = f[jj+1+ii*nx]; }
      } else {
         for( ii=0; ii < ny; ii++ ){ fj0[ii] = f[jj+ii*nx]; fj1[ii] = 0.0; }
      }

      a0  = a*(jj-0.5*nx) + b ;                /* phase ramp for row 0 */
      a1  = a0 + a ;                           /* phase ramp for row 1 */
      ft_shift2( ny , nyup , a0 , fj0 , a1 , fj1 ) ;

      if( jj < nx-1 ){
         for( ii=0; ii < ny; ii++ ){ f[jj+ii*nx] = fj0[ii]; f[jj+1+ii*nx] = fj1[ii]; }
      } else {
         for( ii=0; ii < ny; ii++ ){ f[jj+ii*nx] = fj0[ii]; }
      }
   }

   free(fj0) ; return ;
}

/**-------------------------------------------------------------------**/
/*** Image rotation using 3 shears ***/

MRI_IMAGE * mri_rota_shear( MRI_IMAGE *im, float aa, float bb, float phi )
{
   double cph , sph ;
   float a , b , bot,top ;
   MRI_IMAGE *flim ;
   float *flar ;
   int ii , nxy ;

   if( im == NULL || ! MRI_IS_2D(im) ){
      fprintf(stderr,"*** mri_rota_shear only works on 2D images!\n") ; EXIT(1) ;
   }

   /** if complex image, break into pairs, do each separately, put back together **/

   if( im->kind == MRI_complex ){
      MRI_IMARR *impair ;
      MRI_IMAGE * rim , * iim , * tim ;
      impair = mri_complex_to_pair( im ) ;
      if( impair == NULL ){
         fprintf(stderr,"*** mri_complex_to_pair fails in mri_rota!\n") ; EXIT(1) ;
      }
      rim  = IMAGE_IN_IMARR(impair,0) ;
      iim  = IMAGE_IN_IMARR(impair,1) ;  FREE_IMARR(impair) ;
      tim  = mri_rota_shear( rim , aa,bb,phi ) ; mri_free( rim ) ; rim = tim ;
      tim  = mri_rota_shear( iim , aa,bb,phi ) ; mri_free( iim ) ; iim = tim ;
      flim = mri_pair_to_complex( rim , iim ) ;
      mri_free( rim ) ; mri_free( iim ) ;
      MRI_COPY_AUX(flim,im) ;
      return flim ;
   }

   /** copy input to output **/

   flim = mri_to_float( im ) ;
   flar = MRI_FLOAT_PTR( flim ) ;

   /* find range of image data */

   bot = top = flar[0] ; nxy = im->nx * im->ny ;
   for( ii=1 ; ii < nxy ; ii++ )
           if( flar[ii] < bot ) bot = flar[ii] ;
      else if( flar[ii] > top ) top = flar[ii] ;

   /** rotation params **/

   cph = cos(phi) ; sph = sin(phi) ;

   /* More than 90 degrees?
      Must be reduced to less than 90 degrees by a 180 degree flip. */

   if( cph < 0.0 ){
      int ii , jj , top , nx=flim->nx , ny=flim->ny ;
      float val ;

      top = (nx+1)/2 ;
      for( jj=0 ; jj < ny ; jj++ ){
         for( ii=1 ; ii < top ; ii++ ){
            val               = flar[jj*nx+ii] ;
            flar[jj*nx+ii]    = flar[jj*nx+nx-ii] ;
            flar[jj*nx+nx-ii] = val ;
         }
      }

      top = (ny+1)/2 ;
      for( ii=0 ; ii < nx ; ii++ ){
         for( jj=1 ; jj < top ; jj++ ){
            val                 = flar[ii+jj*nx] ;
            flar[ii+jj*nx]      = flar[ii+(ny-jj)*nx] ;
            flar[ii+(ny-jj)*nx] = val ;
         }
      }

      cph = -cph ; sph = -sph ;
   }

   /* compute shear factors for each direction */

   b = sph ;
   a = (b != 0.0 ) ? ((cph - 1.0) / b) : (0.0) ;

   /* shear thrice */

   ft_xshear( a , 0.0       , im->nx , im->ny , flar ) ;
   ft_yshear( b , bb        , im->nx , im->ny , flar ) ;
   ft_xshear( a , aa - a*bb , im->nx , im->ny , flar ) ;

   /* make sure data does not go out of original range */

   for( ii=0 ; ii < nxy ; ii++ )
           if( flar[ii] < bot ) flar[ii] = bot ;
      else if( flar[ii] > top ) flar[ii] = top ;

   return flim ;
}

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

MRI_IMAGE * mri_rota_variable( int mode, MRI_IMAGE *im, float aa, float bb, float phi )
{
   switch(mode){

      default:
      case MRI_BICUBIC:  return mri_rota( im,aa,bb,phi ) ;

      case MRI_BILINEAR: return mri_rota_bilinear( im,aa,bb,phi ) ;

      case MRI_FOURIER:  return mri_rota_shear( im,aa,bb,phi ) ;
   }
}
