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