/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkWindowToImageFilter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkWindowToImageFilter.h"

#include "vtkCamera.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkRenderWindow.h"
#include "vtkRendererCollection.h"
#include "vtkStreamingDemandDrivenPipeline.h"

#include "vtkCoordinate.h"
#include "vtkActor2D.h"
#include "vtkActor2DCollection.h"
#include <vtkstd/vector>

vtkCxxRevisionMacro(vtkWindowToImageFilter, "1.47");
vtkStandardNewMacro(vtkWindowToImageFilter);

class vtkWTI2DHelperClass
{
public:
  // maintain a list of 2D actors
  vtkActor2DCollection *StoredActors;
  // maintain lists of their vtkCoordinate objects
  vtkCollection        *Coord1s;
  vtkCollection        *Coord2s;
  // Store the display coords for adjustment during tiling
  vtkstd::vector< vtkstd::pair<int, int> > Coords1;
  vtkstd::vector< vtkstd::pair<int, int> > Coords2;
  //
  vtkWTI2DHelperClass()
    {
    this->StoredActors = vtkActor2DCollection::New();
    this->Coord1s = vtkCollection::New();
    this->Coord2s = vtkCollection::New();
    }
  ~vtkWTI2DHelperClass()
    {
    this->Coord1s->RemoveAllItems();
    this->Coord2s->RemoveAllItems();
    this->StoredActors->RemoveAllItems();
    this->Coord1s->Delete();
    this->Coord2s->Delete();
    this->StoredActors->Delete();
    }
};

//----------------------------------------------------------------------------
vtkWindowToImageFilter::vtkWindowToImageFilter()
{
  this->Input = NULL;
  this->Magnification = 1;
  this->ReadFrontBuffer = 1;
  this->ShouldRerender = 1;
  this->Viewport[0] = 0;
  this->Viewport[1] = 0;
  this->Viewport[2] = 1;
  this->Viewport[3] = 1;
  this->InputBufferType = VTK_RGB;

  this->SetNumberOfInputPorts(0);
  this->SetNumberOfOutputPorts(1);
  this->StoredData = new vtkWTI2DHelperClass;
}

//----------------------------------------------------------------------------
vtkWindowToImageFilter::~vtkWindowToImageFilter()
{
  if (this->Input)
    {
    this->Input->UnRegister(this);
    this->Input = NULL;
    }
  delete this->StoredData;
}

//----------------------------------------------------------------------------
vtkImageData* vtkWindowToImageFilter::GetOutput()
{
  return vtkImageData::SafeDownCast(this->GetOutputDataObject(0));
}

//----------------------------------------------------------------------------
void vtkWindowToImageFilter::SetInput(vtkWindow *input)
{
  if (input != this->Input)
    {
    if (this->Input) {this->Input->UnRegister(this);}
    this->Input = input;
    if (this->Input) {this->Input->Register(this);}
    this->Modified();
    }
}

//----------------------------------------------------------------------------
void vtkWindowToImageFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);

  if ( this->Input )
    {
    os << indent << "Input:\n";
    this->Input->PrintSelf(os,indent.GetNextIndent());
    }
  else
    {
    os << indent << "Input: (none)\n";
    }
  os << indent << "ReadFrontBuffer: " << this->ReadFrontBuffer << "\n";
  os << indent << "Magnification: " << this->Magnification << "\n";
  os << indent << "ShouldRerender: " << this->ShouldRerender << "\n";
  os << indent << "Viewport: " << this->Viewport[0] << "," << this->Viewport[1]
     << "," << this->Viewport[2] << "," << this->Viewport[3] << "\n";
  os << indent << "InputBufferType: " << this->InputBufferType << "\n";
}


