/****************************************************************************\
*                                                                            *
*  ENGINE8.CPP - Source Code Module                                          *
*                                                                            *
*  (c)Copyright 2001 Indotek, LLC. All Rights Reserved.                      *
*                                                                            *
\****************************************************************************/

#include "internal.h"

// Clamp the input to the specified range
#define _R3D_CLAMP(v,l,h)   ((v)<(l) ? (l) : (v) > (h) ? (h) : v)

typedef unsigned char _r3d_Pixel;

typedef struct
{
   int Width; // horizontal size of the image in Pixels
   int Height; // vertical size of the image in Pixels
   _r3d_Pixel *data; // pointer to first scanline of image
   int pitch; // byte offset between two scanlines
} _r3d_Image;

_r3d_Image _r3d_Src, _r3d_DstR, _r3d_DstG, _r3d_DstB, _r3d_DstA;

#define _R3D_WHITE_PIXEL    (255)
#define _R3D_BLACK_PIXEL    (0)

typedef struct
{
   int pixel;
   float weight;
} _R3D_CONTRIB;

typedef struct
{
   int n; // number of contributors
   _R3D_CONTRIB *p; // pointer to list of contributions
} _R3D_CLIST;

_R3D_CLIST *_r3d_contrib; // array of contribution lists

#define _R3D_FILTER_SUPPORT     (1.0F)
#define _R3D_BOX_SUPPORT        (0.5F)
#define _R3D_TRIANGLE_SUPPORT   (1.0F)
#define _R3D_BELL_SUPPORT       (1.5F)
#define _R3D_B_SPLINE_SUPPORT   (2.0F)
#define _R3D_LANCZOS3_SUPPORT   (3.0F)
#define _R3D_MITCHELL_SUPPORT   (2.0F)

inline _r3d_Pixel
get_pixel(_r3d_Image *image, int x, int y)
{
   static _r3d_Image *im = NULL;
   static int yy = -1;
   static _r3d_Pixel *p = NULL;

   if ((x < 0) || (x >= image->Width) || (y < 0) || (y >= image->Height))
   {
      return (0);
   }
   if ((im != image) || (yy != y))
   {
      im = image;
      yy = y;
      p = image->data + (y * image->pitch);
   }
   return (p[x]);
}

inline void
get_row(_r3d_Pixel *row, _r3d_Image *image, int y)
{
   if ((y < 0) || (y >= image->Height))
   {
      return;
   }
   memcpy(row,
      image->data + (y * image->pitch),
      (sizeof(_r3d_Pixel) * image->Width));
}

inline void
get_column(_r3d_Pixel *column, _r3d_Image *image, int x)
{
   int i, d;
   _r3d_Pixel *p;

   if ((x < 0) || (x >= image->Width))
   {
      return;
   }
   d = image->pitch;
   for (i = image->Height, p = image->data + x;i-- > 0;p += d)
   {
      *column++ = *p;
   }
}

inline _r3d_Pixel
put_pixel(_r3d_Image *image, int x, int y, _r3d_Pixel data)
{
   static _r3d_Image *im = NULL;
   static int yy = -1;
   static _r3d_Pixel *p = NULL;

   if ((x < 0) || (x >= image->Width) || (y < 0) || (y >= image->Height))
   {
      return (0);
   }
   if ((im != image) || (yy != y))
   {
      im = image;
      yy = y;
      p = image->data + (y * image->pitch);
   }
   return (p[x] = data);
}

inline float
filter(float t)
{
   if (t < 0.0F)
   {
      t = -t;
   }
   if (t < 1.0F)
   {
      return ((2.0F * t - 3.0F) * t * t + 1.0F);
   }
   return (0.0F);
}

inline float
box_filter(float t)
{
   if ((t > -0.5F) && (t <= 0.5F))
   {
      return (1.0F);
   }
   return (0.0F);
}

inline float
triangle_filter(float t)
{
   if (t < 0.0F)
   {
      t = -t;
   }
   if (t < 1.0F)
   {
      return (1.0F - t);
   }
   return (0.0F);
}

inline float
bell_filter(float t)
{
   if (t < 0.0F)
   {
      t = -t;
   }
   if (t < 0.5F)
   {
      return (0.75F - (t * t));
   }
   if (t < 1.5F)
   {
      t = (t - 1.5F);
      return (0.5F * (t * t));
   }
   return (0.0F);
}

inline float
B_spline_filter(float t)
{
   float tt;

   if (t < 0.0F)
   {
      t = -t;
   }
   if (t < 1.0F)
   {
      tt = t * t;
      return ((0.5F * tt * t) - tt + (2.0F / 3.0F));
   }
   else
   {
      if (t < 2.0F)
      {
         t = 2.0F - t;
         return ((1.0F / 6.0F) * (t * t * t));
      }
   }
   return (0.0F);
}

