Blob Blame Raw
/* === S Y N F I G ========================================================= */
/*!	\file centerlinecolors.cpp
**	\brief centerlinecolors File
**
**	$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>


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

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

/* === M A C R O S ========================================================= */

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

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

synfig::Color pixelToColor(const Surface &rsurface, int x, int y, const Gamma &gamma)
{
	const int Y = rsurface.get_h() - y - 1;
	return gamma.apply(rsurface[Y][x]);
}

bool checkPixelThreshold(const Surface &rsurface, int x, int y, int threshold)
{
	const int Y = rsurface.get_h() - y -1; 
	const Color color = rsurface[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)) < threshold * (a / 255.0);
}

//------------------------------------------------------------------------
static synfig::Point3 firstInkChangePosition(
	const Surface &ras,
	const synfig::Point3 &start,
	const synfig::Point3 &end,
	int threshold,
	const Gamma &gamma )
{
  double dist = (end - start).mag();

  int sampleMax = ceil_to_int(dist), sampleCount = sampleMax + 1;
  double sampleMaxD = double(sampleMax);

  // Get first ink color
  int s;
  synfig::Color color ;

  for (s = 0; s != sampleCount; ++s) 
  {
    synfig::Point3 p  = start * (1 - s/sampleMaxD) +  end * (s/sampleMaxD);
    // const TPixelCM32 &pix = pixel(*ras, p.x, p.y);
    if (checkPixelThreshold(ras,p[0],p[1], threshold)) 
    {
      color = pixelToColor(ras, p[0], p[1], gamma);
      break;
    }
  }

  // Get second color
  for (; s != sampleCount; ++s) 
  {
    synfig::Point3 p = start *(1 - s/sampleMaxD) +  end * (s/sampleMaxD);
    // const TPixelCM32 &pix = pixel(*ras, p.x, p.y);

    if (checkPixelThreshold(ras,p[0],p[1],threshold) && pixelToColor(ras, p[0], p[1], gamma) != color)
     break;
  }

  // Return middle position between s-1 and s
  if (s < sampleCount)
    return start * (1 - ((s - 0.5) / sampleMaxD)) + end * ((s - 0.5) / sampleMaxD);

  return synfig::Point3::nan();
}

// Find color of input sequence. Will be copied to its equivalent stroke.
// Currently in use only on colormaps

// Summary: It is better to test the color to be assigned to the strokes
// to check
// sequences * before * convert them to TStroke (since you lose part
// of the original grip
// to the line). You specify a number of 'taste points' of the broken line
// equidistant from each other,
// on which the value of the corresponding pixel input is taken. If
// identifies a change
// of color, the sequence breaking procedure is launched: yes
// identifies the point
// of breaking, and the sequence s is blocked there; a new one is built
// sequence newSeq e
// sampleColor is re-launched (ras, newSeq, sOpposite). Sequences between two points
// of breaking up
// are inserted into the vector 'globals-> singleSequences'.
// In the case of circular sequences there is a small change: the first point of
// splitting
// * only redefines * the s-node, without introducing new sequences.
// The sequence sOpposite, 'inverse' of s, remains and becomes 'forward-oriented'
// after updating
// of the tail.
// Notice that the break nodes are entered with the signature
// 'SAMPLECOLOR_SIGN'.
// NOTE: The J-S 'upper' graph structure is not altered in here.
// Eventualm. to do outside.

