/* === S Y N F I G ========================================================= */
/*!	\file centerlinepolygonizer.cpp
**
**	$Id$
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#include "polygonizerclasses.h"
#include <synfig/surface.h>
#include <synfig/rendering/software/surfacesw.h>
#include <math.h>
#include <ETL/handle>
#include <synfig/layers/layer_bitmap.h>
#include <synfig/vector.h>


/* === U S I N G =========================================================== */

using namespace std;
using namespace etl;
using namespace studio;
using namespace synfig;

typedef etl::handle<synfig::Layer_Bitmap> Handle;
/* === M A C R O S ========================================================= */

/* === G L O B A L S ======================================================= */

/* === P R O C E D U R E S ================================================= */

/* === M E T H O D S ======================================================= */

class RawBorderPoint 
{
  synfig::PointInt m_position;
  int m_ambiguousTurn;  // used to remember cases of multiple turning directions
  // in a RawBorder extraction.

public:
  RawBorderPoint() : m_ambiguousTurn(0) {}
  RawBorderPoint(int i, int j) : m_position(i,j), m_ambiguousTurn(0) {}

  inline synfig::PointInt pos() const { return m_position; }
  inline int x() const { return m_position[0]; }
  inline int y() const { return m_position[1]; }

  enum { left = 1, right = 2 };  // Direction taken at ambiguous turning point
  inline int getAmbiguous() const { return m_ambiguousTurn; }
  inline void setAmbiguous(int direction) { m_ambiguousTurn = direction; }
};

//--------------------------------------------------------------------------

class studio::RawBorder final : public std::vector<RawBorderPoint> 
{
  int m_xExternal;  // x coordinate of a specific vertex in the outer
  // RawBorder which contains this inner one.

  synfig::Point *m_coordinateSums;
  synfig::Point *m_coordinateSquareSums;
  double *m_coordinateMixedSums;

public:
  RawBorder() {}
  ~RawBorder() {}

  void setXExternalPixel(int a) { m_xExternal = a; }
  int xExternalPixel() { return m_xExternal; }
  synfig::Point *&sums() { return m_coordinateSums; }
  synfig::Point *&sums2() { return m_coordinateSquareSums; }
  double *&sumsMix() { return m_coordinateMixedSums; }
};

//--------------------------------------------------------------------------

// Of course we don't want RawBorders to be entirely copied whenever STL
// requires to resize a BorderFamily...
typedef std::vector<RawBorder *> BorderFamily;
typedef std::vector<BorderFamily> BorderList;

//============================
//    Polygonizer Locals
//============================

namespace 
{
// Const names
enum { white = 0, black = 1 };
enum { inner = 0, outer = 1, none = 2, invalid = 3 };
}

//=======================================================================================

//-------------------------------
//    Raster Data Functions
//-------------------------------

//--------------------------------------------------------------------------
class PixelEvaluator 
{
  const Surface &m_surface;
  int m_threshold;

public:
  PixelEvaluator(const Surface &surface, int threshold)
      : m_surface(surface), m_threshold(threshold) {}

  inline unsigned char getBlackOrWhite(int x, int y);
};

// //--------------------------------------------------------------------------

inline unsigned char PixelEvaluator::getBlackOrWhite(int x, int y)
{
	const int Y = m_surface.get_h() - y -1; 
	const Color &color = m_surface[Y][x];
	int r = 255.99*color.get_r();
	int g = 255.99*color.get_g();
	int b = 255.99*color.get_b();
	int a = 255.99*color.get_a();
	return std::max(r,std::max(g,b)) < m_threshold * (a / 255.0);
}

//--------------------------------------------------------------------------

// Signaturemap format:
//  stores a map of bytes, whose first bit represents the color (black/white) of
//  corresponding pixel, and
//  the rest its 'signature', used as an int to store information.

class Signaturemap 
{
  std::unique_ptr<unsigned char[]> m_array;
  int m_rowSize;
  int m_colSize;

public:
  Signaturemap(const Handle &ras, int threshold);

  //not needed 
  //void readRasterData(const Handle &ras, int threshold);

  inline int getRowSize() const { return m_rowSize; }
  inline int getColSize() const { return m_colSize; }

