/************************************************************************/
/*                                                                      */
/*    vspline - a set of generic tools for creation and evaluation      */
/*              of uniform b-splines                                    */
/*                                                                      */
/*            Copyright 2015 - 2023 by Kay F. Jahnke                    */
/*                                                                      */
/*    Permission is hereby granted, free of charge, to any person       */
/*    obtaining a copy of this software and associated documentation    */
/*    files (the "Software"), to deal in the Software without           */
/*    restriction, including without limitation the rights to use,      */
/*    copy, modify, merge, publish, distribute, sublicense, and/or      */
/*    sell copies of the Software, and to permit persons to whom the    */
/*    Software is furnished to do so, subject to the following          */
/*    conditions:                                                       */
/*                                                                      */
/*    The above copyright notice and this permission notice shall be    */
/*    included in all copies or substantial portions of the             */
/*    Software.                                                         */
/*                                                                      */
/*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND    */
/*    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES   */
/*    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND          */
/*    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT       */
/*    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,      */
/*    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      */
/*    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR     */
/*    OTHER DEALINGS IN THE SOFTWARE.                                   */
/*                                                                      */
/************************************************************************/

/// \file slice2.cc
///
/// \brief create 2D image data from a 3D spline
///
/// build a 3D volume from samples of the RGB colour colour_space
/// build a spline over it and extract a 2D slice, using vspline::transform()
///
/// while the result is just about the same as the one we get from slice.cc,
/// here we use additional functors to create the colour gradient and do the
/// coordinate transformation.
///
/// compile with:
/// clang++ -std=c++11 -march=native -o slice2 -O3 -pthread -DUSE_VC=1 slice2.cc -lvigraimpex -lVc
/// or: clang++ -std=c++11 -march=native -o slice2 -O3 -pthread slice2.cc -lvigraimpex
/// g++ also works.

#include <iostream>

#include <vspline/vspline.h>

#include <vigra/stdimage.hxx>
#include <vigra/imageinfo.hxx>
#include <vigra/impex.hxx>

// pixel_type is the result type, an RGB float pixel

typedef vigra::RGBValue < unsigned char , 0 , 1 , 2 > pixel_type ;

// voxel_type is the source data type, here we're using double precision

typedef vigra::TinyVector < double , 3 > voxel_type ;

// coordinate2_type has a 2D coordinate

typedef vigra::TinyVector < float , 2 > coordinate2_type ;

// coordinate3_type has a 3D coordinate

typedef vigra::TinyVector < float , 3 > coordinate3_type ;

// target_type is a 2D array of pixels  

typedef vigra::MultiArray < 2 , pixel_type > target_type ;

// we'll use a common vectorization width of 8 throughout

enum { VSIZE = 8 } ;

// we'll use a functor to create the gradient in the b-spline

struct calculate_gradient_type
: public vspline::unary_functor < coordinate3_type , voxel_type , VSIZE >
{
  // this method generates a voxel from a 3D coordinate.
  
  template < class IN , class OUT >
  void eval ( const IN & c , OUT & result ) const
  {
    // assign input to output and scale
    
    result = c ;
    result *= 25.5 ;
    
    // because we don't have the relevant vigra numeric and promote traits,
    // we *can't* write the obvious
    
    // result = 25.5 * c ;
  } ;
} ;

// type of b-spline evaluator producing pixels from 3D coordinates
// here we pass all template arguments an evaluator can take:
// - coordinate3_type for the type of incoming 3D coordinates
// - pixel_type for the data type we want to receive as result
// - VSIZE for the vectorization width
// - -1 indicates we want unspecialized b-spline evaluation
// - double: we want internal calculations done in double precision
// - voxel_type will be the type of coefficients held in the spline

typedef vspline::evaluator < coordinate3_type ,
                             pixel_type ,
                             VSIZE ,
                             -1 ,
                             double ,
                             voxel_type
                           > ev_type ;

// this functor is used for the coordinate transformation. It receives
// 2D coordinates (discrete target coordinates) and produces 3D
// coordinates which will be used to evaluate the spline. We pass
// the vectorization width explicitly to make sure it's the same
// as that used by the evaluator; if this weren't the case we could
// not 'chain' them further down

struct calculate_pickup_type
: public vspline::unary_functor < coordinate2_type , coordinate3_type , VSIZE >
{
  // this method transforms incoming discrete 2D coordinates - coordinates
  // pertaining to pixels in the target image - into 3D 'pick-up' coordinates
  // at which to evaluate the spline. Note how it's written as a template,
  // since the code for unvectorized and vectorized evaluation is just
  // the same. Note how we have coded coordinate2_type as consisting of
  // two floats, rather than two ints, which would have been just as well,
  // but which would have required transforming 'c' to it's floating point
  // equivalent before doing the maths. The 'index-based' version of
  // vspline::transform will feed the functor with the type it expects as
  // it's incoming type, so coordinate2_type in this case - or it's
  // vectorized equivalent.
  
  template < class IN , class OUT >
  void eval ( const IN & c , OUT & result ) const
  {
    result[0] = c[0] / 192.0f ;
    result[1] = 10.0f - result[0] ;
    result[2] = c[1] / 108.0f ;
  } ;
} ;

int main ( int argc , char * argv[] )
{
  // we want a b-spline with natural boundary conditions
  
  vigra::TinyVector < vspline::bc_code , 3 > bcv ( vspline::NATURAL ) ;
  
  // create quintic 3D b-spline object containing voxels
  // note the shape of the spline: it's ten units wide in each direction.
  // this explains the factor 25.5 used to calculate the voxels it holds:
  // the voxel's values will go from 0 to 255 for each channel
  
  vspline::bspline < voxel_type , 3 >
    colour_space ( vigra::Shape3 ( 10 , 10 , 10 ) , 5 , bcv ) ;
  
  // this functor will calculate the colour cube's content:
    
  calculate_gradient_type gradient ;
  
  // we could instead use vspline's 'amplify_type' to the same effect:

//   vspline::amplify_type < voxel_type , voxel_type , voxel_type , VSIZE >
//            gradient ( voxel_type ( 25.5 ) ) ;
  
  // now we run an index-based transform on the spline's 'core'. This will
  // feed successive 3D coordinates to 'gradient', which will calculate
  // voxel values from them

  vspline::transform ( gradient , colour_space.core ) ;
  
  // prefilter the b-spline
  
  colour_space.prefilter() ;
  
  // create the coordinate transformation functor
  
  calculate_pickup_type pick ;
  
  // get an evaluator for the b-spline
  
  ev_type ev ( colour_space ) ;
  
  // 'chain' the coordinate transformation functor and the evaluator
  
  auto combined = vspline::chain ( pick , ev ) ;
  
  // this is where the result should go:
  
  target_type target ( vigra::Shape2 ( 1920 , 1080 ) ) ;

  // now we perform the transform, yielding the result
  // note how we use a 'index-based' transform feeding the functor
  // (combined) with discrete target coordinates. Inside 'combined',
  // the incoming discrete coordinate is first transformed to the
  // 'pick-up' coordinate, which is in turn used to evaluate the
  // spline, yielding the result, which is stored in 'target'.
  
  vspline::transform ( combined , target ) ;

  // store the result with vigra impex
  
  vigra::ImageExportInfo imageInfo ( "slice.tif" );
  
  vigra::exportImage ( target ,
                      imageInfo
                      .setPixelType("UINT8")
                      .setCompression("100")
                      .setForcedRangeMapping ( 0 , 255 , 0 , 255 ) ) ;
  
  std::cout << "result was written to slice.tif" << std::endl ;
  exit ( 0 ) ;
}