//----------------------------------------------------------------------------
// This method returns the largest region that can be generated.
void vtkWindowToImageFilter::RequestInformation (
  vtkInformation * vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed( inputVector ),
  vtkInformationVector *outputVector)
{
  if (this->Input == NULL )
    {
    vtkErrorMacro(<<"Please specify a renderer as input!");
    return;
    }

  if(this->Magnification > 1 &&
     (this->Viewport[0] != 0 || this->Viewport[1] != 0 ||
      this->Viewport[2] != 1 || this->Viewport[3] != 1))
    {
    vtkWarningMacro(<<"Viewport extents are not used when Magnification > 1");
    this->Viewport[0] = 0;
    this->Viewport[1] = 0;
    this->Viewport[2] = 1;
    this->Viewport[3] = 1;
    }


  // set the extent
  int *size = this->Input->GetSize();
  int wExtent[6];
  wExtent[0]= 0;
  wExtent[1] = int((this->Viewport[2] - this->Viewport[0])*size[0] + 0.5)*
    this->Magnification - 1;
  wExtent[2] = 0;
  wExtent[3] = int((this->Viewport[3] - this->Viewport[1])*size[1] + 0.5)*
    this->Magnification - 1;
  wExtent[4] = 0;
  wExtent[5] = 0;

  // get the info objects
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), wExtent, 6);

  switch( this->InputBufferType )
    {
    case VTK_RGB:
      vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_UNSIGNED_CHAR, 3);
      break;
    case VTK_RGBA:
      vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_UNSIGNED_CHAR, 4);
      break;
    case VTK_ZBUFFER:
      vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_FLOAT, 1);
      break;
    default:
      // VTK_RGB configuration by default
      vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_UNSIGNED_CHAR, 3);
      break;
    }
}

//----------------------------------------------------------------------------
int vtkWindowToImageFilter::ProcessRequest(vtkInformation* request,
                                           vtkInformationVector** inputVector,
                                           vtkInformationVector* outputVector)
{
  // generate the data
  if(request->Has(vtkDemandDrivenPipeline::REQUEST_DATA()))
    {
    this->RequestData(request, inputVector, outputVector);
    return 1;
    }

  // execute information
  if(request->Has(vtkDemandDrivenPipeline::REQUEST_INFORMATION()))
    {
    this->RequestInformation(request, inputVector, outputVector);
    return 1;
    }

  return this->Superclass::ProcessRequest(request, inputVector, outputVector);
}