  unsigned char *pixelByte(int x, int y) 
  {
    return &m_array[(y + 1) * m_rowSize + x + 1];
  }

  bool getBitmapColor(int x, int y) const 
  {
    return m_array[(y + 1) * m_rowSize + x + 1] & 1;
  }

  inline unsigned char getSignature(int x, int y) const 
  {
    return m_array[(y + 1) * m_rowSize + x + 1] >> 1;
  }

  void setSignature(int x, int y, int val) 
  {
    unsigned char *pixel = pixelByte(x, y);
    *pixel &= 1;
    *pixel |= (val << 1);  
  }
};

//--------------------------------------------------------------------------

Signaturemap::Signaturemap(const Handle &ras, int threshold)
{
	// read the raster data
	unsigned char *currByte;
	int x, y;

	rendering::SurfaceResource::LockRead<rendering::SurfaceSW> lock( ras->rendering_surface );
	const Surface &surface = lock->get_surface(); 
	PixelEvaluator evaluator(surface, threshold);//evaluator object with surface, threshold as constructor args
	m_rowSize = surface.get_w() + 2;
	m_colSize = surface.get_h() + 2;
	m_array.reset(new unsigned char[m_rowSize * m_colSize]);

	memset(m_array.get(), none << 1, m_rowSize);

	currByte = m_array.get() + m_rowSize;
	for (y = 0; y <m_colSize - 2 ; ++y) 
	{
		*currByte = none << 1;
		currByte++;

		for (x = 0; x < m_rowSize - 2; ++x, ++currByte)
			*currByte = evaluator.getBlackOrWhite(x, y) | (none << 1);
		*currByte = none << 1;
		currByte++;
	}

	memset(currByte, none << 1, m_rowSize);
}

//--------------------------------------------------------------------------

// Minority check for ambiguous turning directions
inline bool getMinorityCheck(const Signaturemap &ras, int x, int y) 
{
  // Assumes (x,y) is ambiguous case: 2 immediate surrounding pixels are white
  // and 2 black
  return (ras.getBitmapColor(x + 1, y) + ras.getBitmapColor(x + 1, y - 1) +
          ras.getBitmapColor(x - 2, y) + ras.getBitmapColor(x - 2, y - 1) +
          ras.getBitmapColor(x - 1, y + 1) + ras.getBitmapColor(x - 1, y - 2) +
          ras.getBitmapColor(x, y + 1) + ras.getBitmapColor(x, y - 2)) > 4;
}

//--------------------------------------------------------------------------

// Sets signature of a given border
inline void setSignature(Signaturemap &ras, const RawBorder &border, int val) 
{
  unsigned int j;
  int yOld;

  // Set border's alpha channel
  yOld = border.back().y();
  for (j = 0; j < border.size(); ++j) 
  {
    if (border[j].y() < yOld) 
    {
      ras.setSignature(border[j].x(), border[j].y(), val);
    } 
    else if (border[j].y() > yOld) 
    {
      ras.setSignature(border[j].x(), yOld, val);
    }
    yOld = border[j].y();
  }
}
//==========================================================================

//-------------------------------
//    Raw Borders Extraction
//-------------------------------

// RawBorderPoints correspond to lower-left pixel corners.
// EXAMPLE: (0,0) is the lower-left *corner* of the image, whereas (0,0) also
// represents coordinates of the lower-left *pixel*.
// NOTE: 'Ambiguous turning' vertices are those of kind:
//
//    B|W           W|B
//    -x-    -or-   -x-
//    W|B           B|W
//
// Keeping B on the right of our path-seeking direction, we may either turn
// left or right at these points.