inline float
sinc(float x)
{
   x *= 3.1415926535897932384626433832795F;
   if (x != 0.0F)
   {
      return ((float)sin((double)x) / x);
   }
   return (1.0F);
}

inline float
Lanczos3_filter(float t)
{
   if (t < 0.0F)
   {
      t = -t;
   }
   if (t < 3.0F)
   {
      return (sinc(t) * sinc(t / 3.0F));
   }
   return (0.0F);
}

inline float
Mitchell_filter(float t)
{
   if (t < 0.0F)
   {
      t = -t;
   }
   if (t < 1.0F)
   {
      t = (((7.0F) * (t * t * t))
         + ((-12.0F) * t * t)
         + (5.333333333333333333333333333333333F));
      return (t / 6.0F);
   }
   else
   {
      if (t < 2.0F)
      {
         t = (((-2.333333333333333333333333333333333F) * (t * t * t))
            + ((12.0F) * t * t)
            + ((-20.0F) * t)
            + (10.666666666666666666666666666666666F));
         return (t / 6.0F);
      }
   }

   return (0.0F);
}

BOOL
_r3d_Zoom(_r3d_Image *dst, _r3d_Image *src)
{
   float fwidth;
   _r3d_Image tmp; // intermediate image
   float xscale, yscale; // zoom scale factors
   int i, j, k; // loop variables
   int n; // pixel number
   float center, left, right; // filter calculation variables
   float width, fscale, weight; // filter calculation variables
   _r3d_Pixel *raster; // a row or column of pixels
   int LoopVar;

   // create intermediate image to hold horizontal zoom
   tmp.Width = tmp.pitch = dst->Width;
   tmp.Height = src->Height;
   if (NULL == (tmp.data = new unsigned char[tmp.Width * tmp.Height]))
   {
      return FALSE;
   }

   xscale = (float)dst->Width / (float)src->Width;
   yscale = (float)dst->Height / (float)src->Height;

   // pre-calculate filter contributions for a row
   if (NULL == (_r3d_contrib = new _R3D_CLIST[dst->Width]))
   {
      _R3D_DELETE(tmp.data);
      return FALSE;
   }

   if (xscale < 1.0F)
   {
      // _R3D_LANCZOS3_SUPPORT, _R3D_MITCHELL_SUPPORT
      fwidth = _R3D_LANCZOS3_SUPPORT;
      width = fwidth / xscale;
      fscale = 1.0F / xscale;
      for (i = 0;i < dst->Width;++i)
      {
         _r3d_contrib[i].n = 0;
         if (NULL == (_r3d_contrib[i].p = new _R3D_CONTRIB[(int)(width * 2+1)]))
         {
            for (LoopVar = i - 1;LoopVar >= 0;LoopVar--)
               _R3D_DELETE(_r3d_contrib[LoopVar].p);
            _R3D_DELETE(_r3d_contrib);
            _R3D_DELETE(tmp.data);
            return FALSE;
         }
         center = (float)i / xscale;
         left = (float)ceil(center - width);
         right = (float)floor(center + width);
         for (j = (int)left;j <= (int)right;++j)
         {
            weight = center - (float)j;
            // Scaling up, use Mitchell_filter(). Scaling down, use Lanczos3_filter()
            weight = Lanczos3_filter(weight / fscale) / fscale;
            if (j < 0)
            {
               n = -j;
            }
            else
            {
               if (j >= src->Width)
               {
                  n = (src->Width - j) + src->Width - 1;
               }
               else
               {
                  n = j;
               }
            }
            k = _r3d_contrib[i].n++;
            _r3d_contrib[i].p[k].pixel = n;
            _r3d_contrib[i].p[k].weight = weight;
         }
      }
   }
   else
   {
      // _R3D_LANCZOS3_SUPPORT, _R3D_MITCHELL_SUPPORT
      fwidth = _R3D_MITCHELL_SUPPORT;
      for (i = 0;i < dst->Width;++i)
      {
         _r3d_contrib[i].n = 0;
         //_r3d_contrib[i].p = (_R3D_CONTRIB *)calloc((int)(fwidth * 2 + 1),sizeof(_R3D_CONTRIB));
         if (NULL == (_r3d_contrib[i].p = new _R3D_CONTRIB[(int)(fwidth * 2+1)]))
         {
            for (LoopVar = i - 1;LoopVar >= 0;LoopVar--)
               _R3D_DELETE(_r3d_contrib[LoopVar].p);
            _R3D_DELETE(_r3d_contrib);
            _R3D_DELETE(tmp.data);
            return FALSE;
         }
         center = (float)i / xscale;
         left = (float)ceil(center - fwidth);
         right = (float)floor(center + fwidth);
         for (j = (int)left;j <= (int)right;++j)
         {
            weight = center - (float)j;
            // Scaling up, use Mitchell_filter(). Scaling down, use Lanczos3_filter()
            weight = Mitchell_filter(weight);

            if (j < 0)
            {
               n = -j;
            }
            else
            {
               if (j >= src->Width)
               {
                  n = (src->Width - j) + src->Width - 1;
               }
               else
               {
                  n = j;
               }
            }
            k = _r3d_contrib[i].n++;
            _r3d_contrib[i].p[k].pixel = n;
            _r3d_contrib[i].p[k].weight = weight;
         }
      }
   }

   // apply filter to zoom horizontally from src to tmp
   if (NULL == (raster = new _r3d_Pixel[src->Width]))
   {
      for (i = 0;i < tmp.Width;++i)
         _R3D_DELETE(_r3d_contrib[i].p);
      _R3D_DELETE(_r3d_contrib);
      _R3D_DELETE(tmp.data);
      return FALSE;
   }

   for (k = 0;k < tmp.Height;++k)
   {
      get_row(raster, src, k);
      for (i = 0;i < tmp.Width;++i)
      {
         weight = 0.0;
         for (j = 0;j < _r3d_contrib[i].n;++j)
         {
            weight += raster[_r3d_contrib[i].p[j].pixel] * _r3d_contrib[i].p[j].weight;
         }
         put_pixel(&tmp, i, k, (_r3d_Pixel)_R3D_CLAMP(weight, _R3D_BLACK_PIXEL, _R3D_WHITE_PIXEL));
      }
   }
   _R3D_DELETE(raster);

   // free the memory allocated for horizontal filter weights
   for (i = 0;i < dst->Width;++i)
   {
      _R3D_DELETE(_r3d_contrib[i].p);
   }
   _R3D_DELETE(_r3d_contrib);

   // pre-calculate filter contributions for a column
   if (NULL == (_r3d_contrib = new _R3D_CLIST[dst->Height]))
   {
      _R3D_DELETE(tmp.data);
      return FALSE;
   }

   if (yscale < 1.0)
   {
      // _R3D_LANCZOS3_SUPPORT, _R3D_MITCHELL_SUPPORT
      fwidth = _R3D_LANCZOS3_SUPPORT;
      width = fwidth / yscale;
      fscale = 1.0F / yscale;
      for (i = 0;i < dst->Height;++i)
      {
         _r3d_contrib[i].n = 0;
         // _r3d_contrib[i].p = (_R3D_CONTRIB *)calloc((int)(width * 2 + 1),sizeof(_R3D_CONTRIB));
         if (NULL == (_r3d_contrib[i].p = new _R3D_CONTRIB[(int)(width * 2+1)]))
         {
            for (LoopVar = i - 1;LoopVar >= 0;LoopVar--)
               _R3D_DELETE(_r3d_contrib[LoopVar].p);
            _R3D_DELETE(_r3d_contrib);
            _R3D_DELETE(tmp.data);
            return FALSE;
         }

         center = (float)i / yscale;
         left = (float)ceil(center - width);
         right = (float)floor(center + width);
         for (j = (int)left;j <= (int)right;++j)
         {
            weight = center - (float)j;
            // Scaling up, use Mitchell_filter(). Scaling down, use Lanczos3_filter()
            weight = Lanczos3_filter(weight / fscale) / fscale;
            if (j < 0)
            {
               n = -j;
            }
            else
            {
               if (j >= tmp.Height)
               {
                  n = (tmp.Height - j) + tmp.Height - 1;
               }
               else
               {
                  n = j;
               }
            }
            k = _r3d_contrib[i].n++;
            _r3d_contrib[i].p[k].pixel = n;
            _r3d_contrib[i].p[k].weight = weight;
         }
      }
   }
   else
   {
      // _R3D_LANCZOS3_SUPPORT, _R3D_MITCHELL_SUPPORT
      fwidth = _R3D_MITCHELL_SUPPORT;
      for (i = 0;i < dst->Height;++i)
      {
         _r3d_contrib[i].n = 0;
         if (NULL == (_r3d_contrib[i].p = new _R3D_CONTRIB[(int)(fwidth * 2+1)]))
         {
            for (LoopVar = i - 1;LoopVar >= 0;LoopVar--)
               _R3D_DELETE(_r3d_contrib[LoopVar].p);
            _R3D_DELETE(_r3d_contrib);
            _R3D_DELETE(tmp.data);
            return FALSE;
         }

         center = (float)i / yscale;
         left = (float)ceil(center - fwidth);
         right = (float)floor(center + fwidth);
         for (j = (int)left;j <= (int)right;++j)
         {
            weight = center - (float)j;
            // Scaling up, use Mitchell_filter(). Scaling down, use Lanczos3_filter()
            weight = Mitchell_filter(weight);
            if (j < 0)
            {
               n = -j;
            }
            else
            {
               if (j >= tmp.Height)
               {
                  n = (tmp.Height - j) + tmp.Height - 1;
               }
               else
               {
                  n = j;
               }
            }
            k = _r3d_contrib[i].n++;
            _r3d_contrib[i].p[k].pixel = n;
            _r3d_contrib[i].p[k].weight = weight;
         }
      }
   }

   // apply filter to zoom vertically from tmp to dst
   if (NULL == (raster = new _r3d_Pixel[tmp.Height * sizeof(_r3d_Pixel)]))
   {
      for (i = 0;i < dst->Height;++i)
         _R3D_DELETE(_r3d_contrib[i].p);
      _R3D_DELETE(_r3d_contrib);
      _R3D_DELETE(tmp.data);
      return FALSE;
   }

   for (k = 0;k < dst->Width;++k)
   {
      get_column(raster, &tmp, k);
      for (i = 0;i < dst->Height;++i)
      {
         weight = 0.0;
         for (j = 0;j < _r3d_contrib[i].n;++j)
         {
            weight += raster[_r3d_contrib[i].p[j].pixel] * _r3d_contrib[i].p[j].weight;
         }
         put_pixel(dst, k, i, (_r3d_Pixel)_R3D_CLAMP(weight, _R3D_BLACK_PIXEL, _R3D_WHITE_PIXEL));
      }
   }
   _R3D_DELETE(raster);

   // free the memory allocated for vertical filter weights
   for (i = 0;i < dst->Height;++i)
   {
      _R3D_DELETE(_r3d_contrib[i].p);
   }
   _R3D_DELETE(_r3d_contrib);

   _R3D_DELETE(tmp.data);

   return TRUE;
}