static void sampleColor(
	const etl::handle<synfig::Layer_Bitmap> &ras,
	int threshold,
	Sequence &seq,
	Sequence &seqOpposite,
	SequenceList &singleSequences,
	const Gamma &gamma ) 
{
  SkeletonGraph *currGraph = seq.m_graphHolder;

  // Calculate sequence parametrization
  std::vector<unsigned int> nodes;
  std::vector<double> params;

  // Meanwhile, ensure each point belong to ras. Otherwise, typically an error
  // occured in the thinning process and it's better avoid sampling procedure. Only
  // exception, when
  // a point has x==rsurface.get_w() || y==rsurface.get_h(); that is accepted.
  synfig::rendering::SurfaceResource::LockRead<synfig::rendering::SurfaceSW> lock( ras->rendering_surface ); 
	const Surface &rsurface = lock->get_surface(); 
  {
    const synfig::Point3 &headPos = *currGraph->getNode(seq.m_head);
    // get bounds rectangle with = 0,0,lx -1, ly-1
    if (headPos[0] < 0 || rsurface.get_w() < headPos[0] || headPos[1] < 0 || rsurface.get_h() < headPos[1])// check again and return
        return;
    
  }

  unsigned int curr, currLink, next;
  double meanThickness = currGraph->getNode(seq.m_head)->operator[](2);

  params.push_back(0);
  nodes.push_back(seq.m_head);

  for (curr = seq.m_head, currLink = seq.m_headLink;
       curr != seq.m_tail || params.size() == 1; seq.next(curr, currLink)) 
  {
    next = currGraph->getNode(curr).getLink(currLink).getNext();

    const synfig::Point3 &nextPos = *currGraph->getNode(next);
    if (nextPos[0] < 0 || rsurface.get_w() < nextPos[0] || nextPos[1] < 0 || rsurface.get_h() < nextPos[1]) 
    {
      return;
    }

    params.push_back(params.back() + (*currGraph->getNode(next) - *currGraph->getNode(curr)).mag());
    nodes.push_back(next);

    meanThickness += currGraph->getNode(next)->operator[](2);
  }

  meanThickness /= params.size();

  // Exclude 0-length sequences
  if (params.back() < 0.01) 
  {
    seq.m_color = pixelToColor(
		rsurface,
		currGraph->getNode(seq.m_head)->operator[](0),
		currGraph->getNode(seq.m_head)->operator[](1),
		gamma );
    return;
  }

  // Prepare sampling procedure
  int paramCount = params.size(), paramMax = paramCount - 1;

  int sampleMax = std::max(params.back() / std::max(meanThickness, 1.0),3.0),    // Number of color samples depends on
      sampleCount = sampleMax + 1;  // the ratio params.back() / meanThickness

  std::vector<double> sampleParams(sampleCount);  // Sampling lengths
  std::vector<synfig::Point> samplePoints(sampleCount);  // Image points for color sampling
  std::vector<int> sampleSegments(sampleCount);  // Sequence segment index for the above

  // Sample colors
  for (int s = 0, j = 0; s != sampleCount; ++s) 
  {
    double samplePar = params.back() * (s / double(sampleMax));

    while (j != paramMax && params[j + 1] < samplePar)  // params[j] < samplePar <= params[j+1]
      ++j;

    double t = (samplePar - params[j]) / (params[j + 1] - params[j]);

    synfig::Point3 samplePoint(*currGraph->getNode(nodes[j]) * (1 - t) +
                          *currGraph->getNode(nodes[j + 1]) * t);

    sampleParams[s] = samplePar;
    samplePoints[s] = synfig::Point(
        std::min(samplePoint[0],
                 double(rsurface.get_w() - 1)),  // This deals with sample points at
        std::min(samplePoint[1],
                 double(rsurface.get_h() - 1)));  // the top/right raster border
    sampleSegments[s] = j;
  }

  // NOTE: Extremities of a sequence are considered unreliable: they typically
  // happen
  //       to be junction points shared between possibly different-colored
  //       strokes.

  // Find first and last extremity-free sampled points
  synfig::Point3 first(*currGraph->getNode(seq.m_head));
  synfig::Point3 last(*currGraph->getNode(seq.m_tail));

  int i, k;

  for (i = 1;
       params.back() * i / double(sampleMax) <= first[2] && i < sampleCount; ++i)
    ;
  for (k = sampleMax - 1;
       params.back() * (sampleMax - k) / double(sampleMax) <= last[2] && k >= 0;
       --k)
    ;

  // Give s the first sampled ink color found

  // Initialize with a last-resort reasonable color - not just 0
  seq.m_color = seqOpposite.m_color = pixelToColor(rsurface, samplePoints[0][0], samplePoints[0][1], gamma);

  int l;

  for (l = i - 1; l >= 0; --l) 
  {
    if (checkPixelThreshold(rsurface, samplePoints[l][0], samplePoints[l][1], threshold))
    {
      seq.m_color = seqOpposite.m_color = pixelToColor(rsurface, samplePoints[l][0], samplePoints[l][1], gamma);
      break;
    }
  }

  // Then, look for the first reliable ink
  for (l = i; l <= k; ++l) 
  {
    if (checkPixelThreshold(rsurface,samplePoints[l][0],samplePoints[l][1], threshold))
    {
      seq.m_color = seqOpposite.m_color = pixelToColor(rsurface, samplePoints[l][0], samplePoints[l][1], gamma);
      break;
    }
  }

  if (i >= k) goto _getOut;  // No admissible segment found for splitting
                             // check.
  // Find color changes between sampled colors
  for (l = i; l < k; ++l) 
  {
    const int x1 = samplePoints[l + 1][0], y1 = samplePoints[l + 1][1],
              x2 = samplePoints[l + 2][0], y2 = samplePoints[l + 2][1];
    
    // const TPixelCM32
    //     &nextSample = ras->pixels(x1)[y1],
    //     &nextSample2 = ras->pixels(x2)[y2];  // l < k < sampleMax - so +2 is ok

    if (checkPixelThreshold(rsurface, x1, y1, threshold) &&
        pixelToColor(rsurface, x1, y1, gamma) != seq.m_color &&
        checkPixelThreshold(rsurface, x2, y2, threshold) &&
        pixelToColor(rsurface, x2, y2, gamma) == pixelToColor(rsurface, x1, y1, gamma))  // Ignore single-sample color changes
    {
      // Found a color change - apply splitting procedure
      // NOTE: The function RETURNS BEFORE THE FOR IS CONTINUED!

      synfig::Color nextColor = pixelToColor(rsurface, x1, y1, gamma);

      // Identify split segment
      int u;

      for (u = sampleSegments[l]; u < sampleSegments[l + 1]; ++u) 
      {
        const int x = currGraph->getNode(nodes[u + 1])->operator[](0),
                  y = currGraph->getNode(nodes[u + 1])->operator[](1);
        if (checkPixelThreshold(rsurface, x, y, threshold) && pixelToColor(rsurface, x, y, gamma) != seq.m_color)
         break;
      }

      // Now u indicates the splitting segment. Search for splitting point by
      // binary subdivision.
      const synfig::Point3 &nodeStartPos = *currGraph->getNode(nodes[u]),
                           &nodeEndPos   = *currGraph->getNode(nodes[u + 1]);

      synfig::Point3 splitPoint = firstInkChangePosition(rsurface, nodeStartPos, nodeEndPos, threshold, gamma);

      if (splitPoint == synfig::Point3::nan())
        splitPoint = (nodeStartPos + nodeEndPos) * 0.5; 
      // A color change was found, but could not be precisely located. Just take
      // a reasonable representant. Insert a corresponding new node in basic graph structure.

      unsigned int splitNode = currGraph->newNode(splitPoint);

      unsigned int nodesLink =
          currGraph->getNode(nodes[u]).linkOfNode(nodes[u + 1]);
      currGraph->insert(splitNode, nodes[u], nodesLink);
      *currGraph->node(splitNode).link(0) =
          *currGraph->getNode(nodes[u]).getLink(nodesLink);

      nodesLink = currGraph->getNode(nodes[u + 1]).linkOfNode(nodes[u]);
      currGraph->insert(splitNode, nodes[u + 1], nodesLink);
      *currGraph->node(splitNode).link(1) =
          *currGraph->getNode(nodes[u + 1]).getLink(nodesLink);

      currGraph->node(splitNode).setAttribute(SAMPLECOLOR_SIGN);  // Sign all split-inserted nodes

      if (seq.m_head == seq.m_tail &&
          currGraph->getNode(seq.m_head).getLinksCount() == 2 &&
          !currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) 
      {
        // Circular case: we update s to splitNode and relaunch this very
        // procedure on it.
        seq.m_head = seq.m_tail = splitNode;
        sampleColor(ras, threshold, seq, seqOpposite, singleSequences, gamma);
      } 
      else 
      {
        // Update upper (Joint-Sequence) graph data
        Sequence newSeq;
        newSeq.m_graphHolder = currGraph;
        newSeq.m_head        = splitNode;
        newSeq.m_headLink    = 0;
        newSeq.m_tail        = seq.m_tail;
        newSeq.m_tailLink    = seq.m_tailLink;

        seq.m_tail     = splitNode;
        seq.m_tailLink = 1;  // (link from splitNode to nodes[u] inserted for
                             // second by 'insert')

        seqOpposite.m_graphHolder = seq.m_graphHolder;  // Inform that a split was found

        // NOTE: access on s terminates at newSeq's push_back, due to possible
        // reallocation of globals->singleSequences

        if ((!(seq.m_head == newSeq.m_tail &&
               currGraph->getNode(seq.m_head).getLinksCount() == 2)) &&
            currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN))
          singleSequences.push_back(seq);

        sampleColor(ras, threshold, newSeq, seqOpposite, singleSequences, gamma);
      }

      return;
    }
  }