static RawBorder *extractPath(Signaturemap &ras, int x0, int y0, int pathType,
                              int xOuterPixel, int despeckling) 
{
  RawBorder *path = new RawBorder;
  int x, y;
  short dirX, dirY;
  long int area = 0;
  bool nextLeftPixel, nextRightPixel;

  if (pathType == outer) 
  {
    dirX = 0;
    dirY = 1;
  }
  else 
  {
    dirX = 1;
    dirY = 0;
    area += y0;
    path->setXExternalPixel(xOuterPixel);
  }

  path->push_back(RawBorderPoint(x0, y0));

  // Check here if (x0, y0) is an ambiguous-direction point
  nextLeftPixel = ras.getBitmapColor(x0 + (dirY - dirX - 1) / 2,
                                     y0 + (-dirY - dirX - 1) / 2);
  nextRightPixel = ras.getBitmapColor(x0 + (-dirX - dirY - 1) / 2,
                                      y0 + (dirX - dirY - 1) / 2);
  if ((nextRightPixel == black) && (nextLeftPixel == white))
    path->back().setAmbiguous(dirX ? RawBorderPoint::left : RawBorderPoint::right);

  // Begin path extraction

  for (x = x0 + dirX, y = y0 + dirY; !(x == x0 && y == y0); x += dirX, y += dirY) 
  {
    path->push_back(RawBorderPoint(x, y));

    // Calculate next direction
    nextLeftPixel = ras.getBitmapColor(x + (dirX - dirY - 1) / 2,
                                       y + (dirY + dirX - 1) / 2);
    nextRightPixel = ras.getBitmapColor(x + (dirX + dirY - 1) / 2,
                                        y + (dirY - dirX - 1) / 2);

    if ((nextRightPixel == black) && (nextLeftPixel == black)) {
      // Left Turn
      std::swap(dirY, dirX);
      dirX = -dirX;
    } 
    else if ((nextRightPixel == white) && (nextLeftPixel == white)) 
    {
      // Right Turn
      std::swap(dirY, dirX);
      dirY = -dirY;
    } 
    else if ((nextRightPixel == white) && (nextLeftPixel == black)) {
      // path->back().setAmbiguous();

      // Do a surrounding check and connect minority color
      if (getMinorityCheck(ras, x, y) == black) 
      {
        std::swap(dirY, dirX);
        dirY = -dirY;
        path->back().setAmbiguous(RawBorderPoint::right);
      }  // right turn
      else 
      {
        std::swap(dirY, dirX);
        dirX = -dirX;
        path->back().setAmbiguous(RawBorderPoint::left);
      }  // left turn
    }

    // Also calculate border area
    area += y * dirX;

    // And sign treated pixel
    if (dirY != 0) ras.setSignature(x, y + (dirY - 1) / 2, pathType);
  }

  // If the inner region's overall area is under a given threshold,
  // then erase it (intended as image noise).
  if (abs(area) < despeckling) 
  {
    setSignature(ras, *path, invalid);
    delete path;
    path = 0;
  }

  return path;
}

//--------------------------------------------------------------------------

static BorderList *extractBorders(const Handle &ras, int threshold, int despeckling)
{
  Signaturemap byteImage(ras, threshold);

  BorderList *borderHierarchy = new BorderList;
  std::vector<RawBorder *> outerBorders;
  std::list<RawBorder *> innerBorders;
  RawBorder *foundPath;

  int x, y;
  bool Color, oldColor;
  int xOuterPixel = 0;
  bool enteredRegionType;
  unsigned char signature;

  rendering::SurfaceResource::LockRead<rendering::SurfaceSW> lock( ras->rendering_surface );
	const Surface &surface = lock->get_surface(); 
	int width = surface.get_w(); 
	int height = surface.get_h(); 
	
  for (y = 0; y < height; ++y) 
  {
    oldColor          = white;
    enteredRegionType = outer;
    for (x = 0; x < width; ++x) 
    {
      if (oldColor ^ (Color = byteImage.getBitmapColor(x, y))) 
      {
        // Region type changes
        enteredRegionType = !enteredRegionType;

        if ((signature = byteImage.getSignature(x, y)) == none) 
        {
          if ((foundPath = extractPath(byteImage, x, y, !enteredRegionType,
                                       xOuterPixel, despeckling)))
            if (enteredRegionType == outer)
              innerBorders.push_back(foundPath);
            else
              outerBorders.push_back(foundPath);
        }

        // If leaving a white region, remember it - in order to establish
        // border hierarchy in the future
        if (enteredRegionType == inner && signature != invalid) xOuterPixel = x;

        // Invalid pixels got signed by a cut-out path, due to insufficient area
        if (signature == invalid)
          byteImage.setSignature(x, y, none);  // Restore them now

        oldColor = Color;
      }
    }
  }

  // Now, we have all borders found, but no hierarchy between them.

  unsigned int i;
  std::list<RawBorder *>::iterator l;

  // Build hierarchy

  innerBorders.push_front(0);  // Just to keep a fixed list head

  for (i = 0; i < outerBorders.size(); ++i) 
  {
    // Initialize a border family
    borderHierarchy->push_back(BorderFamily());
    borderHierarchy->back().push_back(outerBorders[i]);

    // Reset outerBorders[i]'s signature
    setSignature(byteImage, *outerBorders[i], none);

    // Now check inner borders for insideness - check if the outerPixel
    // remembered in path extraction has been cleared
    for (l = innerBorders.begin(), ++l; l != innerBorders.end(); ++l) 
    {
      if (byteImage.getSignature((*l)->xExternalPixel(), (**l)[0].y()) == none) 
      {
        borderHierarchy->back().push_back(*l);
        setSignature(byteImage, **l, none);
        l = innerBorders.erase(l);
        --l;
      }
    }
  }

  return borderHierarchy;
}

