#include "tregion.h"
#include "tregionoutline.h"
#include "tellipticbrushP.h"
#include "tstrokeoutline.h"
using namespace tellipticbrush;
#define USE_LENGTH
//#define DEBUG_DRAW_TANGENTS
//********************************************************************************
// EXPLANATION
//********************************************************************************
/*! \file tellipticbrush.cpp
\brief This code performs the outlinization of a \a brush stroke with respect to
a
secondary \a path stroke. This is used to draw Adobe Illustrator-like
vectors whose brush is itself a custom vector (and by extension, a
complex
vector image).
Generalization: Introduce a repeat % and a superposition % in the
algorithm
*/
/* TECHNICAL:
We have two strokes: one is the 'guideline', the other is the 'brush', and the
purpose of this algorithm is that of bending the brush to follow the
guideline.
The brush is supposed to be lying horizontally, inside a rectangle
corresponding
to the bounding box of the image containing the brush.
The line segment connecting the midpoints of the vertical image bbox edges
maps
to the guideline's centerline.
Such mapping makes [bbox.x0, bbox.x1] -> [0, 1] in a linear fashion, where the
image interval represents the parametric space of the guideline.
Each vertical scanline of the bbox is further mapped to the segment extruding
from
the guideline's centerline along its normal (multiplied in length by the
guideline's thickness).
*/
//********************************************************************************
// Geometric helpers
//********************************************************************************
namespace {
double getX(const TThickPoint &P0, const TThickPoint &P1, const TThickPoint &P2,
double t) {
double one_t = 1.0 - t;
return P0.x * sq(one_t) + 2.0 * P1.x * t * one_t + P2.x * sq(t);
}
//--------------------------------------------------------------------------------------------
TPointD normal(const TPointD &p, bool left) {
TPointD n(-p.y, p.x);
return (1.0 / norm(n)) * (left ? n : -n);
}
//--------------------------------------------------------------------------------------------
TPointD normal(const TPointD &p) {
return (1.0 / norm(p)) * TPointD(-p.y, p.x);
}
//--------------------------------------------------------------------------------------------
void getHRange(const TThickQuadratic &ttq, double &x0, double &x1) {
const TPointD &P0 = ttq.getP0();
const TPointD &P1 = ttq.getP1();
const TPointD &P2 = ttq.getP2();
// Get the horizontal range of the chunk
x0 = std::min({x0, P0.x, P2.x}), x1 = std::max({x1, P0.x, P2.x});
double t = (P0.x - P1.x) / (P0.x + P2.x - 2.0 * P1.x);
if (t > 0.0 && t < 1.0) {
double x = getX(P0, P1, P2, t);
x0 = std::min(x0, x), x1 = std::max(x1, x);
}
}
void getHRange(const TThickQuadratic &ttq, double t0, double t1, double &x0,
double &x1) {
const TPointD &P0 = ttq.getP0();
const TPointD &P1 = ttq.getP1();
const TPointD &P2 = ttq.getP2();
double x0_ = getX(P0, P1, P2, t0);
double x1_ = getX(P0, P1, P2, t1);
// Get the horizontal range of the chunk
x0 = std::min({x0, x0_, x1_}), x1 = std::max({x1, x0_, x1_});
double t = (P0.x - P1.x) / (P0.x + P2.x - 2.0 * P1.x);
if (t > t0 && t < t1) {
double x = getX(P0, P1, P2, t);
x0 = std::min(x0, x), x1 = std::max(x1, x);
}
}
void getHRange(const TStroke &stroke, double &x0, double &x1) {
int i, nChunks = stroke.getChunkCount();
for (i = 0; i < nChunks; ++i) getHRange(*stroke.getChunk(i), x0, x1);
}
//********************************************************************************
// Outlinization Data
//********************************************************************************
struct StrokeOutlinizationData final
: public tellipticbrush::OutlinizationData {
double m_x0, m_x1, m_xRange;
double m_y0, m_yScale;
public:
StrokeOutlinizationData() : OutlinizationData() {}
StrokeOutlinizationData(const TStroke &stroke, const TRectD &strokeBox,
const TOutlineUtil::OutlineParameter &options)
: OutlinizationData(options)
, m_x0(strokeBox.x0)
, m_x1(strokeBox.x1)
, m_xRange(m_x1 - m_x0)
, m_y0(0.5 * (strokeBox.y0 + strokeBox.y1))
, m_yScale(1.0 / (strokeBox.y1 - strokeBox.y0)) {}
void buildPoint(const CenterlinePoint &p, bool isNextD, CenterlinePoint &ref,
bool isRefNextD, CenterlinePoint &out);
int buildPoints(const CenterlinePoint &p, CenterlinePoint &ref,
CenterlinePoint *out);
int buildPoints(const TStroke &stroke, const TStroke &path,
CenterlinePoint &cp, CenterlinePoint *out);
bool getChunkAndT_param(const TStroke &path, double x, int &chunk, double &t);
bool getChunkAndT_length(const TStroke &path, double x, int &chunk,
double &t);
double toW(double x);
};
//--------------------------------------------------------------------------------------------
double StrokeOutlinizationData::toW(double x) {
return tcrop((x - m_x0) / m_xRange, 0.0, 1.0);
}
//--------------------------------------------------------------------------------------------
bool StrokeOutlinizationData::getChunkAndT_param(const TStroke &path, double x,
int &chunk, double &t) {
double w = toW(x);
return !path.getChunkAndT(w, chunk, t);
}
//--------------------------------------------------------------------------------------------
bool StrokeOutlinizationData::getChunkAndT_length(const TStroke &path, double x,
int &chunk, double &t) {
double s = toW(x) * path.getLength();
return !path.getChunkAndTAtLength(s, chunk, t);
}
//--------------------------------------------------------------------------------------------
void StrokeOutlinizationData::buildPoint(const CenterlinePoint &p, bool pNextD,
CenterlinePoint &ref, bool refNextD,
CenterlinePoint &out) {
TThickPoint &refD = refNextD ? ref.m_nextD : ref.m_prevD;
const TThickPoint *pD;
TThickPoint *outD;
bool *outHasD;
if (pNextD) {
pD = &p.m_nextD;
outD = &out.m_nextD;
outHasD = &out.m_hasNextD;
} else {
pD = &p.m_prevD;
outD = &out.m_prevD;
outHasD = &out.m_hasPrevD;
}
// Build position
refD = (1.0 / norm(refD)) * refD;
TPointD normalDirection(-refD.y, refD.x);
double yPercentage = (p.m_p.y - m_y0) * m_yScale;
double yRelative = yPercentage * ref.m_p.thick;
double yFactor = ref.m_p.thick * m_yScale;
out.m_p = TThickPoint(ref.m_p.x + yRelative * normalDirection.x,
ref.m_p.y + yRelative * normalDirection.y,
p.m_p.thick * yFactor);
// Build direction
double stretchedDY = pD->x * yPercentage * refD.thick + pD->y * yFactor;
*outD = TThickPoint(refD.x * pD->x - refD.y * stretchedDY,
refD.y * pD->x + refD.x * stretchedDY,
pD->thick * (1.0 + refD.thick));
bool covered = (sq(outD->x) + sq(outD->y) < sq(outD->thick) + tolPar);
out.m_covered = out.m_covered && covered;
*outHasD = *outHasD && !covered;
}
//--------------------------------------------------------------------------------------------
/* EXPLANATION:
\ _ \ The path stroke with centerline point C has 2 outward
\ \ \ directions (prevD and nextD), which may be different.
\ |* \ Therefore, there may also be 2 different envelope
directions
\ * . \ from C (the *s in the drawing).
C . | When the brush stroke 'hits' one envelope direction, it
/ * . / transfers on the other e.d., and continues on that side.
/ _ / * /
/ / /
| /
*/
//! Build points resulting from the association between p (must have pos and
//! dirs
//! already built) and ref, returning the number of output points stored in
//! out[] (at max 2).
int StrokeOutlinizationData::buildPoints(const CenterlinePoint &p,
CenterlinePoint &ref,
CenterlinePoint *out) {
out[0] = out[1] = p;
out[0].m_covered = out[1].m_covered = true; // Coverage is rebuilt
bool refSymmetric =
ref.m_hasPrevD && ref.m_hasNextD && ref.m_nextD == ref.m_prevD;
bool pSymmetric = p.m_hasPrevD && p.m_hasNextD && p.m_nextD == p.m_prevD;
// Build prev
bool prevSideIsNext =
(p.m_prevD.x < 0) ? true : (p.m_prevD.x > 0) ? false : ref.m_hasNextD;
bool hasPrev =
p.m_hasPrevD && (prevSideIsNext ? ref.m_hasNextD : ref.m_hasPrevD);
int prevIdx = hasPrev ? 0 : -1;
if (hasPrev) {
CenterlinePoint &outPoint = out[prevIdx];
buildPoint(p, false, ref, prevSideIsNext, outPoint);
}
if (refSymmetric && pSymmetric) {
// Copy prev to next
if (hasPrev) {
CenterlinePoint &outPoint = out[prevIdx];
outPoint.m_hasNextD = outPoint.m_hasPrevD;
outPoint.m_nextD = outPoint.m_prevD;
return 1;
}
return 0;
}
// Build next
bool nextSideIsNext =
(p.m_nextD.x > 0) ? true : (p.m_nextD.x < 0) ? false : ref.m_hasNextD;
bool hasNext =
p.m_hasNextD && (nextSideIsNext ? ref.m_hasNextD : ref.m_hasPrevD);
int nextIdx =
hasNext ? hasPrev ? ((int)prevSideIsNext != nextSideIsNext) : 0 : -1;
if (hasNext) {
CenterlinePoint &outPoint = out[nextIdx];
buildPoint(p, true, ref, nextSideIsNext, outPoint);
}
// Fill in unbuilt directions if necessary
if (hasPrev && hasNext && prevIdx != nextIdx) {
CenterlinePoint &outPrev = out[prevIdx];
CenterlinePoint &outNext = out[nextIdx];
if (dist(outPrev.m_p, outNext.m_p) > 1e-4) {
// If there are 2 full output points, make their unbuilt directions match
outPrev.m_nextD = outNext.m_prevD = 0.5 * (outNext.m_p - outPrev.m_p);
bool covered = (sq(outPrev.m_nextD.x) + sq(outPrev.m_nextD.y) <
sq(outPrev.m_nextD.thick) + tolPar);
outPrev.m_hasNextD = outNext.m_hasPrevD = !covered;
outPrev.m_covered = outPrev.m_covered && covered;
outNext.m_covered = outNext.m_covered && covered;
} else {
// Merge the 2 existing ones
nextIdx = prevIdx;
outPrev.m_nextD = outNext.m_nextD;
outPrev.m_hasNextD = outNext.m_hasPrevD;
outPrev.m_covered = outPrev.m_covered && outNext.m_covered;
}
}
return std::max(prevIdx, nextIdx) + 1;
}
//--------------------------------------------------------------------------------------------
int StrokeOutlinizationData::buildPoints(const TStroke &stroke,
const TStroke &path,
CenterlinePoint &cp,
CenterlinePoint *out) {
const TThickQuadratic &ttq = *stroke.getChunk(cp.m_chunkIdx);
const TThickPoint &P0 = ttq.getP0();
const TThickPoint &P1 = ttq.getP1();
const TThickPoint &P2 = ttq.getP2();
double x = getX(P0, P1, P2, cp.m_t);
double pathT;
int pathChunk;
#ifdef USE_LENGTH
bool ok = getChunkAndT_length(path, x, pathChunk, pathT);
#else
bool ok = getChunkAndT_param(path, x, pathChunk, pathT);
#endif
assert(ok);
CenterlinePoint pathCp(pathChunk, pathT);
cp.buildPos(stroke);
cp.buildDirs(stroke);
pathCp.buildPos(path);
pathCp.buildDirs(path);
return buildPoints(cp, pathCp, out);
}
//********************************************************************************
// Path-Altered Brush Linearizator (base class)
//********************************************************************************
class ReferenceLinearizator : public tellipticbrush::StrokeLinearizator {
protected:
const TStroke *m_path;
StrokeOutlinizationData m_data;
public:
ReferenceLinearizator(const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data);
virtual void linearize(std::vector<CenterlinePoint> &cPoints, int chunk,
double t1) = 0;
};
//--------------------------------------------------------------------------------------------
ReferenceLinearizator::ReferenceLinearizator(
const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data)
: StrokeLinearizator(stroke), m_path(path), m_data(data) {}
//********************************************************************************
// Brush Linearizator on Path inter-chunk points
//********************************************************************************
class ReferenceChunksLinearizator final : public ReferenceLinearizator {
double m_w0, m_w1;
public:
ReferenceChunksLinearizator(const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data)
: ReferenceLinearizator(stroke, path, data) {}
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk) override;
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk,
double t1) override;
void addCenterlinePoints(std::vector<CenterlinePoint> &cPoints,
int brushChunk, double x0, double x1);
void addCenterlinePoints(std::vector<CenterlinePoint> &cPoints,
int strokeChunk, double strokeT, int refChunk);
};
//--------------------------------------------------------------------------------------------
void ReferenceChunksLinearizator::linearize(
std::vector<CenterlinePoint> &cPoints, int chunk) {
// Get the stroke chunk
const TThickQuadratic &ttq = *this->m_stroke->getChunk(chunk);
// Get the chunk's horizontal range
double x0 = (std::numeric_limits<double>::max)(), x1 = -x0;
getHRange(ttq, x0, x1);
// Now, we have to add all points corresponding to the intersections between
// the relative
// vertical projection of path's chunk endpoints, and the stroke
addCenterlinePoints(cPoints, chunk, x0, x1);
}
//--------------------------------------------------------------------------------------------
void ReferenceChunksLinearizator::linearize(
std::vector<CenterlinePoint> &cPoints, int chunk, double t1) {
if (cPoints.empty()) return;
// Get the stroke chunk
const TThickQuadratic &ttq = *this->m_stroke->getChunk(chunk);
// Get the chunk's horizontal range
double x0 = (std::numeric_limits<double>::max)(), x1 = -x0;
getHRange(ttq, cPoints[0].m_t, t1, x0, x1);
// Now, we have to add all points corresponding to the intersections between
// the relative
// vertical projection of path's chunk endpoints, and the stroke
addCenterlinePoints(cPoints, chunk, x0, x1);
}
//--------------------------------------------------------------------------------------------
void ReferenceChunksLinearizator::addCenterlinePoints(
std::vector<CenterlinePoint> &cPoints, int chunk, double x0, double x1) {
const TThickQuadratic &ttq = *this->m_stroke->getChunk(chunk);
const TStroke &path = *this->m_path;
int chunk0, chunk1;
double t0, t1;
#ifdef USE_LENGTH
bool ok0 = m_data.getChunkAndT_length(path, x0, chunk0, t0);
bool ok1 = m_data.getChunkAndT_length(path, x1, chunk1, t1);
#else
bool ok0 = m_data.getChunkAndT_param(path, x0, chunk0, t0);
bool ok1 = m_data.getChunkAndT_param(path, x1, chunk1, t1);
#endif
assert(ok0 && ok1);
const TPointD &P0 = ttq.getP0();
const TPointD &P1 = ttq.getP1();
const TPointD &P2 = ttq.getP2();
double A = P0.x + P2.x - 2.0 * P1.x;
double B = P1.x - P0.x;
double delta_ = sq(B) - P0.x * A; // actual delta = delta_ + x * A;
int i, initialSize = cPoints.size();
for (i = chunk0; i < chunk1; ++i) {
#ifdef USE_LENGTH
double s = std::min(path.getLength(i, 1.0) / path.getLength(), 1.0);
double x = m_data.m_x0 + m_data.m_xRange * s;
#else
double w = path.getW(i, 1.0);
double x = m_data.m_x0 + m_data.m_xRange * w;
#endif
double delta = delta_ + x * A;
if (delta < 0) continue;
// Add first solution
double t = (sqrt(delta) - B) / A;
if (t > 0.0 && t < 1.0) // 0 and 1 are dealt outside
addCenterlinePoints(cPoints, chunk, t, i);
if (delta < tolPar) continue;
// Add second solution
t = -(sqrt(delta) + B) / A;
if (t > 0.0 && t < 1.0) addCenterlinePoints(cPoints, chunk, t, i);
}
// As points may be mixed (by parameter), sort them.
std::sort(cPoints.begin() + initialSize, cPoints.end());
}
//--------------------------------------------------------------------------------------------
void ReferenceChunksLinearizator::addCenterlinePoints(
std::vector<CenterlinePoint> &cPoints, int strokeChunk, double strokeT,
int refChunk) {
CenterlinePoint p(strokeChunk, strokeT);
CenterlinePoint ref(refChunk, 1.0);
CenterlinePoint newPoints[2];
p.buildPos(*m_stroke);
p.buildDirs(*m_stroke);
ref.buildPos(*m_path);
ref.buildDirs(*m_path);
int i, count = m_data.buildPoints(p, ref, newPoints);
for (i = 0; i < count; ++i) cPoints.push_back(newPoints[i]);
}
//********************************************************************************
// Recursive (regular) Reference Stroke Linearizator
//********************************************************************************
class RecursiveReferenceLinearizator final : public ReferenceLinearizator {
public:
typedef void (RecursiveReferenceLinearizator::*SubdivisorFuncPtr)(
std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1);
SubdivisorFuncPtr m_subdivisor;
public:
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk) override;
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk,
double t1) override;
void subdivide(std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1);
void subdivideCenterline(std::vector<CenterlinePoint> &cPoints,
CenterlinePoint &cp0, CenterlinePoint &cp1);
public:
RecursiveReferenceLinearizator(const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data)
: ReferenceLinearizator(stroke, path, data)
, m_subdivisor(&RecursiveReferenceLinearizator::subdivide) {}
};
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::linearize(
std::vector<CenterlinePoint> &cPoints, int chunk) {
linearize(cPoints, chunk, 1.0);
}
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::linearize(
std::vector<CenterlinePoint> &cPoints, int chunk, double t1) {
if (cPoints.empty()) return;
const TStroke &stroke = *this->m_stroke;
const TThickQuadratic &ttq = *stroke.getChunk(chunk);
const TStroke &path = *this->m_path;
// Sort the interval (SHOULD BE DONE OUTSIDE?)
std::stable_sort(cPoints.begin(), cPoints.end());
std::vector<CenterlinePoint> addedPoints;
unsigned int i, size_1 = cPoints.size() - 1;
for (i = 0; i < size_1; ++i) {
CenterlinePoint &cp1 = cPoints[i], cp2 = cPoints[i + 1];
if (cp2.m_t - cp1.m_t > 1e-4)
(this->*m_subdivisor)(addedPoints, cPoints[i], cPoints[i + 1]);
}
if (cPoints[size_1].m_t < t1) {
double t, x = (t1 == 1.0) ? ttq.getP2().x
: getX(ttq.getP0(), ttq.getP1(), ttq.getP2(), t1);
int refChunk;
#ifdef USE_LENGTH
bool ok = m_data.getChunkAndT_length(path, x, refChunk, t);
#else
bool ok = m_data.getChunkAndT_param(path, x, refChunk, t);
#endif
CenterlinePoint strokeCpEnd(chunk, t1);
CenterlinePoint refCp(refChunk, t);
CenterlinePoint newPoints[2];
strokeCpEnd.buildPos(*m_stroke);
strokeCpEnd.buildDirs(*m_stroke);
refCp.buildPos(*m_path);
refCp.buildDirs(*m_path);
int count = m_data.buildPoints(strokeCpEnd, refCp, newPoints);
if (count == 1) // Otherwise, this is either impossible, or already covered
(this->*m_subdivisor)(addedPoints, cPoints[size_1], newPoints[0]);
}
cPoints.insert(cPoints.end(), addedPoints.begin(), addedPoints.end());
}
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::subdivide(
std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1) {
if (!(cp0.m_hasNextD && cp1.m_hasPrevD)) return;
const TStroke &stroke = *this->m_stroke;
const TThickQuadratic &ttq = *stroke.getChunk(cp0.m_chunkIdx);
const TStroke &path = *this->m_path;
// Build the distance of next from the outline of cp's 'envelope extension'
TPointD envDirL0, envDirR0, envDirL1, envDirR1;
buildEnvelopeDirections(cp0.m_p, cp0.m_nextD, envDirL0, envDirR0);
buildEnvelopeDirections(cp1.m_p, cp1.m_prevD, envDirL1, envDirR1);
TPointD diff(convert(cp1.m_p) - convert(cp0.m_p));
double d = std::max(fabs(envDirL0 * (diff + cp1.m_p.thick * envDirL1 -
cp0.m_p.thick * envDirL0)),
fabs(envDirR0 * (diff + cp1.m_p.thick * envDirR1 -
cp0.m_p.thick * envDirR0)));
if (d > m_data.m_pixSize && cp1.m_t - cp0.m_t > 1e-4) {
CenterlinePoint strokeMidPoint(cp0.m_chunkIdx, 0.5 * (cp0.m_t + cp1.m_t));
CenterlinePoint newPoints[2];
int count = m_data.buildPoints(*this->m_stroke, *this->m_path,
strokeMidPoint, newPoints);
if (count == 1) // Otherwise, this is either impossible, or already covered
{
subdivide(cPoints, cp0,
newPoints[0]); // should I use strokeMidPoint(Prev) here ?
subdivide(cPoints, newPoints[0], cp1);
cPoints.push_back(newPoints[0]);
}
}
}
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::subdivideCenterline(
std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1) {
if (cp0.m_covered || !cp0.m_hasNextD) return;
// Build the distance of next from cp's 'direction extension'
TPointD dir((1.0 / norm(cp0.m_nextD)) * cp0.m_nextD);
TPointD diff(convert(cp1.m_p) - convert(cp0.m_p));
double d = fabs(dir.x * diff.y - dir.y * diff.x);
if (d > m_data.m_pixSize && cp1.m_t - cp0.m_t > 1e-4) {
CenterlinePoint strokeMidPoint(cp0.m_chunkIdx, 0.5 * (cp0.m_t + cp1.m_t));
CenterlinePoint newPoints[2];
int count = m_data.buildPoints(*this->m_stroke, *this->m_path,
strokeMidPoint, newPoints);
if (count == 1) // Otherwise, this is either impossible, or already covered
{
subdivide(cPoints, cp0,
newPoints[0]); // should I use strokeMidPoint(Prev) here ?
subdivide(cPoints, newPoints[0], cp1);
cPoints.push_back(newPoints[0]);
}
}
}
//********************************************************************************
// Make Outline Implementation (stroke version)
//********************************************************************************
//********************************************************************************
// Make Outline Implementation (stroke version)
//********************************************************************************
/*
Quick container to store all the linearization features to be supported.
\note The set should be appropriately ordered so that linearizator dependence
can be supported (linearizators may work depending on knowledge of the other
linearized points)
*/
struct LinearizatorsSet {
static const int nLinearizators = 2;
ReferenceChunksLinearizator m_refChunksLinearizator;
RecursiveReferenceLinearizator m_recursiveRefLinearizator;
ReferenceLinearizator *m_linearizatorPtrs[nLinearizators];
public:
LinearizatorsSet(const TStroke &stroke, const TStroke &path,
const StrokeOutlinizationData &data)
: m_refChunksLinearizator(&stroke, &path, data)
, m_recursiveRefLinearizator(&stroke, &path, data) {
m_linearizatorPtrs[0] = &m_refChunksLinearizator;
m_linearizatorPtrs[1] = &m_recursiveRefLinearizator;
}
ReferenceLinearizator *operator[](int i) { return m_linearizatorPtrs[i]; }
const int size() const { return nLinearizators; }
};
} // namespace
//============================================================================================
void TOutlineUtil::makeOutline(const TStroke &path, const TStroke &stroke,
const TRectD &strokeBox, TStrokeOutline &outline,
const TOutlineUtil::OutlineParameter &options) {
// Build outlinization data
StrokeOutlinizationData data(stroke, strokeBox, options);
// Build a set of linearizators for the specified stroke
LinearizatorsSet linearizators(stroke, path, data);
CenterlinePoint newPoints[2];
std::vector<CenterlinePoint> cPoints, chunkPoints;
int i, chunksCount = stroke.getChunkCount();
for (i = 0; i < chunksCount; ++i) {
chunkPoints.clear();
CenterlinePoint cp(i, 0.0);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) chunkPoints.push_back(newPoints[j]);
int linearsCount = linearizators.size();
for (j = 0; j < linearsCount; ++j) {
StrokeLinearizator *linearizator = linearizators[j];
linearizator->linearize(chunkPoints, i);
}
// These points are just PUSH_BACK'd to the vector. A sorting must be
// performed
// before storing them in the overall centerline points vector
std::stable_sort(chunkPoints.begin(), chunkPoints.end());
cPoints.insert(cPoints.end(), chunkPoints.begin(), chunkPoints.end());
}
// Build the final point.
CenterlinePoint cPoint(chunksCount - 1, 1.0);
int count = data.buildPoints(stroke, path, cPoint, newPoints);
for (i = 0; i < count; ++i) cPoints.push_back(newPoints[i]);
// If no centerline point was built, no outline point can, too.
// This specifically happens when the supplied path is a point.
if (cPoints.empty()) return;
// In the selfLoop case, use its info to modify the initial point.
if (stroke.isSelfLoop()) {
CenterlinePoint &lastCp = cPoints[cPoints.size() - 1];
cPoints[0].m_prevD = cPoint.m_prevD;
cPoints[0].m_hasPrevD = true;
lastCp.m_nextD = cPoint.m_prevD;
lastCp.m_hasNextD = true;
}
#ifdef DEBUG_DRAW_TANGENTS
{
// Debug - draw centerline directions (derivatives)
glBegin(GL_LINES);
glColor3d(1.0, 0.0, 0.0);
unsigned int i, size = cPoints.size();
for (i = 0; i < size; ++i) {
glVertex2d(cPoints[i].m_p.x, cPoints[i].m_p.y);
glVertex2d(
cPoints[i].m_p.x + cPoints[i].m_nextD.x * cPoints[i].m_p.thick,
cPoints[i].m_p.y + cPoints[i].m_nextD.y * cPoints[i].m_p.thick);
}
glEnd();
}
#endif
// Now, build the outline associated to the linearized centerline
buildOutline(stroke, cPoints, outline, data);
}
//============================================================================================
/*
TRectD TOutlineUtil::computeBBox(const TStroke &stroke, const TStroke& path)
{
typedef TStroke::OutlineOptions OOpts;
//First, calculate the usual stroke bbox
TRectD roundBBox(::computeBBox(stroke));
const OOpts& oOptions(stroke.outlineOptions());
if(oOptions.m_capStyle != OOpts::PROJECTING_CAP && oOptions.m_joinStyle !=
OOpts::MITER_JOIN)
return roundBBox;
//Build interesting centerline points (in this case, junction points)
std::vector<CenterlinePoint> cPoints;
int i, chunksCount = stroke.getChunkCount();
for(i=0; i<chunksCount; ++i)
{
CenterlinePoint cPoint(i, 0.0);
cPoint.buildPos(stroke);
cPoint.buildDirs(stroke);
cPoints.push_back(cPoint);
}
//Build the final point.
CenterlinePoint cPoint(chunksCount-1, 1.0);
cPoint.buildPos(stroke);
cPoint.buildDirs(stroke);
//In the selfLoop case, use its info to modify the initial point.
if(stroke.isSelfLoop())
{
cPoints[0].m_prevD = cPoint.m_prevD;
cPoints[0].m_hasPrevD = true;
cPoint.m_nextD = cPoint.m_prevD;
cPoint.m_hasNextD = true;
}
cPoints.push_back(cPoint);
//Now, add the associated 'extending' outline points
OutlineBuilder outBuilder(OutlinizationData(), stroke);
TRectD extensionBBox(
(std::numeric_limits<double>::max)(),
(std::numeric_limits<double>::max)(),
-(std::numeric_limits<double>::max)(),
-(std::numeric_limits<double>::max)()
);
unsigned int j, cPointsCount = cPoints.size();
for(j=0; ; ++j)
{
//Search the next uncovered point
for(; j < cPointsCount && cPoints[j].m_covered; ++j)
;
if(j >= cPointsCount)
break;
//Build the associated outline points
outBuilder.buildOutlineExtensions(extensionBBox, cPoints[j]);
}
//Finally, merge the 2 bboxes
return roundBBox + extensionBBox;
}
*/
//============================================================================================
namespace {
void makeCenterline(const TStroke &path, const TEdge &edge,
const TRectD ®ionBox, std::vector<T3DPointD> &outline) {
int initialOutlineSize = outline.size();
static const TOutlineUtil::OutlineParameter options;
const TStroke &stroke = *edge.m_s;
double w0 = edge.m_w0, w1 = edge.m_w1;
bool reversed = w1 < w0;
if (reversed) w0 = edge.m_w1, w1 = edge.m_w0;
// Build outlinization data
StrokeOutlinizationData data(stroke, regionBox, options);
// Build a set of linearizators for the specified stroke
LinearizatorsSet linearizators(stroke, path, data);
CenterlinePoint newPoints[2];
linearizators.m_recursiveRefLinearizator.m_subdivisor =
&RecursiveReferenceLinearizator::subdivideCenterline;
std::vector<CenterlinePoint> chunkPoints;
int i, chunk0, chunk1;
double t0, t1;
bool ok0 = !edge.m_s->getChunkAndT(w0, chunk0, t0);
bool ok1 = !edge.m_s->getChunkAndT(w1, chunk1, t1);
assert(ok0 && ok1);
double tStart = t0;
for (i = chunk0; i < chunk1; ++i, tStart = 0.0) {
chunkPoints.clear();
CenterlinePoint cp(i, tStart);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) chunkPoints.push_back(newPoints[j]);
int linearsCount = linearizators.size();
for (j = 0; j < linearsCount; ++j) {
ReferenceLinearizator *linearizator = linearizators[j];
linearizator->linearize(chunkPoints, i, 1.0);
}
// These points are just PUSH_BACK'd to the vector. A sorting must be
// performed
// before storing them in the overall centerline points vector
std::stable_sort(chunkPoints.begin(), chunkPoints.end());
int size = chunkPoints.size();
outline.reserve(outline.size() + size);
for (j = 0; j < size; ++j) {
const TPointD &point = chunkPoints[j].m_p;
outline.push_back(T3DPointD(point.x, point.y, 0.0));
}
}
// The last one with t1 as endpoint
{
chunkPoints.clear();
CenterlinePoint cp(chunk1, tStart);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) chunkPoints.push_back(newPoints[j]);
int linearsCount = linearizators.size();
for (j = 0; j < linearsCount; ++j) {
ReferenceLinearizator *linearizator = linearizators[j];
linearizator->linearize(chunkPoints, chunk1, t1);
}
std::stable_sort(chunkPoints.begin(), chunkPoints.end());
int size = chunkPoints.size();
outline.reserve(outline.size() + size);
for (j = 0; j < size; ++j) {
const TPointD &point = chunkPoints[j].m_p;
outline.push_back(T3DPointD(point.x, point.y, 0.0));
}
}
// Finally, add the endpoint
CenterlinePoint cp(chunk1, t1);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) {
const TPointD &point = newPoints[j].m_p;
outline.push_back(T3DPointD(point.x, point.y, 0.0));
}
// Eventually, reverse the output
if (reversed)
std::reverse(outline.begin() + initialOutlineSize, outline.end());
}
//--------------------------------------------------------------------------------------------
void makeOutlineRaw(const TStroke &path, const TRegion ®ion,
const TRectD ®ionBox, std::vector<T3DPointD> &outline) {
// Deal with each edge independently
int e, edgesCount = region.getEdgeCount();
for (e = 0; e < edgesCount; ++e)
makeCenterline(path, *region.getEdge(e), regionBox, outline);
}
} // namespace
//--------------------------------------------------------------------------------------------
void TOutlineUtil::makeOutline(const TStroke &path, const TRegion ®ion,
const TRectD ®ionBox,
TRegionOutline &outline) {
outline.m_doAntialiasing = true;
// Build the external boundary
{
outline.m_exterior.resize(1);
outline.m_exterior[0].clear();
makeOutlineRaw(path, region, regionBox, outline.m_exterior[0]);
}
// Build internal boundaries
{
outline.m_interior.clear();
int i, subRegionNumber = region.getSubregionCount();
outline.m_interior.resize(subRegionNumber);
for (i = 0; i < subRegionNumber; i++)
makeOutlineRaw(path, *region.getSubregion(i), regionBox,
outline.m_interior[i]);
}
outline.m_bbox = region.getBBox();
}