_getOut:

  // Color changes not found (and therefore no newSeq got pushed back); if a
  // split happened, update sOpposite.
  if (currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) {
    seqOpposite.m_color    = seq.m_color;
    seqOpposite.m_head     = seq.m_tail;
    seqOpposite.m_headLink = seq.m_tailLink;
    seqOpposite.m_tail     = seq.m_head;
    seqOpposite.m_tailLink = seq.m_headLink;
  }
}


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

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

void studio::calculateSequenceColors(const etl::handle<synfig::Layer_Bitmap> &ras, VectorizerCoreGlobals &g, const Gamma &gamma)
{
  int threshold                           = g.currConfig->m_threshold;
  SequenceList &singleSequences           = g.singleSequences;
  JointSequenceGraphList &organizedGraphs = g.organizedGraphs;

  unsigned int i, j, k;
  int l;

  if (ras && g.currConfig->m_maxThickness > 0.0) 
  {
    // singleSequence is traversed back-to-front because new, possibly splitted
    // sequences
    // are inserted at back - and don't have to be re-sampled.
    for (l = singleSequences.size() - 1; l >= 0; --l) 
    {
      Sequence rear;
      sampleColor(ras, threshold, singleSequences[l], rear, singleSequences, gamma);
      // If rear is built, a split occurred and the rear of this
      // single sequence has to be pushed back.
      if (rear.m_graphHolder) singleSequences.push_back(rear);
    }

    for (i = 0; i < organizedGraphs.size(); ++i)
    {
        for (j = 0; j < organizedGraphs[i].getNodesCount(); ++j)
        {
          if (!organizedGraphs[i].getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED))
          {
              for (k = 0; k < organizedGraphs[i].getNode(j).getLinksCount(); ++k) 
              {
                  Sequence &s = *organizedGraphs[i].node(j).link(k);
                  if (s.isForward() && !s.m_graphHolder->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN))
                  {
                    unsigned int next = organizedGraphs[i].node(j).link(k).getNext();
                    unsigned int nextLink = organizedGraphs[i].tailLinkOf(j, k);
                    Sequence &sOpposite = *organizedGraphs[i].node(next).link(nextLink);
                    sampleColor(ras, threshold, s, sOpposite, singleSequences, gamma);
                  }
              }
          }  

        }
    }
      
  }
}

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