//--------------------------------------------------------------------------

//==================================
//    Calculate optimal polygons
//==================================

// The optimal polygon for a given original border is found like:

// 1) Find couples (i,k(i)), so that k(i) be the largest k:
//        d(j,ik) <= 1;  for *all* i<j<k.  (d is infinite-norm distance)
//    It can be shown that such a condition is equivalent to:
//      exists line l : d(l,j)<=1/2, for all i<=j<=k(i).
// 2) Clean the above couples - find couples (i,l(i)):
//        l(i)=min{k(j)},  j=i..n.
// 3) Calculate clipped couples (i',l'); where i'=i+1, l'=l(i)-1.
// 4) Calculate sums for path penalties.
// 5) Apply optimality algorithm.

// NOTE: Weak simpleness reads like: a set of polygons is weak-simple if no edge
//      *crosses* another edge. Superposition and collision of edges with
//      vertices
//      are still admitted.
//        =>  It can be shown that due to 1) and special conditions on ambiguous
//            turnings applied in both 1) and 3), weak simpleness is insured in
//            our polygonization.

//--------------------------------------------------------------------------

// Helper functions/classes: circular-indexed vectors

// returns 1 whenever the triple (a,b,c) is 'circular' mod n.
// NOTE: We'll find useful taking (i,i,j) as 1 and (i,j,j) as 0.
inline bool isCircular(int a, int b, int c) 
{
  return a <= c ? a <= b && b < c : c > b || b >= a;
}

//--------------------------------------------------------------------------

// Extracts a 'next corner' array - helps improving overall speed
inline std::unique_ptr<int[]> findNextCorners(RawBorder &path)
{
  std::unique_ptr<int[]> corners(new int[path.size()]);

  // NOTE: 0 is a corner, due to the path extraction procedure.
  int currentCorner = 0;
  for (int i = path.size() - 1; i >= 0; --i) {
    if (path[currentCorner].x() != path[i].x() &&
        path[currentCorner].y() != path[i].y())
      currentCorner = i + 1;
    corners[i]      = currentCorner;
  }

  return corners;
}

inline int cross(const synfig::PointInt &a, const synfig::PointInt &b) 
{
  return a[0] * b[1] - a[1] * b[0];
}

