Blame c++/contourgl/contour.cpp

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
}