// Take samples of image colors to associate each stroke to its corresponding
// palette color. Currently working on colormaps, closest-to-black strokes
// otherwise.
/*
void applyStrokeColors(std::vector<TStroke *> &strokes, const TRasterP &ras,
                       TPalette *palette, VectorizerCoreGlobals &g) {
  JointSequenceGraphList &organizedGraphs = g.organizedGraphs;
  SequenceList &singleSequences           = g.singleSequences;

  TRasterCM32P cm = ras;
  unsigned int i, j, k, n;

  if (cm && g.currConfig->m_maxThickness > 0.0) 
  {
    applyStrokeIndices(&g);

    // Treat single sequences before, like conversionToStrokes(..)
    for (i = 0; i < singleSequences.size(); ++i)
      strokes[i]->setStyle(singleSequences[i].m_color);

    // Then, treat remaining graph-strokes
    n = i;

    for (i = 0; i < organizedGraphs.size(); ++i)
      for (j = 0; j < organizedGraphs[i].getNodesCount(); ++j)
        if (!organizedGraphs[i].getNode(j).hasAttribute(
                JointSequenceGraph::ELIMINATED))  // due to junction recovery
          for (k = 0; k < organizedGraphs[i].getNode(j).getLinksCount(); ++k) {
            Sequence &s = *organizedGraphs[i].node(j).link(k);
            if (s.isForward()) {
              // vi->getStroke(n)->setStyle(s.m_color);
              strokes[n]->setStyle(s.m_color);
              ++n;
            }
          }

    // Order vector image according to actual color-coverings at junctions.
    orderColoredStrokes(organizedGraphs, strokes, cm, palette);
  }
  else
  {
    // Choose closest-to-black palette color
    int blackStyleId = palette->getClosestStyle(TPixel32::Black);

    unsigned int i;
    for (i = 0; i < strokes.size(); ++i) strokes[i]->setStyle(blackStyleId);
  }
}

*/