/**
  \class CDCTPanel
  \brief This class will compress or decompress a YUV panel using the DCT
  
  This class does both forward and inverse DCT conversion of a YUV panel;
  the direction is specified at creation time in the constructor. In theory
  InverseDCT(ForwardDCT(yuv)) = yuv, that is, an image can be converted to
  DCT and back to its original.
*/

#include <stdio.h>

#include "DCTPanel.h"

/**
  \brief Constructor.
  \param base_panel The yuv panel we use as our basis for calculation.
  \param inv When true, this class performs the IDCT, otherwise FDCT
  \param name Name for this panel
  \param desc Description for this panel
  
 */  
CDCTPanel::CDCTPanel(CCamPanel *base_panel, bool inv, const char *name, const char *desc)
	: CCamPanel(name, desc, YUV420)
{
   pBasePanel = NULL;
   pTilesY = NULL;
   pTilesUV = NULL;
   TilesDefault = true;

   Inverse = inv;
   pBasePanel = base_panel;
   if (pBasePanel == NULL)
     return;
   if (pBasePanel->GetPanelType() != YUV420) {
     pBasePanel = NULL;
     return;
   }
   
   ConnectUsage(pBasePanel);
   ConnectResizes(pBasePanel);
   SetSize(pBasePanel->GetImageSize());
}

/**
  \brief Destructor

  Cleans up tiles.
*/

CDCTPanel::~CDCTPanel()
{
   if (TilesDefault) {
     delete pTilesY;
     delete pTilesUV;
   }
}

// private

/** 
  \brief Initialize CCamTile structures depending on image size
  
  The function creates the Tiles and initializes the image offsets.

  Each tile is 8x8 pixels; the input pixels are positioned in a linear
  block of 8x8 pixels, and so are the output pixels. You can set your own 
  coordinates by using SetTiles()

*/
void CDCTPanel::CreateTiles()
{
   int i, k, l, x, y;
   int w8, h8;

printf("CDCTPanel::CreateTiles(%d, %d)\n", image_w, image_h);
   tiles_y = (image_w * image_h) / 64;
   tiles_uv = tiles_y >> 2;

   delete pTilesY; // Clean up old tiles
   delete pTilesUV;
   pTilesY = new CCamTile[tiles_y];
   pTilesUV = new CCamTile[tiles_uv];
   if (pTilesY == NULL || pTilesUV == NULL)
     return;

   i = 0;
   w8 = image_w >> 3; // 1/8th
   h8 = image_h >> 3;
   for (y = 0; y < h8; y++) {
      for (x = 0; x < w8; x++) {
         /* Record location */ 
         pTilesY[i].x = x << 3;
         pTilesY[i].y = y << 3;
         /* Calculate offsets */
         for (l = 0; l < 8; l++) {
            for (k = 0; k < 8; k++) {
               /* 8x8 blocks */
               pTilesY[i].out_offsets[l][k] = pTilesY[i].in_offsets[l][k]  = ((y << 3) + l) * image_w + (x << 3) + k;
            }
         }
         i++;
      }
   }

   // Repeat for U/V tiles, which are only quarter size   
   i = 0;
   w8 = half_w >> 3; // 1/8th
   h8 = half_h >> 3;
   for (y = 0; y < h8; y++) {
      for (x = 0; x < w8; x++) {
         /* Record location */ 
         pTilesUV[i].x = x << 3;
         pTilesUV[i].y = y << 3;
         /* Calculate offsets */
         for (l = 0; l < 8; l++) {
            for (k = 0; k < 8; k++) {
               /* 8x8 blocks */
               pTilesUV[i].out_offsets[l][k] = pTilesUV[i].in_offsets[l][k]  = ((y << 3) + l) * half_w + (x << 3) + k;
            }
         }
         i++;
      }
   }
}



// public

/**
  \brief Set tiles coordinates
  
  In case you want your own ordering of input-output pixels, or want to 
  share the arrays, use this function to set two arrays of CCamTiles
  for the Y and UV panels resp. If you supply two NULL pointers CDCTPanel
  switches back to its default tiles.
*/
  
void CDCTPanel::SetTiles(CCamTile *tilesy, CCamTile *tilesuv)
{
   TilesDefault = false;
   delete pTilesY;
   delete pTilesUV;
   pTilesY = tilesy;
   pTilesUV = tilesuv;
   if (pTilesY == NULL && pTilesUV == NULL) {
     TilesDefault = true;
     CreateTiles();
   }
}


// public slots

/**
  \brief Called when the base panel image is updated. Starts DCT calculation.
 */

void CDCTPanel::UpdatePanel()
{
   int t;
   CCamTile *pCurrent;
   uchar *src_buffer, *dst_buffer;

   if (pBasePanel == NULL)
     return;

   if (IsVisible() || IsUsed()) {
     /* Y panel */ 
     src_buffer = pBasePanel->GetImage(0).bits();
     dst_buffer = ImgY.bits();
     pCurrent = pTilesY;
     for (t = 0; t < tiles_y; t++) {
        if (Inverse)
          pCurrent->CalculateInverse(dst_buffer, src_buffer);
        else
          pCurrent->CalculateForward(dst_buffer, src_buffer);
        pCurrent++;
     }

     /* U panel */   
     src_buffer = pBasePanel->GetImage(1).bits();
     dst_buffer = ImgU.bits();
     pCurrent = pTilesUV;
     for (t = 0; t < tiles_uv; t++) {
        if (Inverse)
          pCurrent->CalculateInverse(dst_buffer, src_buffer);
        else
          pCurrent->CalculateForward(dst_buffer, src_buffer);
        pCurrent++;
     }

     /* V panel */
     src_buffer = pBasePanel->GetImage(2).bits();
     dst_buffer = ImgV.bits();
     pCurrent = pTilesUV;
     for (t = 0; t < tiles_uv; t++) {
        if (Inverse)
          pCurrent->CalculateInverse(dst_buffer, src_buffer);
        else
          pCurrent->CalculateForward(dst_buffer, src_buffer);
        pCurrent++;
     }
     
     emit Updated();
   }
}


/** 
  \brief [overloaded] Set size, create tiles
 */
void CDCTPanel::SetSize(const QSize &ns)
{
   CCamPanel::SetSize(ns);
   if (TilesDefault)
     CreateTiles();
}
