|
|
faaf7d |
/*
|
|
|
faaf7d |
......... 2015 Ivan Mahonin
|
|
|
faaf7d |
|
|
|
faaf7d |
This program is free software: you can redistribute it and/or modify
|
|
|
faaf7d |
it under the terms of the GNU General Public License as published by
|
|
|
faaf7d |
the Free Software Foundation, either version 3 of the License, or
|
|
|
faaf7d |
(at your option) any later version.
|
|
|
faaf7d |
|
|
|
faaf7d |
This program is distributed in the hope that it will be useful,
|
|
|
faaf7d |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
faaf7d |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
faaf7d |
GNU General Public License for more details.
|
|
|
faaf7d |
|
|
|
faaf7d |
You should have received a copy of the GNU General Public License
|
|
|
faaf7d |
along with this program. If not, see <http: licenses="" www.gnu.org="">.</http:>
|
|
|
faaf7d |
*/
|
|
|
faaf7d |
|
|
|
93cbac |
#include <cassert></cassert>
|
|
|
faaf7d |
|
|
|
faaf7d |
#include "contour.h"
|
|
|
faaf7d |
|
|
|
faaf7d |
|
|
|
faaf7d |
using namespace std;
|
|
|
faaf7d |
|
|
|
faaf7d |
|
|
|
faaf7d |
const Vector Contour::blank;
|
|
|
faaf7d |
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::clear() {
|
|
|
faaf7d |
if (!chunks.empty()) {
|
|
|
faaf7d |
chunks.clear();
|
|
|
faaf7d |
first = 0;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::move_to(const Vector &v) {
|
|
|
faaf7d |
if (chunks.empty()) {
|
|
|
faaf7d |
if (!v.is_equal_to(blank))
|
|
|
faaf7d |
chunks.push_back(Chunk(MOVE, v));
|
|
|
faaf7d |
} else {
|
|
|
faaf7d |
if (!v.is_equal_to(chunks.back().p1)) {
|
|
|
faaf7d |
if (chunks.back().type == MOVE)
|
|
|
faaf7d |
chunks.back().p1 = v;
|
|
|
faaf7d |
else
|
|
|
faaf7d |
if (chunks.back().type == CLOSE)
|
|
|
faaf7d |
chunks.push_back(Chunk(MOVE, v));
|
|
|
faaf7d |
else {
|
|
|
faaf7d |
chunks.push_back(Chunk(CLOSE, chunks[first].p1));
|
|
|
faaf7d |
chunks.push_back(Chunk(MOVE, v));
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
first = chunks.size();
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::line_to(const Vector &v) {
|
|
|
faaf7d |
if (!v.is_equal_to(current()))
|
|
|
faaf7d |
chunks.push_back(Chunk(LINE, v));
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::conic_to(const Vector &v, const Vector &t) {
|
|
|
faaf7d |
if (!v.is_equal_to(current()))
|
|
|
faaf7d |
chunks.push_back(Chunk(CONIC, v, t));
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::cubic_to(const Vector &v, const Vector &t0, const Vector &t1) {
|
|
|
faaf7d |
if (!v.is_equal_to(current()))
|
|
|
faaf7d |
chunks.push_back(Chunk(CUBIC, v, t0, t1));
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::close() {
|
|
|
faaf7d |
if (chunks.size() > first) {
|
|
|
faaf7d |
if (first > 0)
|
|
|
faaf7d |
chunks.push_back(Chunk(CLOSE, chunks[first-1].p1));
|
|
|
faaf7d |
else
|
|
|
faaf7d |
chunks.push_back(Chunk(CLOSE, blank));
|
|
|
faaf7d |
first = chunks.size();
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::line_split(
|
|
|
faaf7d |
Rect &ref_line_bounds,
|
|
|
faaf7d |
const Rect &bounds,
|
|
|
faaf7d |
const Vector &min_size,
|
|
|
d989ab |
const Vector &p1,
|
|
|
d989ab |
int level )
|
|
|
faaf7d |
{
|
|
|
d989ab |
assert(level > 0);
|
|
|
d989ab |
|
|
|
d989ab |
if (allow_split_lines) {
|
|
|
d989ab |
const Vector &p0 = current();
|
|
|
d989ab |
if ( fabs(p1.x - p0.x) > min_size.x
|
|
|
d989ab |
|| fabs(p1.y - p0.y) > min_size.y )
|
|
|
d989ab |
{
|
|
|
d989ab |
Vector p = (p0 + p1)*0.5;
|
|
|
d989ab |
line_split(ref_line_bounds, bounds, min_size, p, level-1);
|
|
|
5890eb |
line_split(ref_line_bounds, bounds, min_size, p1, level-1);
|
|
|
d989ab |
return;
|
|
|
d989ab |
}
|
|
|
d989ab |
}
|
|
|
d989ab |
|
|
|
faaf7d |
line_to(p1);
|
|
|
faaf7d |
return;
|
|
|
faaf7d |
|
|
|
faaf7d |
// TODO: fix bugs
|
|
|
faaf7d |
|
|
|
faaf7d |
ref_line_bounds = ref_line_bounds.expand(p1);
|
|
|
faaf7d |
|
|
|
faaf7d |
if (bounds.intersects(ref_line_bounds)) {
|
|
|
faaf7d |
Vector s = ref_line_bounds.p1 - ref_line_bounds.p0;
|
|
|
faaf7d |
if ( fabs(s.x) > min_size.x
|
|
|
faaf7d |
|| fabs(s.y) > min_size.y )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
line_to(p1);
|
|
|
faaf7d |
ref_line_bounds.p0 = p1;
|
|
|
faaf7d |
ref_line_bounds.p1 = p1;
|
|
|
faaf7d |
return;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
if (!chunks.empty())
|
|
|
faaf7d |
chunks.back().p1 = p1;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::conic_split(
|
|
|
faaf7d |
Rect &ref_line_bounds,
|
|
|
faaf7d |
const Rect &bounds,
|
|
|
faaf7d |
const Vector &min_size,
|
|
|
faaf7d |
const Vector &p1,
|
|
|
faaf7d |
const Vector ¢er,
|
|
|
93cbac |
Real radius,
|
|
|
93cbac |
Real radians0,
|
|
|
93cbac |
Real radians1,
|
|
|
faaf7d |
int level )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
assert(level > 0);
|
|
|
faaf7d |
|
|
|
faaf7d |
const Vector &p0 = current();
|
|
|
faaf7d |
if ( fabs(p1.x - p0.x) > min_size.x
|
|
|
faaf7d |
|| fabs(p1.y - p0.y) > min_size.y )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
Rect b = conic_bounds(p0, p1, center, radius, radians0, radians1);
|
|
|
faaf7d |
if (bounds.intersects(b)) {
|
|
|
93cbac |
Real radians = 0.5*(radians0 + radians1);
|
|
|
faaf7d |
Vector p( radius*cos(radians) + center.x,
|
|
|
faaf7d |
radius*sin(radians) + center.y );
|
|
|
faaf7d |
conic_split(ref_line_bounds, bounds, min_size, p, center, radius, radians0, radians, level - 1);
|
|
|
faaf7d |
conic_split(ref_line_bounds, bounds, min_size, p1, center, radius, radians, radians1, level - 1);
|
|
|
faaf7d |
return;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
line_split(ref_line_bounds, bounds, min_size, p1);
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::cubic_split(
|
|
|
faaf7d |
Rect &ref_line_bounds,
|
|
|
faaf7d |
const Rect &bounds,
|
|
|
faaf7d |
const Vector &min_size,
|
|
|
faaf7d |
const Vector &p1,
|
|
|
faaf7d |
const Vector &bezier_pp0,
|
|
|
faaf7d |
const Vector &bezier_pp1,
|
|
|
faaf7d |
int level )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
assert(level > 0);
|
|
|
faaf7d |
|
|
|
faaf7d |
const Vector &p0 = current();
|
|
|
faaf7d |
if ( fabs(p1.x - p0.x) > min_size.x
|
|
|
faaf7d |
|| fabs(p1.y - p0.y) > min_size.y )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
Rect b = cubic_bounds(p0, p1, bezier_pp0, bezier_pp1);
|
|
|
faaf7d |
if (bounds.intersects(b)) {
|
|
|
faaf7d |
Vector pp = (bezier_pp0 + bezier_pp1)*0.5;
|
|
|
faaf7d |
Vector pp00 = (p0 + bezier_pp0)*0.5;
|
|
|
faaf7d |
Vector pp11 = (p1 + bezier_pp1)*0.5;
|
|
|
faaf7d |
Vector pp01 = (pp00 + pp)*0.5;
|
|
|
faaf7d |
Vector pp10 = (pp11 + pp)*0.5;
|
|
|
faaf7d |
Vector p = (pp01 + pp10)*0.5;
|
|
|
faaf7d |
|
|
|
faaf7d |
cubic_split(ref_line_bounds, bounds, min_size, p, pp00, pp01, level - 1);
|
|
|
faaf7d |
cubic_split(ref_line_bounds, bounds, min_size, p1, pp10, pp11, level - 1);
|
|
|
faaf7d |
return;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
line_split(ref_line_bounds, bounds, min_size, p1);
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::split(Contour &c, const Rect &bounds, const Vector &min_size) const {
|
|
|
f29469 |
c.clear();
|
|
|
f29469 |
|
|
|
faaf7d |
Rect line_bounds;
|
|
|
faaf7d |
line_bounds.p0 = c.current();
|
|
|
faaf7d |
line_bounds.p1 = c.current();
|
|
|
faaf7d |
|
|
|
faaf7d |
for(ChunkList::const_iterator i = chunks.begin(); i != chunks.end(); ++i) {
|
|
|
faaf7d |
switch(i->type) {
|
|
|
faaf7d |
case MOVE:
|
|
|
faaf7d |
c.move_to(i->p1);
|
|
|
faaf7d |
line_bounds.p0 = c.current();
|
|
|
faaf7d |
line_bounds.p1 = c.current();
|
|
|
faaf7d |
break;
|
|
|
faaf7d |
case LINE:
|
|
|
faaf7d |
case CLOSE:
|
|
|
5890eb |
c.line_split(line_bounds, bounds, min_size, i->p1);
|
|
|
faaf7d |
break;
|
|
|
faaf7d |
case CONIC:
|
|
|
faaf7d |
{
|
|
|
faaf7d |
const Vector &p0 = c.current();
|
|
|
faaf7d |
Vector center;
|
|
|
93cbac |
Real radius = 0.0;
|
|
|
93cbac |
Real radians0 = 0.0;
|
|
|
93cbac |
Real radians1 = 0.0;
|
|
|
faaf7d |
if (conic_convert(p0, i->p1, i->t0, center, radius, radians0, radians1))
|
|
|
faaf7d |
c.conic_split(line_bounds, bounds, min_size, i->p1, center, radius, radians0, radians1);
|
|
|
faaf7d |
else
|
|
|
faaf7d |
c.line_split(line_bounds, bounds, min_size, i->p1);
|
|
|
faaf7d |
}
|
|
|
faaf7d |
break;
|
|
|
faaf7d |
case CUBIC:
|
|
|
faaf7d |
{
|
|
|
faaf7d |
const Vector &p0 = c.current();
|
|
|
faaf7d |
Vector pp0, pp1;
|
|
|
faaf7d |
cubic_convert(p0, i->p1, i->t0, i->t1, pp0, pp1);
|
|
|
faaf7d |
c.cubic_split(line_bounds, bounds, min_size, i->p1, pp0, pp1);
|
|
|
faaf7d |
}
|
|
|
faaf7d |
break;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
bool Contour::conic_convert(
|
|
|
faaf7d |
const Vector &p0,
|
|
|
faaf7d |
const Vector &p1,
|
|
|
faaf7d |
const Vector &t,
|
|
|
faaf7d |
Vector &out_center,
|
|
|
93cbac |
Real &out_radius,
|
|
|
93cbac |
Real &out_radians0,
|
|
|
93cbac |
Real &out_radians1 )
|
|
|
faaf7d |
{
|
|
|
93cbac |
Real tl = sqrt(t.x*t.x + t.y*t.y);
|
|
|
faaf7d |
if (fabs(tl) < 1e-6) {
|
|
|
faaf7d |
out_center = Vector();
|
|
|
faaf7d |
out_radius = 0.0;
|
|
|
faaf7d |
out_radians0 = 0.0;
|
|
|
faaf7d |
out_radians1 = 0.0;
|
|
|
faaf7d |
return false;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
Vector d = p1 - p0;
|
|
|
faaf7d |
Vector n(-t.y/tl, t.x/tl);
|
|
|
faaf7d |
|
|
|
93cbac |
Real r = 0.5*(d.x*d.x + d.y*d.y)/(d.x*n.x + d.y*n.y);
|
|
|
faaf7d |
out_center = p0 + n*r;
|
|
|
faaf7d |
out_radius = fabs(r);
|
|
|
faaf7d |
out_radians0 = atan2(p0.y - out_center.y, p0.x - out_center.x);
|
|
|
faaf7d |
out_radians1 = atan2(p1.y - out_center.y, p1.x - out_center.x);
|
|
|
faaf7d |
bool ccw = r > 0.0;
|
|
|
faaf7d |
|
|
|
faaf7d |
out_radians0 = wrap_angle(out_radians0, 2.0*M_PI);
|
|
|
faaf7d |
out_radians1 = wrap_angle(out_radians1, 2.0*M_PI);
|
|
|
faaf7d |
if (ccw) { if (out_radians1 < out_radians0) out_radians1 += 2.0*M_PI; }
|
|
|
faaf7d |
else { if (out_radians1 > out_radians0) out_radians1 -= 2.0*M_PI; }
|
|
|
faaf7d |
|
|
|
faaf7d |
return true;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
Rect Contour::conic_bounds(
|
|
|
faaf7d |
const Vector &p0,
|
|
|
faaf7d |
const Vector &p1,
|
|
|
faaf7d |
const Vector ¢er,
|
|
|
93cbac |
Real radius,
|
|
|
93cbac |
Real radians0,
|
|
|
93cbac |
Real radians1 )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
radius = fabs(radius);
|
|
|
faaf7d |
|
|
|
faaf7d |
Rect r;
|
|
|
faaf7d |
r.p0 = p0;
|
|
|
faaf7d |
r.p1 = p1;
|
|
|
faaf7d |
|
|
|
faaf7d |
if (angle_between(radians0, radians1, 0.0*M_PI, 2.0*M_PI))
|
|
|
faaf7d |
r = r.expand(Vector(center.x + radius, center.y));
|
|
|
faaf7d |
if (angle_between(radians0, radians1, 0.5*M_PI, 2.0*M_PI))
|
|
|
faaf7d |
r = r.expand(Vector(center.x, center.y + radius));
|
|
|
faaf7d |
if (angle_between(radians0, radians1, 1.0*M_PI, 2.0*M_PI))
|
|
|
faaf7d |
r = r.expand(Vector(center.x - radius, center.y));
|
|
|
faaf7d |
if (angle_between(radians0, radians1, 1.5*M_PI, 2.0*M_PI))
|
|
|
faaf7d |
r = r.expand(Vector(center.x, center.y - radius));
|
|
|
faaf7d |
|
|
|
faaf7d |
return r;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
void Contour::cubic_convert(
|
|
|
faaf7d |
const Vector &p0,
|
|
|
faaf7d |
const Vector &p1,
|
|
|
faaf7d |
const Vector &t0,
|
|
|
faaf7d |
const Vector &t1,
|
|
|
faaf7d |
Vector &out_bezier_pp0,
|
|
|
faaf7d |
Vector &out_bezier_pp1 )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
out_bezier_pp0 = t0/3.0 + p0;
|
|
|
faaf7d |
out_bezier_pp1 = p1 - t1/3.0;
|
|
|
faaf7d |
}
|
|
|
faaf7d |
|
|
|
faaf7d |
Rect Contour::cubic_bounds(
|
|
|
faaf7d |
const Vector &p0,
|
|
|
faaf7d |
const Vector &p1,
|
|
|
faaf7d |
const Vector &bezier_pp0,
|
|
|
faaf7d |
const Vector &bezier_pp1 )
|
|
|
faaf7d |
{
|
|
|
faaf7d |
Rect r;
|
|
|
faaf7d |
r.p0.x = min(min(p0.x, bezier_pp0.x), min(p1.x, bezier_pp1.x));
|
|
|
faaf7d |
r.p0.y = min(min(p0.y, bezier_pp0.y), min(p1.y, bezier_pp1.y));
|
|
|
faaf7d |
r.p1.x = max(max(p0.x, bezier_pp0.x), max(p1.x, bezier_pp1.x));
|
|
|
faaf7d |
r.p1.y = max(max(p0.y, bezier_pp0.y), max(p1.y, bezier_pp1.y));
|
|
|
faaf7d |
return r;
|
|
|
faaf7d |
}
|
|
|
93cbac |
|
|
|
93cbac |
void Contour::transform(const Rect &from, const Rect &to) {
|
|
|
93cbac |
Vector s( (to.p1.x - to.p0.x)/(from.p1.x - from.p0.x),
|
|
|
93cbac |
(to.p1.y - to.p0.y)/(from.p1.y - from.p0.y) );
|
|
|
93cbac |
Vector o( to.p0.x - from.p0.x*s.x,
|
|
|
93cbac |
to.p0.y - from.p0.y*s.y );
|
|
|
93cbac |
for(Contour::ChunkList::iterator i = chunks.begin(); i != chunks.end(); ++i) {
|
|
|
93cbac |
i->p1 = i->p1*s + o;
|
|
|
93cbac |
i->t0 = i->t0*s;
|
|
|
93cbac |
i->t1 = i->t1*s;
|
|
|
93cbac |
}
|
|
|
93cbac |
}
|
|
|
93cbac |
|
|
|
f29469 |
void Contour::downgrade(Contour &c, const Vector &min_size) const {
|
|
|
f29469 |
c.clear();
|
|
|
f29469 |
Rect r;
|
|
|
f29469 |
for(Contour::ChunkList::const_iterator i = chunks.begin(); i != chunks.end(); ++i) {
|
|
|
f29469 |
switch(i->type) {
|
|
|
f29469 |
case Contour::CLOSE:
|
|
|
f29469 |
c.close();
|
|
|
f29469 |
r.p0 = r.p1 = c.current();
|
|
|
f29469 |
break;
|
|
|
f29469 |
case Contour::MOVE:
|
|
|
f29469 |
c.move_to(i->p1);
|
|
|
f29469 |
r.p0 = r.p1 = c.current();
|
|
|
f29469 |
break;
|
|
|
f29469 |
case Contour::CONIC:
|
|
|
f29469 |
case Contour::CUBIC:
|
|
|
f29469 |
case Contour::LINE:
|
|
|
f29469 |
r = r.expand(i->p1);
|
|
|
f29469 |
if ( fabs(r.p1.x - r.p0.x) > min_size.x
|
|
|
d9c2d9 |
|| fabs(r.p1.y - r.p0.y) > min_size.y )
|
|
|
f29469 |
{
|
|
|
f29469 |
c.line_to(i->p1);
|
|
|
f29469 |
r.p0 = r.p1 = c.current();
|
|
|
f29469 |
}
|
|
|
f29469 |
break;
|
|
|
f29469 |
}
|
|
|
f29469 |
}
|
|
|
f29469 |
}
|
|
|
f29469 |
|
|
|
93cbac |
void Contour::to_polyspan(Polyspan &polyspan) const {
|
|
|
93cbac |
polyspan.move_to(0.0, 0.0);
|
|
|
93cbac |
Vector p0;
|
|
|
93cbac |
for(Contour::ChunkList::const_iterator i = chunks.begin(); i != chunks.end(); ++i) {
|
|
|
93cbac |
switch(i->type) {
|
|
|
93cbac |
case Contour::CLOSE:
|
|
|
93cbac |
polyspan.close();
|
|
|
93cbac |
break;
|
|
|
93cbac |
case Contour::MOVE:
|
|
|
93cbac |
polyspan.move_to(i->p1.x, i->p1.y);
|
|
|
93cbac |
break;
|
|
|
93cbac |
case Contour::LINE:
|
|
|
93cbac |
polyspan.line_to(i->p1.x, i->p1.y);
|
|
|
93cbac |
break;
|
|
|
93cbac |
case Contour::CONIC: {
|
|
|
93cbac |
Vector center;
|
|
|
93cbac |
Real radius = 0.0;
|
|
|
93cbac |
Real radians0 = 0.0;
|
|
|
93cbac |
Real radians1 = 0.0;
|
|
|
93cbac |
if (conic_convert(p0, i->p1, i->t0, center, radius, radians0, radians1)) {
|
|
|
93cbac |
// TODO: fix bugs
|
|
|
93cbac |
Vector pp0( center.x + 2.0*radius*cos(0.5*(radians0 + radians1)),
|
|
|
93cbac |
center.y + 2.0*radius*sin(0.5*(radians0 + radians1)) );
|
|
|
93cbac |
polyspan.conic_to(pp0.x, pp0.y, i->p1.x, i->p1.y);
|
|
|
93cbac |
} else {
|
|
|
93cbac |
polyspan.line_to(i->p1.x, i->p1.y);
|
|
|
93cbac |
}
|
|
|
93cbac |
}
|
|
|
93cbac |
break;
|
|
|
93cbac |
case Contour::CUBIC: {
|
|
|
93cbac |
Vector pp0, pp1;
|
|
|
93cbac |
cubic_convert(p0, i->p1, i->t0, i->t1, pp0, pp1);
|
|
|
93cbac |
polyspan.cubic_to(pp0.x, pp0.y, pp1.x, pp1.y, i->p1.x, i->p1.y);
|
|
|
93cbac |
}
|
|
|
93cbac |
break;
|
|
|
93cbac |
default:
|
|
|
93cbac |
break;
|
|
|
93cbac |
}
|
|
|
93cbac |
p0 = i->p1;
|
|
|
93cbac |
}
|
|
|
93cbac |
}
|