//----------------------------------------------------------------------------
// This function reads a region from a file.  The regions extent/axes
// are assumed to be the same as the file extent/order.
void vtkWindowToImageFilter::RequestData(
  vtkInformation* vtkNotUsed( request ),
  vtkInformationVector** vtkNotUsed( inputVector ),
  vtkInformationVector* outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  vtkImageData *out =
    vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));
  out->SetExtent(
    outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()));
  out->AllocateScalars();

  if (!this->Input)
    {
    return;
    }

  int outIncrY;
  int size[2],winsize[2];
  int idxY, rowSize;
  int i;

  if (! ((out->GetScalarType() == VTK_UNSIGNED_CHAR &&
        (this->InputBufferType == VTK_RGB || this->InputBufferType == VTK_RGBA)) ||
        (out->GetScalarType() == VTK_FLOAT && this->InputBufferType == VTK_ZBUFFER)))
    {
    vtkErrorMacro("mismatch in scalar types!");
    return;
    }

  // get the size of the render window
  winsize[0] = this->Input->GetSize()[0];
  winsize[1] = this->Input->GetSize()[1];

  size[0] = int(winsize[0]*(this->Viewport[2]-this->Viewport[0]) + 0.5);
  size[1] = int(winsize[1]*(this->Viewport[3]-this->Viewport[1]) + 0.5);

  rowSize = size[0]*out->GetNumberOfScalarComponents();
  outIncrY = size[0]*this->Magnification*out->GetNumberOfScalarComponents();

  float *viewAngles;
  double *windowCenters;
  vtkRenderWindow *renWin = vtkRenderWindow::SafeDownCast(this->Input);
  if (!renWin)
    {
    vtkWarningMacro(
      "The window passed to window to image should be a RenderWindow or one of its subclasses");
    return;
    }

  vtkRendererCollection *rc = renWin->GetRenderers();
  vtkRenderer *aren;
  vtkCamera *cam;
  int numRenderers = rc->GetNumberOfItems();

  // for each renderer
  vtkCamera **cams = new vtkCamera *[numRenderers];
  viewAngles = new float [numRenderers];
  windowCenters = new double [numRenderers*2];
  double *parallelScale = new double [numRenderers];
  vtkCollectionSimpleIterator rsit;
  rc->InitTraversal(rsit);
  for (i = 0; i < numRenderers; ++i)
    {
    aren = rc->GetNextRenderer(rsit);
    cams[i] = aren->GetActiveCamera();
    cams[i]->Register(this);
    cams[i]->GetWindowCenter(windowCenters+i*2);
    viewAngles[i] = cams[i]->GetViewAngle();
    parallelScale[i] = cams[i]->GetParallelScale();
    cam = cams[i]->NewInstance();
    cam->SetPosition(cams[i]->GetPosition());
    cam->SetFocalPoint(cams[i]->GetFocalPoint());
    cam->SetViewUp(cams[i]->GetViewUp());
    cam->SetClippingRange(cams[i]->GetClippingRange());
    cam->SetParallelProjection(cams[i]->GetParallelProjection());
    cam->SetFocalDisk(cams[i]->GetFocalDisk());
    cam->SetUserTransform(cams[i]->GetUserTransform());
    cam->SetViewShear(cams[i]->GetViewShear());
    aren->SetActiveCamera(cam);
    }

  // render each of the tiles required to fill this request
  this->Input->SetTileScale(this->Magnification);
  this->Input->GetSize();

  //this->Rescale2DActors();
  int x, y;
  for (y = 0; y < this->Magnification; y++)
    {
    for (x = 0; x < this->Magnification; x++)
      {
      // setup the Window ivars
      this->Input->SetTileViewport((double)x/this->Magnification,
                                   (double)y/this->Magnification,
                                   (x+1.0)/this->Magnification,
                                   (y+1.0)/this->Magnification);
      double *tvp = this->Input->GetTileViewport();

      // for each renderer, setup camera
      rc->InitTraversal(rsit);
      for (i = 0; i < numRenderers; ++i)
        {
        aren = rc->GetNextRenderer(rsit);
        cam = aren->GetActiveCamera();
        double *vp = aren->GetViewport();
        double visVP[4];
        visVP[0] = (vp[0] < tvp[0]) ? tvp[0] : vp[0];
        visVP[0] = (visVP[0] > tvp[2]) ? tvp[2] : visVP[0];
        visVP[1] = (vp[1] < tvp[1]) ? tvp[1] : vp[1];
        visVP[1] = (visVP[1] > tvp[3]) ? tvp[3] : visVP[1];
        visVP[2] = (vp[2] > tvp[2]) ? tvp[2] : vp[2];
        visVP[2] = (visVP[2] < tvp[0]) ? tvp[0] : visVP[2];
        visVP[3] = (vp[3] > tvp[3]) ? tvp[3] : vp[3];
        visVP[3] = (visVP[3] < tvp[1]) ? tvp[1] : visVP[3];

        // compute magnification
        double mag = (visVP[3] - visVP[1])/(vp[3] - vp[1]);
        // compute the delta
        double deltax = (visVP[2] + visVP[0])/2.0 - (vp[2] + vp[0])/2.0;
        double deltay = (visVP[3] + visVP[1])/2.0 - (vp[3] + vp[1])/2.0;
        // scale by original window size
        if (visVP[2] - visVP[0] > 0)
          {
          deltax = 2.0*deltax/(visVP[2] - visVP[0]);
          }
        if (visVP[3] - visVP[1] > 0)
          {
          deltay = 2.0*deltay/(visVP[3] - visVP[1]);
          }
        cam->SetWindowCenter(deltax,deltay);
        //cam->SetViewAngle(atan(tan(viewAngles[i]*3.1415926/360.0)*mag)  //FIXME
        cam->SetViewAngle(asin(sin(viewAngles[i]*3.1415926/360.0)*mag)
                          * 360.0 / 3.1415926);
        cam->SetParallelScale(parallelScale[i]*mag);
        }

      // Shift 2d actors just before rendering
      //this->Shift2DActors(size[0]*x, size[1]*y);
      // now render the tile and get the data
      if (this->ShouldRerender || this->Magnification > 1)
        {
        this->Input->Render();
        }

      int buffer = this->ReadFrontBuffer;
      if(!this->Input->GetDoubleBuffer())
        {
        buffer = 1;
        }
      if (this->InputBufferType == VTK_RGB || this->InputBufferType == VTK_RGBA)
        {
        unsigned char *pixels, *pixels1, *outPtr;
        if (this->InputBufferType == VTK_RGB)
          {
          pixels =
            this->Input->GetPixelData(int(this->Viewport[0]* winsize[0]),
                                      int(this->Viewport[1]* winsize[1]),
                                      int(this->Viewport[2]* winsize[0] + 0.5) - 1,  
                                      int(this->Viewport[3]* winsize[1] + 0.5) - 1, buffer);
          }
        else
          {
          pixels =
            renWin->GetRGBACharPixelData(int(this->Viewport[0]* winsize[0]),
                                         int(this->Viewport[1]* winsize[1]),
                                         int(this->Viewport[2]* winsize[0] + 0.5) - 1,  
                                         int(this->Viewport[3]* winsize[1] + 0.5) - 1, buffer);

          }

        pixels1 = pixels;

        // now write the data to the output image
        outPtr =
          (unsigned char *)out->GetScalarPointer(x*size[0],y*size[1], 0);
        for (idxY = 0; idxY < size[1]; idxY++)
          {
          memcpy(outPtr,pixels1,rowSize);
          outPtr += outIncrY;
          pixels1 += rowSize;
          }

        // free the memory
        delete [] pixels;
        }
      else
        { // VTK_ZBUFFER
        float *pixels, *pixels1, *outPtr;
        pixels = renWin->GetZbufferData(int(this->Viewport[0]* winsize[0]),
                                        int(this->Viewport[1]* winsize[1]),
                                        int(this->Viewport[2]* winsize[0] + 0.5) - 1,  
                                        int(this->Viewport[3]* winsize[1] + 0.5) - 1);

        pixels1 = pixels;

        // now write the data to the output image
        outPtr =
          (float *)out->GetScalarPointer(x*size[0],y*size[1], 0);
        for (idxY = 0; idxY < size[1]; idxY++)
          {
          memcpy(outPtr,pixels1,rowSize*sizeof(float));
          outPtr += outIncrY;
          pixels1 += rowSize;
          }

        // free the memory
        delete [] pixels;
        }
      }
    }

  // restore settings
  // for each renderer
  rc->InitTraversal(rsit);
  for (i = 0; i < numRenderers; ++i)
    {
    aren = rc->GetNextRenderer(rsit);
    // store the old view angle & set the new
    cam = aren->GetActiveCamera();
    aren->SetActiveCamera(cams[i]);
    cams[i]->UnRegister(this);
    cam->Delete();
    }
  delete [] viewAngles;
  delete [] windowCenters;
  delete [] parallelScale;
  delete [] cams;

  // render each of the tiles required to fill this request
  this->Input->SetTileScale(1);
  this->Input->SetTileViewport(0.0,0.0,1.0,1.0);
  this->Input->GetSize();
  // restore every 2d actors
  //this->Restore2DActors();
}