// Calculate furthest k satisfying 1) for all fixed i.
inline std::unique_ptr<int[]> furthestKs(RawBorder &path, std::unique_ptr<int[]> &nextCorners) 
{
  int n = path.size();
  std::unique_ptr<int[]> kVector(new int[n]);

  enum { left, up, right, down };
  int directionsOccurred[4];

  nextCorners = findNextCorners(path);

  int i, j, k;
  synfig::PointInt shift;
  synfig::PointInt leftConstraint, rightConstraint, violatedConstraint;
  synfig::PointInt newLeftConstraint, newRightConstraint;
  synfig::PointInt jPoint, jNextPoint, iPoint, temp;
  synfig::Point tempD;
  synfig::PointInt direction;

  int directionSignature;

  for (i = 0; i < n; ++i) 
  {
    // Initialize search
    leftConstraint = rightConstraint = synfig::PointInt();
    directionsOccurred[0] = directionsOccurred[1] = directionsOccurred[2] = directionsOccurred[3] = 0;
    j = i;
    jNextPoint = iPoint = path[i].pos();

    // Search for k(i)
    while (1) {
      // NOTE: Here using TPoint::operator= is less effective than setting
      // its x and y components directly...

      jPoint     = jNextPoint;
      jNextPoint = path[nextCorners[j]].pos();

      // Update directions count
      directionSignature = jNextPoint[0] > jPoint[0]
                               ? right
                               : jNextPoint[0] < jPoint[0]
                                     ? left
                                     : jNextPoint[1] > jPoint[1] ? up : down;
      directionsOccurred[directionSignature] = 1;

      // If all 4 axis directions occurred, quit
      if (directionsOccurred[left] && directionsOccurred[right] &&
          directionsOccurred[up] && directionsOccurred[down]) {
        k = j;
        goto foundK;
      }

      // Update displacement from i
      shift = jNextPoint - iPoint;

      // Test j against constraints
      // if(cross(shift, leftConstraint)<0 || cross(shift, rightConstraint)>0)
      if (cross(shift, leftConstraint) < 0) {
        violatedConstraint = leftConstraint;
        break;
      }
      if (cross(shift, rightConstraint) > 0) {
        violatedConstraint = rightConstraint;
        break;
      }

      // Update constraints
      if (abs(shift[0]) > 1 || abs(shift[1]) > 1) {
        newLeftConstraint[0] =
            shift[0] + (shift[1] < 0 || (shift[1] == 0 && shift[0] < 0) ? 1 : -1);
        newLeftConstraint[1] =
            shift[1] + (shift[0] > 0 || (shift[0] == 0 && shift[1] < 0) ? 1 : -1);

        if (cross(newLeftConstraint, leftConstraint) >= 0)
          leftConstraint = newLeftConstraint;

        newRightConstraint[0] =
            shift[0] + (shift[1] > 0 || (shift[1] == 0 && shift[0] < 0) ? 1 : -1);

        newRightConstraint[1] =
            shift[1] + (shift[0] < 0 || (shift[0] == 0 && shift[1] < 0) ? 1 : -1);

        if (cross(newRightConstraint, rightConstraint) <= 0)
          rightConstraint = newRightConstraint;
      }

      // Imposing strict constraint for ambiguous turnings, to ensure polygons'
      // weak simpleness.
      // Has to be defined *outside* abs checks.
      if (path[nextCorners[j]].getAmbiguous()) {
        if (path[nextCorners[j]].getAmbiguous() == RawBorderPoint::left)
          rightConstraint = shift;
        else
          leftConstraint = shift;
      }

      j = nextCorners[j];
    }

    // At this point, constraints are violated by the next corner.
    // Then, search for the last k between j and corners[j] not violating them.
    temp =  jNextPoint - jPoint;
    tempD[0]=temp[0];
    tempD[1]=temp[1];
    tempD = tempD.norm();
    direction[0]=round(tempD[0]);
    direction[1]=round(tempD[1]) ;

    k = (j + cross(jPoint - iPoint, violatedConstraint) / cross(violatedConstraint, direction)) % n;

  foundK:

    kVector[i] = k;
  }

  return kVector;
}

//--------------------------------------------------------------------------

