1 /******************************************************************************************************
2  * (C) 2018 markummitchell@github.com. This file is part of Engauge Digitizer, which is released      *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission.     *
5  ******************************************************************************************************/
6 
7 #include <algorithm>
8 #include "GridLog.h"
9 #include "GridTriangleFill.h"
10 #include <QImage>
11 #include <QList>
12 #include <qmath.h>
13 #include <QPoint>
14 
15 // Non-member comparison function
compareByY(const QPoint & first,const QPoint & second)16 static bool compareByY (const QPoint &first,
17                         const QPoint &second)
18 {
19   if (first.y() < second.y()) {
20     return true;
21   } else if (first.y() > second.y()) {
22     return false;
23   } else {
24     // If y values are the same then sort by x values
25     return (first.x() < second.x());
26   }
27 }
28 
GridTriangleFill()29 GridTriangleFill::GridTriangleFill ()
30 {
31 }
32 
drawLine(GridLog & gridLog,QImage & image,int x0,int x1,int y)33 void GridTriangleFill::drawLine (GridLog &gridLog,
34                                  QImage &image,
35                                  int x0,
36                                  int x1,
37                                  int y)
38 {
39   const double RADIUS = 0.1;
40 
41   if (x0 > x1) {
42     int xTemp = x0;
43     x0 = x1;
44     x1 = xTemp;
45 
46   }
47 
48   for (int x = x0; x <= x1; x++) {
49 
50     gridLog.showOutputScanLinePixel (x, y, RADIUS);
51 
52     image.setPixel (QPoint (x, y),
53                     Qt::black);
54   }
55 }
56 
fill(GridLog & gridLog,QImage & image,const QPoint & p0In,const QPoint & p1In,const QPoint & p2In)57 void GridTriangleFill::fill (GridLog &gridLog,
58                              QImage &image,
59                              const QPoint &p0In,
60                              const QPoint &p1In,
61                              const QPoint &p2In)
62 {
63   if (p0In.x() > 0 && p0In.y() > 0 &&
64       p1In.x() > 0 && p1In.y() > 0 &&
65       p2In.x() > 0 && p2In.y() > 0) {
66 
67     QPoint p0, p1, p2;
68 
69     sortByAscendingY (p0In, p1In, p2In, p0, p1, p2);
70 
71     if (p1.y() == p2.y()) {
72 
73       // Triangle with flat bottom
74       flatBottom (gridLog, image, p0, p1, p2);
75 
76     } else if (p0.y() == p1.y()) {
77 
78       // Triangle with flat top
79       flatTop (gridLog, image, p0, p1, p2);
80 
81     } else {
82 
83       // General case is handled by splitting the triangle into one flat top piece and
84       // one flat bottom piece. Fourth point is at same y value as middle point p1
85       double s = double (p1.y() - p0.y()) / double (p2.y() - p0.y());
86       QPoint p3 (qFloor (p0.x() + s * (p2.x() - p0.x())),
87                  p1.y());
88       flatBottom (gridLog, image, p0, p1, p3);
89       flatTop (gridLog, image, p1, p3, p2);
90     }
91   }
92 }
93 
flatBottom(GridLog & gridLog,QImage & image,const QPoint & p0,const QPoint & p1,const QPoint & p2)94 void GridTriangleFill::flatBottom (GridLog &gridLog,
95                                    QImage &image,
96                                    const QPoint &p0,
97                                    const QPoint &p1,
98                                    const QPoint &p2)
99 {
100   // Either neither or both denominators are zero, since p1.y()=p2.y()
101   double denom0 = p1.y() - p0.y();
102   double denom1 = p2.y() - p0.y();
103   if (qAbs (denom0) <= 0 || qAbs (denom1) <= 0) {
104     drawLine (gridLog,
105               image,
106               p0.x(),
107               p2.x(),
108               p0.y());
109   } else {
110     double slopeInverse0 = (p1.x() - p0.x()) / denom0;
111     double slopeInverse1 = (p2.x() - p0.x()) / denom1;
112 
113     // For rounding lower point down and upper point up, thus ensuring thorough coverage
114     // (=no gaps between triangles), we order the inverse slopes
115     if (slopeInverse0 > slopeInverse1) {
116       double temp = slopeInverse0;
117       slopeInverse0 = slopeInverse1;
118       slopeInverse1 = temp;
119     }
120 
121     double x0 = p0.x();
122     double x1 = p0.x();
123 
124     for (int scanLineY = p0.y(); scanLineY <= p1.y(); scanLineY++) {
125       drawLine (gridLog,
126                 image,
127                 qFloor (x0),
128                 qFloor (x1),
129                 scanLineY);
130       x0 += slopeInverse0;
131       x1 += slopeInverse1;
132     }
133   }
134 }
135 
flatTop(GridLog & gridLog,QImage & image,const QPoint & p0,const QPoint & p1,const QPoint & p2)136 void GridTriangleFill::flatTop (GridLog &gridLog,
137                                 QImage &image,
138                                 const QPoint &p0,
139                                 const QPoint &p1,
140                                 const QPoint &p2)
141 {
142   // Either neither or both denominators are zero, since p0.y()=p1.y()
143   double denom0 = p2.y() - p0.y();
144   double denom1 = p2.y() - p1.y();
145   if (qAbs (denom0) <= 0 || qAbs (denom1) <= 0) {
146     drawLine (gridLog,
147               image,
148               p0.x(),
149               p2.x(),
150               p0.y());
151   } else {
152     double slopeInverse0 = (p2.x() - p0.x()) / denom0;
153     double slopeInverse1 = (p2.x() - p1.x()) / denom1;
154 
155     // For rounding lower point down and upper point up, thus ensuring thorough coverage
156     // (=no gaps between triangles), we order the inverse slopes
157     if (slopeInverse1 > slopeInverse0) {
158       double temp = slopeInverse0;
159       slopeInverse0 = slopeInverse1;
160       slopeInverse1 = temp;
161     }
162 
163     double x0 = p2.x();
164     double x1 = p2.x();
165 
166     for (int scanLineY = p2.y(); scanLineY >= p0.y(); scanLineY--) {
167       drawLine (gridLog,
168                 image,
169                 qFloor (x0),
170                 qFloor (x1),
171                 scanLineY);
172       x0 -= slopeInverse0;
173       x1 -= slopeInverse1;
174     }
175   }
176 }
177 
sortByAscendingY(QPoint p0In,QPoint p1In,QPoint p2In,QPoint & p0,QPoint & p1,QPoint & p2) const178 void GridTriangleFill::sortByAscendingY (QPoint p0In,
179                                          QPoint p1In,
180                                          QPoint p2In,
181                                          QPoint &p0,
182                                          QPoint &p1,
183                                          QPoint &p2) const
184 {
185   // Sort by ascending y value
186   QList<QPoint> list;
187   list << p0In << p1In << p2In;
188   std::sort (list.begin(), list.end(), compareByY);
189 
190   p0 = list.front();
191   list.pop_front();
192   p1 = list.front();
193   list.pop_front();
194   p2 = list.front();
195 }
196