//----------------------------------------------------------------------------
// On each tile we must subtract the origin of each actor to ensure
// it appears in the corrrect relative location
void vtkWindowToImageFilter::Restore2DActors()
{
  vtkActor2D            *actor;
  vtkCoordinate         *c1, *c2;
  vtkCoordinate         *n1, *n2;
  int i;
  //
  for (this->StoredData->StoredActors->InitTraversal(), i=0;
    (actor = this->StoredData->StoredActors->GetNextItem()); i++)
    {
    c1 = actor->GetPositionCoordinate();
    c2 = actor->GetPosition2Coordinate();
    n1 = vtkCoordinate::SafeDownCast(this->StoredData->Coord1s->GetItemAsObject(i));
    n2 = vtkCoordinate::SafeDownCast(this->StoredData->Coord2s->GetItemAsObject(i));
    c1->SetCoordinateSystem(n1->GetCoordinateSystem());
    c1->SetReferenceCoordinate(n1->GetReferenceCoordinate());
    c1->SetReferenceCoordinate(n1->GetReferenceCoordinate());
    c1->SetValue(n1->GetValue());
    c2->SetCoordinateSystem(n2->GetCoordinateSystem());
    c2->SetReferenceCoordinate(n2->GetReferenceCoordinate());
    c2->SetValue(n2->GetValue());
    }
  this->StoredData->Coord1s->RemoveAllItems();
  this->StoredData->Coord2s->RemoveAllItems();
  this->StoredData->StoredActors->RemoveAllItems();
}