// Now find the effective intervals inside which we can define possible
// arcs approximating the given raw border:
//    for every a in [i,res[i]], the arc connecting border[i] and
//    border[a] will be a possible one.
inline std::unique_ptr<int[]> calculateForwardArcs(RawBorder &border, bool ambiguitiesCheck) 
{
  int const n = (int)border.size();

  std::unique_ptr<int[]> nextCorners;
  std::unique_ptr<int[]> k = furthestKs(border, nextCorners);
  std::unique_ptr<int[]> K(new int[n]);
  std::unique_ptr<int[]> res(new int[n]);

  // find K[i]= min {k[j]}, j=i..n-1.
  for (int i = 0; i < n; ++i) 
  {
    int j;
    for (j = i, K[i] = k[i]; isCircular(i, j, K[i]); j = (j + 1) % n)
      if (isCircular(j, k[j], K[i])) K[i] = k[j];
  }

  // Finally, we perform the following clean-up operations:
  //  first, extremities of [i,K[i]] are clipped away, to obtain a
  //    smoother optimal polygon (and deal with cases like the unitary
  //    square);
  //  second, arcs of the kind [i,j] with j<i, become [i,n].

  for (int i = n - 1, j = 0; j < n; i = j, ++j) 
  {
    res[j] = K[i] < j ? (K[i] == 0 ? n - 1 : n) : K[i] - 1;
  }

  // Amibiguities check for vertex and edge superpositions. Prevent problems in
  // the forecoming
  // straight-skeleton thinning process.

  if (ambiguitiesCheck) 
  {
    for (int i = 1; nextCorners[i] > 0; i = nextCorners[i]) 
    {
      if (border[i].getAmbiguous() == RawBorderPoint::right) 
      {
        // Check vertices from i (excluded) to res[res[i]]; if in it there
        // exists vertex k so that pos(k)==pos(i)...
        // This prevents the existence of 0 degree angles in the optimal
        // polygon.

        int rrPlus1 = (res[res[i] % n] + 1) % n;

        // remember that isCircular(a,a,b) == 1 ...
        for (int j = nextCorners[i]; isCircular(i, j, rrPlus1) && j != i; j = nextCorners[j]) 
        {
          if (border[j].getAmbiguous() && (border[j].pos() == border[i].pos())) 
          {
            res[res[i] % n] = j - 1;
            assert((res[i] % n) != j - 1);

            // Further, ensure res is increasing
            for (int k = res[i] % n; res[k] >= j - 1 && k >= 0; --k) 
            {
              res[k] = j - 1;
              assert(k != j - 1);
            }

            break;
          }
        }
      }
    }
  }

  return res;
}

//--------------------------------------------------------------------------

// Let sum[i] and sum2[i] be respectively the sums of vertex coordinates
// from 0 to i, and the sums of their squares; sumsMix contain sums of
// xy terms.
inline void calculateSums(RawBorder &path) 
{
  unsigned int i, n = path.size();
  synfig::PointInt temp;
  path.sums()    = new synfig::Point[n + 1];
  path.sums2()   = new synfig::Point[n + 1];
  path.sumsMix() = new double[n + 1];

  path.sums()[0][0] = path.sums()[0][1] = path.sums2()[0][0] = path.sums2()[0][1] = 0;
  for (i = 1; i < path.size(); ++i) 
  {
    temp = path[i].pos() - path[0].pos();
    synfig::Point currentRelativePos(temp[0],temp[1]);

    path.sums()[i] = path.sums()[i - 1] + currentRelativePos;

    path.sums2()[i][0] = path.sums2()[i - 1][0] + currentRelativePos[0] * currentRelativePos[0];
    path.sums2()[i][1] = path.sums2()[i - 1][1] + currentRelativePos[1] * currentRelativePos[1];

    path.sumsMix()[i] = path.sumsMix()[i - 1] + currentRelativePos[0] * currentRelativePos[1];
  }

  // path[n] is virtually intended as path[0], but we prefer to introduce
  // it in the optimality algorithm's count
  path.sums()[n][0] = path.sums()[n][1] = path.sums2()[n][0] = path.sums2()[n][1] = 0;
}

//--------------------------------------------------------------------------

// Let a,b the index-extremities of an arc of this path.
// Then return its penalty.
inline double penalty(RawBorder &path, int a, int b) 
{
  synfig::PointInt p;

  int n = b - a + 1;
  p = path[b == path.size() ? 0 : b].pos() - path[a].pos();

  synfig::Point v(-p[1],p[0]);//convert(rotate90(p))
  synfig::Point sum   = path.sums()[b] - path.sums()[a];
  synfig::Point sum2  = path.sums2()[b] - path.sums2()[a];
  double sumMix = path.sumsMix()[b] - path.sumsMix()[a];

  double F1 = sum2[0] - 2 * sum[0] * path[a].x() + n * path[a].x() * path[a].x();
  double F2 = sum2[1] - 2 * sum[1] * path[a].y() + n * path[a].y() * path[a].y();
  double F3 = sumMix - sum[0] * path[a].y() - sum[1] * path[a].x() +
              n * path[a].x() * path[a].y();
  return sqrt((v[1] * v[1] * F1 + v[0] * v[0] * F2 - 2 * v[0] * v[1] * F3) / n);
}

