#pragma once inline void testStrokeImprovement() { auto require=[](bool ok,const char *message){if(!ok) throw std::runtime_error(message);}; for(double angle:{0.0,.37,1.57079632679,2.8}) for(bool reverse:{false,true}) { std::vector p; for(int i=0;i<=20;++i) { double x=i*10,y=i==0 || i==20?0:(i%3)-1; p.push_back({LONG(std::lround(300+x*std::cos(angle)-y*std::sin(angle))), LONG(std::lround(300+x*std::sin(angle)+y*std::cos(angle)))}); } if(reverse) std::reverse(p.begin(),p.end()); bool line=false; ks::ScreenshotShape shape{}; auto result=ks::improveScreenshotStroke(p,&shape,&line); require(line && shape==ks::ScreenshotShape::None && result.size()==2 && result.front().x==p.front().x && result.front().y==p.front().y && result.back().x==p.back().x && result.back().y==p.back().y, "recognize noisy lines at arbitrary angles and preserve directed endpoints"); } for(auto p:std::vector>{ {{0,0},{50,1},{100,0}}, {{0,0},{0,0},{50,0},{100,0}}}) { bool line=false; require(ks::improveScreenshotStroke(p,nullptr,&line).size()==2 && line, "recognize sparsely sampled lines and repeated mouse positions"); } for(auto p:std::vector>{ {{0,0},{8,1},{16,0}}, {{0,0},{50,0},{40,0},{80,0},{100,0}}, {{0,0},{25,8},{50,12},{75,8},{100,0}}, {{0,0},{50,0},{51,10},{52,0},{100,0}}, {{0,0},{0,0},{0,0}}}) { bool line=true; ks::improveScreenshotStroke(p,nullptr,&line); require(!line,"do not straighten short strokes, retracing, curves, spikes or stationary input"); } auto path=[](std::vector corners) { std::vector p; for(size_t i=1;i>{ {{435,172},{455,135},{481,76},{514,78},{567,78},{605,76},{631,103},{652,135},{681,171},{609,172},{539,174},{435,172}}, {{531,311},{553,280},{582,235},{616,236},{672,233},{686,264},{693,287},{710,318},{640,314},{531,311}}, {{70,40},{160,40},{220,150},{20,150},{70,40}}}) for(double angle:{0.0,.6,2.2}) for(bool reverse:{false,true}) { auto p=path(corners); for(auto &q:p) { double x=q.x,y=q.y; q={LONG(std::lround(x*std::cos(angle)-y*std::sin(angle)+700)), LONG(std::lround(x*std::sin(angle)+y*std::cos(angle)+700))}; } if(reverse) std::reverse(p.begin(),p.end()); ks::ScreenshotShape kind{}; auto result=ks::improveScreenshotStroke(p,&kind); require(kind==ks::ScreenshotShape::Trapezoid && result.size()==5,"recognize hand-drawn and tilted trapezoids"); double ax=result[1].x-result[0].x,ay=result[1].y-result[0].y; double bx=result[3].x-result[2].x,by=result[3].y-result[2].y; require(std::abs(ax*by-ay*bx)/(std::hypot(ax,ay)*std::hypot(bx,by))<.025,"recognized trapezoid bases are parallel"); } // Approximate the reported skewed square, including its small closing gap. auto unevenBox=path({{662,297},{663,213},{659,199},{659,181},{691,181},{718,189}, {753,195},{786,195},{775,217},{771,249},{771,289},{766,313}, {745,311},{716,304},{689,300},{671,300}}); for(double factor:{.5,1.0,2.5}) for(bool reverse:{false,true}) { auto p=unevenBox; for(auto &q:p) {q.x=LONG(std::lround((q.x-650)*factor+50));q.y=LONG(std::lround((q.y-170)*factor+50));} if(reverse) std::reverse(p.begin(),p.end()); ks::ScreenshotShape kind{}; auto result=ks::improveScreenshotStroke(p,&kind); require(kind==ks::ScreenshotShape::Rectangle && result.size()==5,"uneven square must not become a circle"); } require(triangle.size()==4,"recognize triangle"); for(double ry:{50.0,90.0}) { std::vector p; for(int i=0;i<=120;++i) {double a=i*2*3.141592653589793/120; p.push_back({LONG(std::lround(150+90*std::cos(a))),LONG(std::lround(150+ry*std::sin(a)))});} auto result=ks::improveScreenshotStroke(p); require(result.size()==97 && result.front().x==result.back().x && result.front().y==result.back().y, "recognize ellipse and circle as closed outlines"); } auto open=path({{20,20},{200,20},{200,150},{60,150}}); // Hand-traced approximations of the uneven loops in the reported screenshot. for(auto corners:std::vector>{ {{424,408},{449,407},{474,418},{487,439},{488,461},{475,485},{453,488},{432,479},{412,458},{410,435},{416,418},{431,409}}, {{352,474},{373,482},{389,501},{392,518},{378,529},{351,531},{331,519},{324,504},{326,486},{339,477},{352,474}}, {{246,236},{221,242},{192,256},{166,271},{151,289},{154,303},{172,316},{195,315},{226,305},{246,291},{250,272},{252,252},{246,236}}}) { auto result=ks::improveScreenshotStroke(path(corners)); require(result.size()==97,"recognize uneven hand-drawn loops from screenshot"); } for(double size:{35.0,100.0,300.0}) for(double aspect:{.5,1.0}) for(int direction:{-1,1}) { std::vector p; for(int i=0;i<=120;++i) { double a=direction*(.3+i*5.85/120),r=1+.07*std::sin(3*a)+.035*std::cos(7*a); double x=size*r*std::cos(a),y=size*aspect*r*std::sin(a); p.push_back({LONG(std::lround(500+x*.866-y*.5)),LONG(std::lround(500+x*.5+y*.866))}); } require(ks::improveScreenshotStroke(p).size()==97,"recognize wobbly tilted ovals with closing gaps at different sizes"); } for(double turns:{.72,1.8}) { std::vector p; for(int i=0;i<=120;++i) {double a=i*turns*2*3.141592653589793/120; p.push_back({LONG(std::lround(150+90*std::cos(a))),LONG(std::lround(150+70*std::sin(a)))});} auto result=ks::improveScreenshotStroke(p); require(result.size()!=97,"do not close broad arcs or repeated loops into ovals"); } auto smoothed=ks::improveScreenshotStroke(open); require(smoothed.size()>5 && smoothed.front().x==20 && smoothed.back().x==60,"open outline stays open"); auto scribble=path({{20,20},{200,150},{200,20},{20,150},{20,20}}); require(ks::improveScreenshotStroke(scribble).size()>97,"crossed scribble is not a shape"); require(ks::improveScreenshotStroke({{1,1},{1,1},{1,1},{1,1},{1,1}}).empty(),"stationary stroke unchanged"); std::vector jitter; for(int i=0;i<=100;++i) jitter.push_back({20+i*2,100+(i%2?2:-2)}); auto smooth=ks::improveScreenshotStroke(jitter); double deviation=0; for(auto p:smooth) deviation+=std::abs(p.y-100); require(deviation/smooth.size()<1.5 && smooth.front().y==jitter.front().y && smooth.back().y==jitter.back().y, "smoothing reduces jitter and preserves endpoints"); }