1 // -*- c-basic-offset: 4 -*-
2
3 /** @file Mask.h
4 *
5 * @brief declaration of classes to work with mask
6 *
7 * @author Thomas Modes
8 *
9 */
10
11 /* This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This software is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public
22 * License along with this software. If not, see
23 * <http://www.gnu.org/licenses/>.
24 *
25 */
26
27 // for debugging
28 #include <iostream>
29 #include <stdio.h>
30
31 #include "Mask.h"
32
33 #include <iostream>
34 #include <vector>
35 #include <panotools/PanoToolsInterface.h>
36 #include <panodata/PTScriptParsing.h>
37
38 namespace HuginBase {
39
isInside(const hugin_utils::FDiff2D p) const40 bool MaskPolygon::isInside(const hugin_utils::FDiff2D p) const
41 {
42 if(m_polygon.size()<3)
43 return false;
44 if(!m_boundingBox.contains(vigra::Point2D(p.x,p.y)))
45 return false;
46 int wind=getWindingNumber(p);
47 if(m_invert)
48 return wind==0;
49 else
50 return wind!=0;
51 };
52
getWindingNumber(const hugin_utils::FDiff2D p) const53 int MaskPolygon::getWindingNumber(const hugin_utils::FDiff2D p) const
54 {
55 // algorithm is modified version of winding number method
56 // described at http://www.softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
57 // Copyright 2001, softSurfer (www.softsurfer.com)
58 // This code may be freely used and modified for any purpose
59 // providing that this copyright notice is included with it.
60 if(m_polygon.size()<3)
61 return 0;
62 int wind=0;
63 hugin_utils::FDiff2D a=m_polygon[m_polygon.size()-1];
64 for(unsigned int i=0;i<m_polygon.size();i++)
65 {
66 hugin_utils::FDiff2D b=m_polygon[i];
67 if(a.y<=p.y)
68 {
69 if(b.y>p.y)
70 if((b.x-a.x)*(p.y-a.y)<(p.x-a.x)*(b.y-a.y))
71 wind++;
72 }
73 else
74 {
75 if(b.y<=p.y)
76 if((b.x-a.x)*(p.y-a.y)>(p.x-a.x)*(b.y-a.y))
77 wind--;
78 };
79 a=b;
80 };
81 return wind;
82 };
83
getTotalWindingNumber() const84 int MaskPolygon::getTotalWindingNumber() const
85 {
86 if(m_polygon.size()<2)
87 return 0;
88 MaskPolygon diffPoly;
89 unsigned int count=m_polygon.size();
90 for(unsigned int i=0;i<count;i++)
91 {
92 diffPoly.addPoint(m_polygon[(i+1)%count]-m_polygon[i]);
93 };
94 return diffPoly.getWindingNumber(hugin_utils::FDiff2D(0,0));
95 };
96
isPositive() const97 bool MaskPolygon::isPositive() const
98 {
99 return (m_maskType==Mask_positive) ||
100 (m_maskType==Mask_Stack_positive);
101 };
102
setMaskPolygon(const VectorPolygon & newMask)103 void MaskPolygon::setMaskPolygon(const VectorPolygon& newMask)
104 {
105 m_polygon=newMask;
106 calcBoundingBox();
107 };
108
addPoint(const hugin_utils::FDiff2D p)109 void MaskPolygon::addPoint(const hugin_utils::FDiff2D p)
110 {
111 m_polygon.push_back(p);
112 calcBoundingBox();
113 };
114
insertPoint(const unsigned int index,const hugin_utils::FDiff2D p)115 void MaskPolygon::insertPoint(const unsigned int index, const hugin_utils::FDiff2D p)
116 {
117 if(index<=m_polygon.size())
118 {
119 m_polygon.insert(m_polygon.begin()+index,p);
120 calcBoundingBox();
121 };
122 };
123
124
removePoint(const unsigned int index)125 void MaskPolygon::removePoint(const unsigned int index)
126 {
127 if(index<m_polygon.size())
128 {
129 m_polygon.erase(m_polygon.begin()+index);
130 calcBoundingBox();
131 };
132 };
133
movePointTo(const unsigned int index,const hugin_utils::FDiff2D p)134 void MaskPolygon::movePointTo(const unsigned int index, const hugin_utils::FDiff2D p)
135 {
136 if(index<m_polygon.size())
137 {
138 m_polygon[index].x=p.x;
139 m_polygon[index].y=p.y;
140 calcBoundingBox();
141 };
142 };
143
movePointBy(const unsigned int index,const hugin_utils::FDiff2D diff)144 void MaskPolygon::movePointBy(const unsigned int index, const hugin_utils::FDiff2D diff)
145 {
146 if(index<m_polygon.size())
147 {
148 m_polygon[index].x+=diff.x;
149 m_polygon[index].y+=diff.y;
150 calcBoundingBox();
151 };
152 };
153
scale(const double factorx,const double factory)154 void MaskPolygon::scale(const double factorx,const double factory)
155 {
156 for(unsigned int i=0;i<m_polygon.size();i++)
157 {
158 m_polygon[i].x*=factorx;
159 m_polygon[i].y*=factory;
160 };
161 calcBoundingBox();
162 };
163
transformPolygon(const PTools::Transform & trans)164 void MaskPolygon::transformPolygon(const PTools::Transform &trans)
165 {
166 double xnew,ynew;
167 VectorPolygon newPoly;
168 for(unsigned int i=0;i<m_polygon.size();i++)
169 {
170 if(trans.transformImgCoord(xnew,ynew,m_polygon[i].x,m_polygon[i].y))
171 {
172 newPoly.push_back(hugin_utils::FDiff2D(xnew,ynew));
173 };
174 };
175 m_polygon=newPoly;
176 calcBoundingBox();
177 };
178
subSample(const double max_distance)179 void MaskPolygon::subSample(const double max_distance)
180 {
181 if(m_polygon.size()<3)
182 return;
183 VectorPolygon oldPoly=m_polygon;
184 unsigned int count=oldPoly.size();
185 m_polygon.clear();
186 for(unsigned int i=0;i<count;i++)
187 {
188 addPoint(oldPoly[i]);
189 hugin_utils::FDiff2D p1=oldPoly[i];
190 hugin_utils::FDiff2D p2=oldPoly[(i+1)%count];
191 double distance=norm(p2-p1);
192 if(distance>max_distance)
193 {
194 //add intermediate points
195 double currentDistance=max_distance;
196 while(currentDistance<distance)
197 {
198 hugin_utils::FDiff2D p_new=p1+(p2-p1)*currentDistance/distance;
199 addPoint(p_new);
200 currentDistance+=max_distance;
201 };
202 };
203 };
204 };
205
calcBoundingBox()206 void MaskPolygon::calcBoundingBox()
207 {
208 if(!m_polygon.empty())
209 {
210 m_boundingBox.setUpperLeft(vigra::Point2D(m_polygon[0].x,m_polygon[0].y));
211 m_boundingBox.setLowerRight(vigra::Point2D(m_polygon[0].x+1,m_polygon[0].y+1));
212 if(m_polygon.size()>1)
213 {
214 for(unsigned int i=1;i<m_polygon.size();i++)
215 {
216 m_boundingBox|=vigra::Point2D(m_polygon[i].x,m_polygon[i].y);
217 };
218 };
219 //adding a small border to get no rounding error because polygon coordinates are float
220 //numbers, but bounding box has integer coordinates
221 m_boundingBox.addBorder(2);
222 };
223 };
224
225 //helper function for clipping
226 enum clipSide
227 {
228 clipLeft=0,
229 clipRight,
230 clipTop,
231 clipBottom
232 };
233
clip_isSide(const hugin_utils::FDiff2D p,const vigra::Rect2D r,const clipSide side)234 bool clip_isSide(const hugin_utils::FDiff2D p, const vigra::Rect2D r, const clipSide side)
235 {
236 switch(side){
237 case clipLeft:
238 return p.x>=r.left();
239 case clipRight:
240 return p.x<=r.right();
241 case clipTop:
242 return p.y>=r.top();
243 case clipBottom:
244 return p.y<=r.bottom();
245 };
246 //this should never happens
247 return false;
248 }
249
clip_getIntersection(const hugin_utils::FDiff2D p,const hugin_utils::FDiff2D q,const vigra::Rect2D r,const clipSide side)250 hugin_utils::FDiff2D clip_getIntersection(const hugin_utils::FDiff2D p, const hugin_utils::FDiff2D q, const vigra::Rect2D r, const clipSide side)
251 {
252 double a;
253 double b;
254 double xnew;
255 double ynew;
256 if(q.x-p.x==0)
257 {
258 a=0;
259 b=p.y;
260 }
261 else
262 {
263 a=(q.y-p.y)/(q.x-p.x);
264 b=p.y-p.x*a;
265 };
266 switch(side){
267 case clipLeft:
268 xnew=r.left();
269 ynew=xnew*a+b;
270 break;
271 case clipRight:
272 xnew=r.right();
273 ynew=xnew*a+b;
274 break;
275 case clipTop:
276 ynew=r.top();
277 if(a!=0)
278 xnew=(ynew-b)/a;
279 else
280 xnew=p.x;
281 break;
282 case clipBottom:
283 ynew=r.bottom();
284 if(a!=0)
285 xnew=(ynew-b)/a;
286 else
287 xnew=p.x;
288 break;
289 };
290 return hugin_utils::FDiff2D(xnew,ynew);
291 };
292
clip_onPlane(const VectorPolygon & polygon,const vigra::Rect2D r,const clipSide side)293 VectorPolygon clip_onPlane(const VectorPolygon& polygon, const vigra::Rect2D r, const clipSide side)
294 {
295 if(polygon.size()<3)
296 {
297 return polygon;
298 };
299 hugin_utils::FDiff2D s=polygon[polygon.size()-1];
300 hugin_utils::FDiff2D p;
301 VectorPolygon newPolygon;
302 for(unsigned int i=0;i<polygon.size();i++)
303 {
304 p=polygon[i];
305 if(clip_isSide(p,r,side))
306 {
307 // point p is "inside"
308 if(!clip_isSide(s,r,side))
309 // and point s is "outside"
310 newPolygon.push_back(clip_getIntersection(p,s,r,side));
311 newPolygon.push_back(p);
312 }
313 else
314 {
315 //point p is "outside"
316 if(clip_isSide(s,r,side))
317 //ans point s is "inside"
318 newPolygon.push_back(clip_getIntersection(s,p,r,side));
319 };
320 s=p;
321 };
322 return newPolygon;
323 };
324
clipPolygon(const vigra::Rect2D rect)325 bool MaskPolygon::clipPolygon(const vigra::Rect2D rect)
326 {
327 //clipping using Sutherland-Hodgman algorithm
328 m_polygon=clip_onPlane(m_polygon, rect, clipLeft);
329 m_polygon=clip_onPlane(m_polygon, rect, clipRight);
330 m_polygon=clip_onPlane(m_polygon, rect, clipTop);
331 m_polygon=clip_onPlane(m_polygon, rect, clipBottom);
332 calcBoundingBox();
333 return (m_polygon.size()>2);
334 };
335
336 //helper function for clipping
337 /** check if point is inside circle
338 @returns true, if point p is inside circle given by center and radius
339 @param p point to test
340 @param center center point of circle test
341 @param radius radius of circle to test
342 */
clip_insideCircle(const hugin_utils::FDiff2D p,const hugin_utils::FDiff2D center,const double radius)343 bool clip_insideCircle(const hugin_utils::FDiff2D p, const hugin_utils::FDiff2D center, const double radius)
344 {
345 return p.squareDistance(center)<=radius*radius;
346 };
347
348 /** returns intersection of line and circle
349 @param p fist point of line segment
350 @param s seconst point of line segment
351 @param center center of circle
352 @param radius radius of circle
353 @returns vector with all intersection of line between p and s and given circle
354 */
clip_getIntersectionCircle(const hugin_utils::FDiff2D p,const hugin_utils::FDiff2D s,const hugin_utils::FDiff2D center,const double radius)355 std::vector<hugin_utils::FDiff2D> clip_getIntersectionCircle(const hugin_utils::FDiff2D p, const hugin_utils::FDiff2D s, const hugin_utils::FDiff2D center, const double radius)
356 {
357 std::vector<hugin_utils::FDiff2D> intersections;
358 hugin_utils::FDiff2D slope=s-p;
359 if(slope.squareLength()<1e-5)
360 {
361 return intersections;
362 };
363 hugin_utils::FDiff2D p2=p-center;
364 double dotproduct=p2.x*slope.x+p2.y*slope.y;
365 double root=sqrt(dotproduct*dotproduct-slope.squareLength()*(p2.squareLength()-radius*radius));
366 double t1=(-dotproduct+root)/slope.squareLength();
367 double t2=(-dotproduct-root)/slope.squareLength();
368 std::set<double> t;
369 if(t1>0 && t1<1)
370 {
371 t.insert(t1);
372 };
373 if(t2>0 && t2<1)
374 {
375 if(fabs(t2-t1)>1e-5)
376 {
377 t.insert(t2);
378 };
379 };
380 if(!t.empty())
381 {
382 for(std::set<double>::const_iterator it=t.begin();it!=t.end();++it)
383 {
384 intersections.push_back(p+slope*(*it));
385 };
386 };
387 return intersections;
388 };
389
390 /** calculates angle between vector a and b in radians */
angle_between(const hugin_utils::FDiff2D a,const hugin_utils::FDiff2D b)391 double angle_between(const hugin_utils::FDiff2D a, const hugin_utils::FDiff2D b)
392 {
393 return asin((a.x*b.y-a.y*b.x)/(sqrt(a.squareLength())*sqrt(b.squareLength())));
394 };
395
396 /** adds an arc with given radius at the end of the polygon, the point is not added to the arc
397 @param poly polygon to which the arc should added
398 @param s point to which the arc should go
399 @param center center of arc
400 @param radius radius of arc
401 @param clockwise true, if arc should go clockwise; else it goes anti-clockwise
402 */
generateArc(VectorPolygon & poly,const hugin_utils::FDiff2D s,const hugin_utils::FDiff2D center,const double radius,const bool clockwise)403 void generateArc(VectorPolygon& poly, const hugin_utils::FDiff2D s, const hugin_utils::FDiff2D center, const double radius, const bool clockwise)
404 {
405 if(poly.empty())
406 {
407 return;
408 };
409 hugin_utils::FDiff2D p=poly[poly.size()-1];
410 double maxDistance=5.0;
411 if(p.squareDistance(s)<maxDistance*maxDistance)
412 {
413 return;
414 };
415 double angle=atan2(p.y-center.y,p.x-center.x);
416 double final_angle=atan2(s.y-center.y,s.x-center.x);
417 //step 1 degree or less, so that max distance between 2 points is smaller than maxDistance
418 double step=std::min<double>(PI/180,atan2(maxDistance,radius));
419 if(!clockwise)
420 {
421 while(final_angle<angle)
422 {
423 final_angle+=2*PI;
424 };
425 angle+=step;
426 while(angle<final_angle)
427 {
428 poly.push_back(hugin_utils::FDiff2D(cos(angle)*radius+center.x,sin(angle)*radius+center.y));
429 angle+=step;
430 };
431 }
432 else
433 {
434 while(final_angle>angle)
435 {
436 final_angle-=2*PI;
437 };
438 angle-=step;
439 while(angle>final_angle)
440 {
441 poly.push_back(hugin_utils::FDiff2D(cos(angle)*radius+center.x,sin(angle)*radius+center.y));
442 angle-=step;
443 };
444 };
445 };
446
clipPolygon(const hugin_utils::FDiff2D center,const double radius)447 bool MaskPolygon::clipPolygon(const hugin_utils::FDiff2D center,const double radius)
448 {
449 if(radius<=0 || m_polygon.size()<3)
450 {
451 return false;
452 };
453 hugin_utils::FDiff2D s=m_polygon[m_polygon.size()-1];
454 bool s_inside=clip_insideCircle(s,center,radius);
455 hugin_utils::FDiff2D p;
456 VectorPolygon newPolygon;
457 bool needsFinalArc=false;
458 double angleCovered=0;
459 double angleCoveredOffset=0;
460 for(unsigned int i=0;i<m_polygon.size();i++)
461 {
462 p=m_polygon[i];
463 bool p_inside=clip_insideCircle(p,center,radius);
464 if(p_inside)
465 {
466 if(s_inside)
467 {
468 //both points inside
469 newPolygon.push_back(p);
470 }
471 else
472 {
473 //line crosses circles from outside
474 std::vector<hugin_utils::FDiff2D> points=clip_getIntersectionCircle(p,s,center,radius);
475 DEBUG_ASSERT(points.size()==1);
476 angleCovered+=angle_between(s-center,points[0]-center);
477 if(newPolygon.empty())
478 {
479 needsFinalArc=true;
480 angleCoveredOffset=angleCovered;
481 }
482 else
483 {
484 generateArc(newPolygon,points[0],center,radius,angleCovered<0);
485 };
486 newPolygon.push_back(points[0]);
487 newPolygon.push_back(p);
488 };
489 }
490 else
491 {
492 if(!s_inside)
493 {
494 //both points outside of circle
495 std::vector<hugin_utils::FDiff2D> points=clip_getIntersectionCircle(s,p,center,radius);
496 //intersection can only be zero points or 2 points
497 if(points.size()>1)
498 {
499 angleCovered+=angle_between(s-center,points[0]-center);
500 if(newPolygon.empty())
501 {
502 needsFinalArc=true;
503 angleCoveredOffset=angleCovered;
504 }
505 else
506 {
507 generateArc(newPolygon,points[0],center,radius,angleCovered<0);
508 };
509 newPolygon.push_back(points[0]);
510 newPolygon.push_back(points[1]);
511 angleCovered=angle_between(points[1]-center,p-center);
512 }
513 else
514 {
515 angleCovered+=angle_between(s-center,p-center);
516 };
517 }
518 else
519 {
520 //line segment intersects circle from inside
521 std::vector<hugin_utils::FDiff2D> points=clip_getIntersectionCircle(s,p,center,radius);
522 angleCovered=0;
523 DEBUG_ASSERT(points.size()==1);
524 newPolygon.push_back(points[0]);
525 };
526 };
527 s=p;
528 s_inside=p_inside;
529 };
530 if(needsFinalArc && newPolygon.size()>1)
531 {
532 generateArc(newPolygon,newPolygon[0],center,radius,(angleCovered+angleCoveredOffset)<0);
533 };
534 m_polygon=newPolygon;
535 calcBoundingBox();
536 return (m_polygon.size()>2);
537 };
538
rotate90(bool clockwise,unsigned int maskWidth,unsigned int maskHeight)539 void MaskPolygon::rotate90(bool clockwise,unsigned int maskWidth,unsigned int maskHeight)
540 {
541 for(unsigned int i=0;i<m_polygon.size();i++)
542 {
543 if(clockwise)
544 {
545 hugin_utils::FDiff2D p=m_polygon[i];
546 m_polygon[i].x=maskHeight-p.y;
547 m_polygon[i].y=p.x;
548 }
549 else
550 {
551 hugin_utils::FDiff2D p=m_polygon[i];
552 m_polygon[i].x=p.y;
553 m_polygon[i].y=maskWidth-p.x;
554 };
555 };
556 calcBoundingBox();
557 };
558
FindPointNearPos(const hugin_utils::FDiff2D p,const double tol) const559 unsigned int MaskPolygon::FindPointNearPos(const hugin_utils::FDiff2D p, const double tol) const
560 {
561 if(m_polygon.empty())
562 return UINT_MAX;
563 hugin_utils::FDiff2D p1;
564 unsigned int j=m_polygon.size()-1;
565 hugin_utils::FDiff2D p2=m_polygon[j];
566 for(unsigned int i=0;i<m_polygon.size();i++)
567 {
568 p1=m_polygon[i];
569 // find intersection of perpendicular through point p and line between point i and j
570 hugin_utils::FDiff2D diff=p2-p1;
571 if(norm(diff)<0.001)
572 continue;
573 double u=((p.x-p1.x)*(p2.x-p1.x)+(p.y-p1.y)*(p2.y-p1.y))/hugin_utils::sqr(norm(diff));
574 if((u>=0.1) && (u<=0.9))
575 {
576 // intersection is between p1 and p2
577 hugin_utils::FDiff2D footpoint=p1+diff*u;
578 // now check distance between intersection and p
579 if(norm(p-footpoint)<tol)
580 return i==0 ? j+1 : i;
581 };
582 j=i;
583 p2=p1;
584 };
585 return UINT_MAX;
586 };
587
operator =(const MaskPolygon & otherPoly)588 MaskPolygon& MaskPolygon::operator=(const MaskPolygon& otherPoly)
589 {
590 if (this == &otherPoly)
591 return *this;
592 setMaskType(otherPoly.getMaskType());
593 setMaskPolygon(otherPoly.getMaskPolygon());
594 setImgNr(otherPoly.getImgNr());
595 setInverted(otherPoly.isInverted());
596 return *this;
597 };
598
operator ==(const MaskPolygon & otherPoly) const599 const bool MaskPolygon::operator==(const MaskPolygon& otherPoly) const
600 {
601 return ((m_maskType == otherPoly.getMaskType()) && (m_polygon == otherPoly.getMaskPolygon()));
602 };
603
parsePolygonString(const std::string & polygonStr)604 bool MaskPolygon::parsePolygonString(const std::string& polygonStr)
605 {
606 m_polygon.clear();
607 if(polygonStr.length()==0)
608 return false;
609 std::stringstream is(polygonStr);
610 while(is.good())
611 {
612 double x;
613 if (is >> x)
614 {
615 double y;
616 if (is >> y)
617 {
618 m_polygon.push_back(hugin_utils::FDiff2D(x, y));
619 };
620 };
621 };
622 calcBoundingBox();
623 return m_polygon.size()>2;
624 };
625
printPolygonLine(std::ostream & o,const unsigned int newImgNr) const626 void MaskPolygon::printPolygonLine(std::ostream &o, const unsigned int newImgNr) const
627 {
628 o<<"k i"<<newImgNr<<" ";
629 o<<"t"<<(int)m_maskType<<" ";
630 o<<"p\"";
631 for(unsigned int i=0; i<m_polygon.size(); i++)
632 {
633 o<<m_polygon[i].x<<" "<<m_polygon[i].y;
634 if((i+1)!=m_polygon.size())
635 o<<" ";
636 };
637 o<<"\""<<std::endl;
638 };
639
LoadMaskFromStream(std::istream & stream,vigra::Size2D & imageSize,MaskPolygonVector & newMasks,size_t imgNr)640 void LoadMaskFromStream(std::istream& stream,vigra::Size2D& imageSize, MaskPolygonVector &newMasks, size_t imgNr)
641 {
642 while (stream.good())
643 {
644 std::string line;
645 std::getline(stream,line);
646 switch (line[0])
647 {
648 case '#':
649 {
650 unsigned int w;
651 if (PTScriptParsing::getIntParam(w, line, "w"))
652 imageSize.setWidth(w);
653 unsigned int h;
654 if (PTScriptParsing::getIntParam(h, line, "h"))
655 imageSize.setHeight(h);
656 break;
657 }
658 case 'k':
659 {
660 HuginBase::MaskPolygon newPolygon;
661 //Ignore image number set in mask
662 newPolygon.setImgNr(imgNr);
663 unsigned int param;
664 if (PTScriptParsing::getIntParam(param,line,"t"))
665 {
666 newPolygon.setMaskType((HuginBase::MaskPolygon::MaskType)param);
667 }
668 std::string format;
669 if (PTScriptParsing::getPTParam(format,line,"p"))
670 {
671 if(newPolygon.parsePolygonString(format)) {
672 newMasks.push_back(newPolygon);
673 }
674 }
675 break;
676 }
677 default:
678 {
679 break;
680 }
681 }
682 }
683 };
684
SaveMaskToStream(std::ostream & stream,vigra::Size2D imageSize,MaskPolygon & maskToWrite,size_t imgNr)685 void SaveMaskToStream(std::ostream& stream, vigra::Size2D imageSize, MaskPolygon &maskToWrite, size_t imgNr)
686 {
687 stream << "# w" << imageSize.width() << " h" << imageSize.height() << std::endl;
688 maskToWrite.printPolygonLine(stream, imgNr);
689 };
690
691 } // namespace
692