//--------------------------------------------------------------------------

// NOTA: Il seguente algoritmo di riduzione assicura la semplicita' (debole) dei
// poligoni prodotti.
//

inline void reduceBorder(RawBorder &border, Contour &res, bool ambiguitiesCheck) 
{
  int n = border.size();
  int minPenaltyNext;
  std::unique_ptr<int[]> minPenaltyNextArray(new int[n]);

  // Calculate preliminary infos
  std::unique_ptr<int[]> longestArcFrom = calculateForwardArcs(border, ambiguitiesCheck);
  calculateSums(border);

  std::unique_ptr<double[]> penaltyToEnd(new double[n + 1]);

  // EXPLANATION:
  // The fastest way to extract the optimal reduced border is based on the
  // weakly monotonic property of longestArc[].
  // The minimal number of its vertices 'm' is easily found by
  // traversing the path with the longest step allowed. Let b[] be that
  // succession; then, given res[i], it has to be reached by a vertex in
  // the interval: {a[i-1], .. , b[i-1]}, where longestArc[a[i-1]]=a[i],
  // longestArc[a[i-1]-1]<a[i], and a[m]=n.

  // Calculate m
  int m = 0;
  for (int i = 0; i < n; i = longestArcFrom[i]) ++m;

  // Calculate b[]
  std::unique_ptr<int[]> b(new int[m + 1]);
  b[m] = n;
  for (int i = 0, j = 0; j < m; i = longestArcFrom[i], ++j) b[j] = i;

  // NOTE: a[] need not be completely found - we just remember the
  // a=a[j+1] currently needed.

  // Now, build the optimal polygon
  for (int j = m - 1, a = n; j >= 0; --j) 
  {
    int k;
    for (k = b[j]; k >= 0 && longestArcFrom[k] >= a; --k) 
    {
      penaltyToEnd[k] = infinity;
      for (int i = a; i <= longestArcFrom[k]; ++i) 
      {
        double newPenalty = penaltyToEnd[i] + penalty(border, k, i);
        
        if (newPenalty < penaltyToEnd[k]) 
          penaltyToEnd[k] = newPenalty;
        
        minPenaltyNext = i;
      }
      minPenaltyNextArray[k] = minPenaltyNext;
    }
    a = k + 1;
  }

  // Finally, read off the optimal polygon

  res.resize(m);
  for (int i = 0, j = 0; i < n; i = minPenaltyNextArray[i], ++j) {
    res[j] = ContourNode(border[i].x(), border[i].y());

    // Ambiguities are still remembered in the output polygon.
    if (border[i].getAmbiguous() == RawBorderPoint::left)
      res[j].setAttribute(ContourNode::AMBIGUOUS_LEFT);
    if (border[i].getAmbiguous() == RawBorderPoint::right)
      res[j].setAttribute(ContourNode::AMBIGUOUS_RIGHT);
  }

  delete[] border.sums();
  delete[] border.sums2();
  delete[] border.sumsMix();
}

//--------------------------------------------------------------------------

// Reduction caller and list copier.
inline void reduceBorders(BorderList &borders, Contours &result,bool ambiguitiesCheck) 
{
  unsigned int i, j;

  // Initialize output container
  result.resize(borders.size());

  // Copy results
  for (i = 0; i < borders.size(); ++i) 
  {
    result[i].resize(borders[i].size());
    for (j = 0; j < borders[i].size(); ++j) 
    {
      reduceBorder(*borders[i][j], result[i][j], ambiguitiesCheck);
      delete borders[i][j];
    }
  }
}

/* === E N T R Y P O I N T ================================================= */
//===========================
//    Polygonization Main
//===========================

//Extracts a polygonal, minimal yet faithful representation of image contours

void studio::polygonize(const etl::handle<synfig::Layer_Bitmap> &ras, Contours &polygons, VectorizerCoreGlobals &g)
{
	
	BorderList *borders;
	
	borders = extractBorders(ras, g.currConfig->m_threshold, g.currConfig->m_despeckling);
	
	reduceBorders(*borders, polygons, g.currConfig->m_maxThickness > 0.0);
}