BOOL R3D_STDCALL
r3d_SurfaceStretchSmooth(int hSrc, int hDst)
{
   unsigned long dwColor;
   unsigned long *SrcRGBA, *DstRGBA;
   int LoopVar;

   // Allocate 8 new unsigned char arrays
   if (NULL == (_r3d_Src.data = new unsigned char[_r3d_Surface[hSrc]->dwWidth * _r3d_Surface[hSrc]->dwHeight]))
   {
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "Can not allocate memory", DD_OK);
      return FALSE;
   }
   if (NULL == (_r3d_DstR.data = new unsigned char[_r3d_Surface[hDst]->dwWidth * _r3d_Surface[hDst]->dwHeight]))
   {
      _R3D_DELETE(_r3d_Src.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "Can not allocate memory", DD_OK);
      return FALSE;
   }
   if (NULL == (_r3d_DstG.data = new unsigned char[_r3d_Surface[hDst]->dwWidth * _r3d_Surface[hDst]->dwHeight]))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "Can not allocate memory", DD_OK);
      return FALSE;
   }
   if (NULL == (_r3d_DstB.data = new unsigned char[_r3d_Surface[hDst]->dwWidth * _r3d_Surface[hDst]->dwHeight]))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _R3D_DELETE(_r3d_DstG.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "Can not allocate memory", DD_OK);
      return FALSE;
   }
   if (NULL == (_r3d_DstA.data = new unsigned char[_r3d_Surface[hDst]->dwWidth * _r3d_Surface[hDst]->dwHeight]))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _R3D_DELETE(_r3d_DstG.data);
      _R3D_DELETE(_r3d_DstB.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "Can not allocate memory", DD_OK);
      return FALSE;
   }

   // Assign the information to the Image structures

   // 1. Assign Width, Height and pitch
   _r3d_Src.Width = _r3d_Src.pitch = _r3d_Surface[hSrc]->dwWidth;
   _r3d_Src.Height = _r3d_Surface[hSrc]->dwHeight;
   _r3d_DstR.Width = _r3d_DstR.pitch = _r3d_Surface[hDst]->dwWidth;
   _r3d_DstR.Height = _r3d_Surface[hDst]->dwHeight;
   _r3d_DstG.Width = _r3d_DstG.pitch = _r3d_Surface[hDst]->dwWidth;
   _r3d_DstG.Height = _r3d_Surface[hDst]->dwHeight;
   _r3d_DstB.Width = _r3d_DstB.pitch = _r3d_Surface[hDst]->dwWidth;
   _r3d_DstB.Height = _r3d_Surface[hDst]->dwHeight;
   _r3d_DstA.Width = _r3d_DstA.pitch = _r3d_Surface[hDst]->dwWidth;
   _r3d_DstA.Height = _r3d_Surface[hDst]->dwHeight;
   // 2. Assign Src color components to Image data
   SrcRGBA = (unsigned long *)r3d_SurfaceGet(hSrc);

   // Scale Image with filter for Red
   for (LoopVar = 0;LoopVar < (int)(_r3d_Surface[hSrc]->dwWidth * _r3d_Surface[hSrc]->dwHeight);LoopVar++)
   {
      dwColor = SrcRGBA[LoopVar];
      _r3d_Src.data[LoopVar] = r3d_SurfaceColorGetR(dwColor);
   }
   if (!_r3d_Zoom(&_r3d_DstR, &_r3d_Src))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _R3D_DELETE(_r3d_DstG.data);
      _R3D_DELETE(_r3d_DstB.data);
      _R3D_DELETE(_r3d_DstA.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "_r3d_Zoom", DD_OK);
      return FALSE;
   }

   // Scale Image with filter for Green
   for (LoopVar = 0;LoopVar < (int)(_r3d_Surface[hSrc]->dwWidth * _r3d_Surface[hSrc]->dwHeight);LoopVar++)
   {
      dwColor = SrcRGBA[LoopVar];
      _r3d_Src.data[LoopVar] = r3d_SurfaceColorGetG(dwColor);
   }
   if (!_r3d_Zoom(&_r3d_DstG, &_r3d_Src))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _R3D_DELETE(_r3d_DstG.data);
      _R3D_DELETE(_r3d_DstB.data);
      _R3D_DELETE(_r3d_DstA.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "_r3d_Zoom", DD_OK);
      return FALSE;
   }

   // Scale Image with filter for Blue
   for (LoopVar = 0;LoopVar < (int)(_r3d_Surface[hSrc]->dwWidth * _r3d_Surface[hSrc]->dwHeight);LoopVar++)
   {
      dwColor = SrcRGBA[LoopVar];
      _r3d_Src.data[LoopVar] = r3d_SurfaceColorGetB(dwColor);
   }
   if (!_r3d_Zoom(&_r3d_DstB, &_r3d_Src))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _R3D_DELETE(_r3d_DstG.data);
      _R3D_DELETE(_r3d_DstB.data);
      _R3D_DELETE(_r3d_DstA.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "_r3d_Zoom", DD_OK);
      return FALSE;
   }

   // Scale Image with filter for Alpha
   for (LoopVar = 0;LoopVar < (int)(_r3d_Surface[hSrc]->dwWidth * _r3d_Surface[hSrc]->dwHeight);LoopVar++)
   {
      dwColor = SrcRGBA[LoopVar];
      _r3d_Src.data[LoopVar] = r3d_SurfaceColorGetA(dwColor);
   }
   if (!_r3d_Zoom(&_r3d_DstA, &_r3d_Src))
   {
      _R3D_DELETE(_r3d_Src.data);
      _R3D_DELETE(_r3d_DstR.data);
      _R3D_DELETE(_r3d_DstG.data);
      _R3D_DELETE(_r3d_DstB.data);
      _R3D_DELETE(_r3d_DstA.data);
      _r3d_FormulateErrorString("_r3d_SmoothStretch", "_r3d_Zoom", DD_OK);
      return FALSE;
   }

   // Now reassign it to hDst
   DstRGBA = (unsigned long *)r3d_SurfaceGet(hDst);
   for (LoopVar = 0;LoopVar < (int)(_r3d_Surface[hDst]->dwWidth * _r3d_Surface[hDst]->dwHeight);LoopVar++)
   {
      dwColor = r3d_SurfaceColorMakeRGBA(_r3d_DstR.data[LoopVar], _r3d_DstG.data[LoopVar], _r3d_DstB.data[LoopVar], _r3d_DstA.data[LoopVar]);
      DstRGBA[LoopVar] = dwColor;
   }

   // Free Resources
   _R3D_DELETE(_r3d_Src.data);
   _R3D_DELETE(_r3d_DstR.data);
   _R3D_DELETE(_r3d_DstG.data);
   _R3D_DELETE(_r3d_DstB.data);
   _R3D_DELETE(_r3d_DstA.data);

   return TRUE;
}