//----------------------------------------------------------------------------
void vtkWindowToImageFilter::Rescale2DActors()
{
  vtkActor2D            *actor;
  vtkProp               *aProp;
  vtkRenderer           *aren;
  vtkPropCollection     *pc;
  vtkRendererCollection *rc;
  vtkCoordinate         *c1, *c2;
  vtkCoordinate         *n1, *n2;
  int                   *p1, *p2;
  double                d1[3], d2[3];
  //
  vtkRenderWindow *renWin = vtkRenderWindow::SafeDownCast(this->Input);
  rc = renWin->GetRenderers();
  for (rc->InitTraversal(); (aren = rc->GetNextItem()); )
    {
    pc = aren->GetViewProps();
    if (pc)
      {
      for ( pc->InitTraversal(); (aProp = pc->GetNextProp()); )
        {
        actor = vtkActor2D::SafeDownCast((aProp));
        if (actor)
          {
          // put the actor in our list for retrieval later
          this->StoredData->StoredActors->AddItem(actor);
          // Copy all existing coordinate stuff
          n1 = actor->GetPositionCoordinate();
          n2 = actor->GetPosition2Coordinate();
          c1 = vtkCoordinate::New();
          c2 = vtkCoordinate::New();
          c1->SetCoordinateSystem(n1->GetCoordinateSystem());
          c1->SetReferenceCoordinate(n1->GetReferenceCoordinate());
          c1->SetReferenceCoordinate(n1->GetReferenceCoordinate());
          c1->SetValue(n1->GetValue());
          c2->SetCoordinateSystem(n2->GetCoordinateSystem());
          c2->SetReferenceCoordinate(n2->GetReferenceCoordinate());
          c2->SetValue(n2->GetValue());
          this->StoredData->Coord1s->AddItem(c1);
          this->StoredData->Coord2s->AddItem(c2);
          c1->Delete();
          c2->Delete();
          // work out the position in new magnified pixels
          p1 = n1->GetComputedDisplayValue(aren);
          p2 = n2->GetComputedDisplayValue(aren);
          d1[0] = p1[0]*this->Magnification;
          d1[1] = p1[1]*this->Magnification;
          d1[2] = 0.0;
          d2[0] = p2[0]*this->Magnification;
          d2[1] = p2[1]*this->Magnification;
          d2[2] = 0.0;
          this->StoredData->Coords1.push_back(
            vtkstd::pair<int, int>(static_cast<int>(d1[0]), static_cast<int>(d1[1])) );
          this->StoredData->Coords2.push_back(
            vtkstd::pair<int, int>(static_cast<int>(d2[0]), static_cast<int>(d2[1])) );
          // Make sure they have no dodgy offsets
          n1->SetCoordinateSystemToDisplay();
          n2->SetCoordinateSystemToDisplay();
          n1->SetReferenceCoordinate(NULL);
          n2->SetReferenceCoordinate(NULL);
          n1->SetValue(d1[0], d1[1]);
          n2->SetValue(d2[0], d2[1]);
          //
          }
        }
      }
    }
}
//----------------------------------------------------------------------------
// On each tile we must subtract the origin of each actor to ensure
// it appears in the correct relative location
void vtkWindowToImageFilter::Shift2DActors(int x, int y)
{
  vtkActor2D    *actor;
  vtkCoordinate *c1, *c2;
  double        d1[3], d2[3];
  int           i;
  //
  for (this->StoredData->StoredActors->InitTraversal(), i=0;
    (actor = this->StoredData->StoredActors->GetNextItem()); i++)
    {
    c1 = actor->GetPositionCoordinate();
    c2 = actor->GetPosition2Coordinate();
    c1->GetValue(d1);
    c2->GetValue(d2);
    d1[0] = this->StoredData->Coords1[i].first  - x;
    d1[1] = this->StoredData->Coords1[i].second - y + 1;
    d2[0] = this->StoredData->Coords2[i].first  - x;
    d2[1] = this->StoredData->Coords2[i].second - y + 1;
    c1->SetValue(d1);
    c2->SetValue(d2);
    }
}

//----------------------------------------------------------------------------
int vtkWindowToImageFilter::FillOutputPortInformation(
  int vtkNotUsed(port), vtkInformation* info)
{
  // now add our info
  info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkImageData");
  return 1;
}
