Remove spikes from glyph shape
This commit is contained in:
parent
8f09c3ac82
commit
badbe9ddba
@ -67,24 +67,189 @@ void visualize_heal(const std::string& svg_filepath, const ExPolygons &expolygon
|
|||||||
const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)});
|
const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)});
|
||||||
const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)});
|
const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)});
|
||||||
|
|
||||||
// Reduce number of Points in polygons with respect to flatness
|
struct SpikeDesc
|
||||||
// Flatness is area that could be modified
|
{
|
||||||
void simplify(Polygon &polygon, float flatness);
|
// cosinus of max spike angle
|
||||||
void simplify(Polygons &polygons, float flatness);
|
double cos_angle; // speed up to skip acos
|
||||||
|
|
||||||
|
// Half of Wanted bevel size
|
||||||
|
double half_bevel;
|
||||||
|
|
||||||
|
SpikeDesc(double bevel_size) {
|
||||||
|
// create min angle given by spike_length
|
||||||
|
// Use it as minimal height of 1 pixel base spike
|
||||||
|
double length = 6; // has to be smaller than count of heal iteration
|
||||||
|
double angle = 2. * atan2(length, .5); // [rad]
|
||||||
|
cos_angle = std::fabs(cos(angle));
|
||||||
|
|
||||||
|
// When remove spike this angle is set.
|
||||||
|
// Value must be grater than min_angle
|
||||||
|
half_bevel = bevel_size / 2;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void priv::simplify(Polygon &polygon, float flatness) {
|
// Remove long sharp corners aka spikes
|
||||||
polygon.douglas_peucker(flatness);
|
// by adding points to bevel tip of spikes - Not printable parts
|
||||||
|
// Try to not modify long sides of spike and add points on it's side
|
||||||
|
void remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc);
|
||||||
|
void remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc);
|
||||||
|
void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc);
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <Geometry.hpp>
|
||||||
|
void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
|
||||||
|
{
|
||||||
|
enum class Type {
|
||||||
|
add, // Move with point B on A-side and add new point on C-side
|
||||||
|
move, // Only move with point B
|
||||||
|
erase // left only points A and C without move
|
||||||
|
};
|
||||||
|
struct SpikeHeal
|
||||||
|
{
|
||||||
|
Type type;
|
||||||
|
size_t index;
|
||||||
|
Point b;
|
||||||
|
Point add;
|
||||||
|
};
|
||||||
|
using SpikeHeals = std::vector<SpikeHeal>;
|
||||||
|
SpikeHeals heals;
|
||||||
|
|
||||||
|
size_t count = polygon.size();
|
||||||
|
if (count < 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Point *ptr_a = &polygon[count - 2];
|
||||||
|
const Point *ptr_b = &polygon[count - 1];
|
||||||
|
for (const Point &c : polygon) {
|
||||||
|
const Point &a = *ptr_a;
|
||||||
|
const Point &b = *ptr_b;
|
||||||
|
ScopeGuard sg([&ptr_a, &ptr_b, &c]() {
|
||||||
|
// prepare for next loop
|
||||||
|
ptr_a = ptr_b;
|
||||||
|
ptr_b = &c;
|
||||||
|
});
|
||||||
|
|
||||||
|
// calc sides
|
||||||
|
Point ba = a - b;
|
||||||
|
Point bc = c - b;
|
||||||
|
|
||||||
|
Vec2d ba_f = ba.cast<double>();
|
||||||
|
Vec2d bc_f = bc.cast<double>();
|
||||||
|
double dot_product = ba_f.dot(bc_f);
|
||||||
|
|
||||||
|
// sqrt together after multiplication save one sqrt
|
||||||
|
double ba_size_sq = ba_f.squaredNorm();
|
||||||
|
double bc_size_sq = bc_f.squaredNorm();
|
||||||
|
double norm = sqrt(ba_size_sq * bc_size_sq);
|
||||||
|
double cos_angle = dot_product / norm;
|
||||||
|
|
||||||
|
// small angle are around 1 --> cos(0) = 1
|
||||||
|
if (cos_angle < spike_desc.cos_angle)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SpikeHeal heal;
|
||||||
|
heal.index = &b - &polygon.points.front();
|
||||||
|
|
||||||
|
// has to be in range <-1, 1>
|
||||||
|
// Due to preccission of floating point number could be sligtly out of range
|
||||||
|
if (cos_angle > 1.)
|
||||||
|
cos_angle = 1.;
|
||||||
|
if (cos_angle < -1.)
|
||||||
|
cos_angle = -1.;
|
||||||
|
|
||||||
|
// Current Spike angle
|
||||||
|
double angle = acos(cos_angle);
|
||||||
|
double wanted_size = spike_desc.half_bevel / cos(angle / 2.);
|
||||||
|
double wanted_size_sq = wanted_size * wanted_size;
|
||||||
|
|
||||||
|
bool is_ba_short = ba_size_sq < wanted_size_sq;
|
||||||
|
bool is_bc_short = bc_size_sq < wanted_size_sq;
|
||||||
|
auto a_side = [&b, &ba_f, &ba_size_sq, &wanted_size]() {
|
||||||
|
Vec2d ab_norm = ba_f / sqrt(ba_size_sq);
|
||||||
|
return b + (wanted_size * ab_norm).cast<coord_t>();
|
||||||
|
};
|
||||||
|
auto c_side = [&b, &bc_f, &bc_size_sq, &wanted_size]() {
|
||||||
|
Vec2d bc_norm = bc_f / sqrt(bc_size_sq);
|
||||||
|
return b + (wanted_size * bc_norm).cast<coord_t>();
|
||||||
|
};
|
||||||
|
if (is_ba_short && is_bc_short) {
|
||||||
|
// remove short spike
|
||||||
|
heal.type = Type::erase;
|
||||||
|
} else if (is_ba_short){
|
||||||
|
// move point B on C-side
|
||||||
|
heal.type = Type::move;
|
||||||
|
heal.b = c_side();
|
||||||
|
} else if (is_bc_short) {
|
||||||
|
// move point B on A-side
|
||||||
|
heal.type = Type::move;
|
||||||
|
heal.b = a_side();
|
||||||
|
} else {
|
||||||
|
// move point B on A-side and add point on C-side
|
||||||
|
heal.type = Type::add;
|
||||||
|
heal.b = a_side();
|
||||||
|
heal.add = c_side();
|
||||||
|
}
|
||||||
|
heals.push_back(heal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heals.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// sort index from high to low
|
||||||
|
if (heals.front().index == (count - 1))
|
||||||
|
std::rotate(heals.begin(), heals.begin()+1, heals.end());
|
||||||
|
std::reverse(heals.begin(), heals.end());
|
||||||
|
|
||||||
|
int extend = 0;
|
||||||
|
int curr_extend = 0;
|
||||||
|
for (const SpikeHeal &h : heals)
|
||||||
|
switch (h.type) {
|
||||||
|
case Type::add:
|
||||||
|
++curr_extend;
|
||||||
|
if (extend < curr_extend)
|
||||||
|
extend = curr_extend;
|
||||||
|
break;
|
||||||
|
case Type::erase:
|
||||||
|
--curr_extend;
|
||||||
|
}
|
||||||
|
|
||||||
|
Points &pts = polygon.points;
|
||||||
|
if (extend > 0)
|
||||||
|
pts.reserve(pts.size() + extend);
|
||||||
|
|
||||||
|
for (const SpikeHeal &h : heals) {
|
||||||
|
switch (h.type) {
|
||||||
|
case Type::add:
|
||||||
|
pts[h.index] = h.b;
|
||||||
|
pts.insert(pts.begin() + h.index+1, h.add);
|
||||||
|
break;
|
||||||
|
case Type::erase:
|
||||||
|
pts.erase(pts.begin() + h.index);
|
||||||
|
break;
|
||||||
|
case Type::move:
|
||||||
|
pts[h.index] = h.b;
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void priv::simplify(Polygons &polygons, float flatness) {
|
void priv::remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc)
|
||||||
|
{
|
||||||
for (Polygon &polygon : polygons)
|
for (Polygon &polygon : polygons)
|
||||||
simplify(polygon, flatness);
|
remove_spikes(polygon, spike_desc);
|
||||||
// simplification could ceate empty polygon
|
|
||||||
remove_bad(polygons);
|
remove_bad(polygons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void priv::remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc)
|
||||||
|
{
|
||||||
|
for (ExPolygon &expolygon : expolygons) {
|
||||||
|
remove_spikes(expolygon.contour, spike_desc);
|
||||||
|
remove_spikes(expolygon.holes, spike_desc);
|
||||||
|
}
|
||||||
|
remove_bad(expolygons);
|
||||||
|
}
|
||||||
|
|
||||||
bool priv::is_valid(const FontFile &font, unsigned int index) {
|
bool priv::is_valid(const FontFile &font, unsigned int index) {
|
||||||
if (font.data == nullptr) return false;
|
if (font.data == nullptr) return false;
|
||||||
if (font.data->empty()) return false;
|
if (font.data->empty()) return false;
|
||||||
@ -380,7 +545,8 @@ bool priv::remove_self_intersections(ExPolygons &shape, unsigned max_iteration)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygons Emboss::heal_shape(const Polygons &shape) {
|
ExPolygons Emboss::heal_shape(const Polygons &shape, double precision)
|
||||||
|
{
|
||||||
// When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work
|
// When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work
|
||||||
// fix of self intersections
|
// fix of self intersections
|
||||||
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm
|
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm
|
||||||
@ -388,7 +554,15 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) {
|
|||||||
const double clean_distance = 1.415; // little grater than sqrt(2)
|
const double clean_distance = 1.415; // little grater than sqrt(2)
|
||||||
ClipperLib::CleanPolygons(paths, clean_distance);
|
ClipperLib::CleanPolygons(paths, clean_distance);
|
||||||
Polygons polygons = to_polygons(paths);
|
Polygons polygons = to_polygons(paths);
|
||||||
|
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.size() < 3; }), polygons.end());
|
||||||
|
|
||||||
|
// use douglas peucker to reduce amount of used points
|
||||||
|
for (Polygon &polygon : polygons)
|
||||||
|
polygon.douglas_peucker(precision);
|
||||||
|
|
||||||
|
priv::SpikeDesc spike(precision);
|
||||||
|
priv::remove_spikes(polygons, spike);
|
||||||
|
|
||||||
// Do not remove all duplicits but do it better way
|
// Do not remove all duplicits but do it better way
|
||||||
// Overlap all duplicit points by rectangle 3x3
|
// Overlap all duplicit points by rectangle 3x3
|
||||||
Points duplicits = collect_duplicates(to_points(polygons));
|
Points duplicits = collect_duplicates(to_points(polygons));
|
||||||
@ -405,6 +579,12 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) {
|
|||||||
// https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01
|
// https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01
|
||||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
|
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
|
||||||
ExPolygons res = Slic3r::union_ex(polygons, ClipperLib::pftNonZero);
|
ExPolygons res = Slic3r::union_ex(polygons, ClipperLib::pftNonZero);
|
||||||
|
|
||||||
|
priv::remove_spikes(polygons, spike);
|
||||||
|
|
||||||
|
double min_area = precision * precision;
|
||||||
|
priv::remove_small_islands(res, min_area);
|
||||||
|
|
||||||
heal_shape(res);
|
heal_shape(res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -438,6 +618,8 @@ bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
|
|||||||
{
|
{
|
||||||
if (shape.empty()) return true;
|
if (shape.empty()) return true;
|
||||||
|
|
||||||
|
//priv::visualize_heal("C:/data/temp/heal_prev.svg", shape);
|
||||||
|
|
||||||
// create loop permanent memory
|
// create loop permanent memory
|
||||||
Polygons holes;
|
Polygons holes;
|
||||||
Points intersections;
|
Points intersections;
|
||||||
@ -488,7 +670,7 @@ bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
|
|||||||
intersections.clear();
|
intersections.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// priv::visualize_heal("C:/data/temp/heal.svg", shape);
|
//priv::visualize_heal("C:/data/temp/heal.svg", shape);
|
||||||
assert(false);
|
assert(false);
|
||||||
shape = {priv::create_bounding_rect(shape)};
|
shape = {priv::create_bounding_rect(shape)};
|
||||||
return false;
|
return false;
|
||||||
@ -622,18 +804,10 @@ std::optional<Glyph> priv::get_glyph(const stbtt_fontinfo &font_info, int unicod
|
|||||||
|
|
||||||
// change outer cw to ccw and inner ccw to cw order
|
// change outer cw to ccw and inner ccw to cw order
|
||||||
std::reverse(pts.begin(), pts.end());
|
std::reverse(pts.begin(), pts.end());
|
||||||
|
|
||||||
{ // use douglas peucker to reduce amount of used points
|
|
||||||
pts.push_back(pts.front());
|
|
||||||
pts = MultiPoint::_douglas_peucker(pts, flatness);
|
|
||||||
pts.pop_back();
|
|
||||||
if (pts.size() < 3)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
glyph_polygons.emplace_back(pts);
|
glyph_polygons.emplace_back(pts);
|
||||||
}
|
}
|
||||||
if (!glyph_polygons.empty())
|
if (!glyph_polygons.empty())
|
||||||
glyph.shape = Emboss::heal_shape(glyph_polygons);
|
glyph.shape = Emboss::heal_shape(glyph_polygons, flatness / SHAPE_SCALE);
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,11 +154,13 @@ namespace Emboss
|
|||||||
ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop, std::function<bool()> was_canceled = nullptr);
|
ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop, std::function<bool()> was_canceled = nullptr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fix intersections and self intersections in polygons glyph shape
|
/// Fix duplicit points and self intersections in polygons.
|
||||||
|
/// Also try to reduce amount of points and remove useless polygon parts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shape">Input shape to heal</param>
|
/// <param name="shape">Input shape to heal</param>
|
||||||
|
/// <param name="precision">Define wanted precision of shape after heal</param>
|
||||||
/// <returns>Healed shapes</returns>
|
/// <returns>Healed shapes</returns>
|
||||||
ExPolygons heal_shape(const Polygons &shape);
|
ExPolygons heal_shape(const Polygons &shape, double precision);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// NOTE: call Slic3r::union_ex before this call
|
/// NOTE: call Slic3r::union_ex before this call
|
||||||
|
@ -203,7 +203,7 @@ ExPolygons heal_and_check(const Polygons &polygons)
|
|||||||
Points polygons_points = to_points(polygons);
|
Points polygons_points = to_points(polygons);
|
||||||
Points duplicits_prev = collect_duplicates(polygons_points);
|
Points duplicits_prev = collect_duplicates(polygons_points);
|
||||||
|
|
||||||
ExPolygons shape = Emboss::heal_shape(polygons);
|
ExPolygons shape = Emboss::heal_shape(polygons, 10);
|
||||||
|
|
||||||
// Is default shape for unhealabled shape?
|
// Is default shape for unhealabled shape?
|
||||||
bool is_default_shape =
|
bool is_default_shape =
|
||||||
|
Loading…
Reference in New Issue
Block a user