1 /******************************************************************************************************
2 * (C) 2014 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 "CallbackAxisPointsAbstract.h"
8 #include "EngaugeAssert.h"
9 #include "Logger.h"
10 #include "Point.h"
11 #include <qmath.h>
12 #include "QtToString.h"
13 #include "Transformation.h"
14
15 // Epsilon test values
16 const double ONE_PIXEL = 1.0; // Screen coordinates
17 const double ZERO_EPSILON = 0.0; // Graph coordinates
18
CallbackAxisPointsAbstract(const DocumentModelCoords & modelCoords,DocumentAxesPointsRequired documentAxesPointsRequired)19 CallbackAxisPointsAbstract::CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords,
20 DocumentAxesPointsRequired documentAxesPointsRequired) :
21 m_modelCoords (modelCoords),
22 m_isError (false),
23 m_documentAxesPointsRequired (documentAxesPointsRequired)
24 {
25 }
26
CallbackAxisPointsAbstract(const DocumentModelCoords & modelCoords,const QString pointIdentifierOverride,const QPointF & posScreenOverride,const QPointF & posGraphOverride,DocumentAxesPointsRequired documentAxesPointsRequired)27 CallbackAxisPointsAbstract::CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords,
28 const QString pointIdentifierOverride,
29 const QPointF &posScreenOverride,
30 const QPointF &posGraphOverride,
31 DocumentAxesPointsRequired documentAxesPointsRequired) :
32 m_modelCoords (modelCoords),
33 m_pointIdentifierOverride (pointIdentifierOverride),
34 m_posScreenOverride (posScreenOverride),
35 m_posGraphOverride (posGraphOverride),
36 m_isError (false),
37 m_documentAxesPointsRequired (documentAxesPointsRequired)
38 {
39 }
40
anyPointsRepeatPair(const CoordPairVector & vector,double epsilon) const41 bool CallbackAxisPointsAbstract::anyPointsRepeatPair (const CoordPairVector &vector,
42 double epsilon) const
43 {
44 for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
45 for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
46
47 if (qAbs (vector.at(pointLeft).x() - vector.at(pointRight).x()) <= epsilon &&
48 qAbs (vector.at(pointLeft).y() - vector.at(pointRight).y()) <= epsilon) {
49
50 // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
51 return true;
52 }
53 }
54 }
55
56 // No columns repeat
57 return false;
58 }
59
anyPointsRepeatSingle(const CoordSingleVector & vector,double epsilon) const60 bool CallbackAxisPointsAbstract::anyPointsRepeatSingle (const CoordSingleVector &vector,
61 double epsilon) const
62 {
63 for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
64 for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
65
66 if (qAbs (vector.at(pointLeft) - vector.at(pointRight)) <= epsilon) {
67
68 // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
69 return true;
70 }
71 }
72 }
73
74 // No columns repeat
75 return false;
76 }
77
callback(const QString &,const Point & point)78 CallbackSearchReturn CallbackAxisPointsAbstract::callback (const QString & /* curveName */,
79 const Point &point)
80 {
81 QPointF posScreen = point.posScreen ();
82 QPointF posGraph = point.posGraph ();
83
84 if (m_pointIdentifierOverride == point.identifier ()) {
85
86 // Override the old point coordinates with its new (if all tests are passed) coordinates
87 posScreen = m_posScreenOverride;
88 posGraph = m_posGraphOverride;
89 }
90
91 // Try to compute transform
92 if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2) {
93 return callbackRequire2AxisPoints (posScreen,
94 posGraph);
95 } else if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
96 return callbackRequire3AxisPoints (posScreen,
97 posGraph);
98 } else {
99 return callbackRequire4AxisPoints (point.isXOnly(),
100 posScreen,
101 posGraph);
102 }
103 }
104
callbackRequire2AxisPoints(const QPointF & posScreen,const QPointF & posGraph)105 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire2AxisPoints (const QPointF &posScreen,
106 const QPointF &posGraph)
107 {
108 CallbackSearchReturn rtn = CALLBACK_SEARCH_RETURN_CONTINUE;
109
110 // Update range variables. The same nonzero length value is stored in every x and y coordinate of every axis point
111 m_xGraphLow = 0;
112 m_yGraphLow = 0;
113 m_xGraphHigh = posGraph.x();
114 m_yGraphHigh = posGraph.x();
115
116 int numberPoints = m_screenInputs.count();
117 if (numberPoints < 2) {
118
119 // Append new point
120 m_screenInputs.push_back (posScreen);
121 m_graphOutputs.push_back (posGraph);
122 numberPoints = m_screenInputs.count();
123
124 if (numberPoints == 2) {
125 loadTransforms2 ();
126 }
127
128 // Error checking
129 if (anyPointsRepeatPair (m_screenInputs,
130 ONE_PIXEL)) {
131
132 m_isError = true;
133 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
134 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
135
136 }
137 }
138
139 if (m_screenInputs.count() > 1) {
140
141 // There are enough axis points so quit
142 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
143
144 }
145
146 return rtn;
147 }
148
callbackRequire3AxisPoints(const QPointF & posScreen,const QPointF & posGraph)149 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire3AxisPoints (const QPointF &posScreen,
150 const QPointF &posGraph)
151 {
152 CallbackSearchReturn rtn = CALLBACK_SEARCH_RETURN_CONTINUE;
153
154 // Update range variables
155 int numberPoints = m_screenInputs.count();
156 if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
157 if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
158 if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
159 if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
160
161 if (numberPoints < 3) {
162
163 // Append new point
164 m_screenInputs.push_back (posScreen);
165 m_graphOutputs.push_back (posGraph);
166 numberPoints = m_screenInputs.count();
167
168 if (numberPoints == 3) {
169 loadTransforms3 ();
170 }
171
172 // Error checking
173 if ((m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2 ||
174 m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) &&
175 anyPointsRepeatPair (m_screenInputs, ONE_PIXEL)) {
176
177 m_isError = true;
178 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
179 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
180
181 } else if ((m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2 ||
182 m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) &&
183 anyPointsRepeatPair (m_graphOutputs, ZERO_EPSILON)) {
184
185 m_isError = true;
186 m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
187 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
188
189 } else if ((numberPoints == 3) && threePointsAreCollinear (m_screenInputsTransform,
190 COORD_IS_LINEAR,
191 COORD_IS_LINEAR)) {
192
193 m_isError = true;
194 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
195 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
196
197 } else if ((numberPoints == 3) && threePointsAreCollinear (m_graphOutputsTransform,
198 logXGraph (),
199 logYGraph ())) {
200
201 m_isError = true;
202 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
203 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
204
205 }
206 }
207
208 if (m_screenInputs.count() > 2) {
209
210 // There are enough axis points so quit
211 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
212
213 }
214
215 return rtn;
216 }
217
callbackRequire4AxisPoints(bool isXOnly,const QPointF & posScreen,const QPointF & posGraph)218 CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire4AxisPoints (bool isXOnly,
219 const QPointF &posScreen,
220 const QPointF &posGraph)
221 {
222 CallbackSearchReturn rtn = CALLBACK_SEARCH_RETURN_CONTINUE;
223
224 // Update range variables
225 int numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
226 if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
227 if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
228 if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
229 if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
230
231 if (numberPoints < 4) {
232
233 // Append the new point
234 if (isXOnly) {
235
236 m_screenInputsX.push_back (posScreen);
237 m_graphOutputsX.push_back (posGraph.x());
238
239 } else {
240
241 m_screenInputsY.push_back (posScreen);
242 m_graphOutputsY.push_back (posGraph.y());
243
244 }
245
246 numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
247 if (numberPoints == 4) {
248 loadTransforms4 ();
249 }
250 }
251
252 if (m_screenInputsX.count() > 2) {
253
254 m_isError = true;
255 m_errorMessage = QObject::tr ("Too many x axis points. There should only be two");
256 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
257
258 } else if (m_screenInputsY.count() > 2) {
259
260 m_isError = true;
261 m_errorMessage = QObject::tr ("Too many y axis points. There should only be two");
262 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
263
264 } else {
265
266 if ((m_screenInputsX.count() == 2) &&
267 (m_screenInputsY.count() == 2)) {
268
269 // Done, although an error may intrude
270 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
271 }
272
273 // Error checking
274 if (anyPointsRepeatPair (m_screenInputsX,
275 ONE_PIXEL) ||
276 anyPointsRepeatPair (m_screenInputsY,
277 ONE_PIXEL)) {
278
279 m_isError = true;
280 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an existing axis point");
281 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
282
283 } else if (anyPointsRepeatSingle (m_graphOutputsX,
284 ZERO_EPSILON) ||
285 anyPointsRepeatSingle (m_graphOutputsY,
286 ZERO_EPSILON)) {
287
288 m_isError = true;
289 m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
290 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
291
292 } else if ((numberPoints == 4) && threePointsAreCollinear (m_screenInputsTransform,
293 COORD_IS_LINEAR,
294 COORD_IS_LINEAR)) {
295
296 m_isError = true;
297 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
298 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
299
300 } else if ((numberPoints == 4) && threePointsAreCollinear (m_graphOutputsTransform,
301 logXGraph (),
302 logYGraph ())) {
303
304 m_isError = true;
305 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
306 rtn = CALLBACK_SEARCH_RETURN_INTERRUPT;
307
308 }
309 }
310
311 return rtn;
312 }
313
documentAxesPointsRequired() const314 DocumentAxesPointsRequired CallbackAxisPointsAbstract::documentAxesPointsRequired() const
315 {
316 return m_documentAxesPointsRequired;
317 }
318
loadTransforms2()319 void CallbackAxisPointsAbstract::loadTransforms2 ()
320 {
321 // To get a third point from two existing points we compute the vector between the first 2 points and then take
322 // the cross product with the out-of-plane unit vector to get the perpendicular vector, and the endpoint of that
323 // is used as the third point. This implicitly assumes that the graph-to-screen coordinates scaling is the
324 // same in both directions. The advantage of this approach is that no assumptions are made about the inputs
325
326 double d0To1ScreenX = m_screenInputs.at (1).x () - m_screenInputs.at (0).x ();
327 double d0To1ScreenY = m_screenInputs.at (1).y () - m_screenInputs.at (0).y ();
328 double d0To1ScreenZ = 0;
329 double d0To1GraphX = m_graphOutputs.at (1).x () - m_graphOutputs.at (0).x ();
330 double d0To1GraphY = m_graphOutputs.at (1).y () - m_graphOutputs.at (0).y ();
331 double d0To1GraphZ = 0;
332
333 double unitNormalX = 0;
334 double unitNormalY = 0;
335 double unitNormalZ = 1;
336
337 double d0To2ScreenX = unitNormalY * d0To1ScreenZ - unitNormalZ * d0To1ScreenY;
338 double d0To2ScreenY = unitNormalZ * d0To1ScreenX - unitNormalX * d0To1ScreenZ;
339 double d0To2GraphX = unitNormalY * d0To1GraphZ - unitNormalZ * d0To1GraphY;
340 double d0To2GraphY = unitNormalZ * d0To1GraphX - unitNormalX * d0To1GraphZ;
341
342 // Hack since +Y for screen coordinates is down but up for graph coordinates. Users expect +Y to be up
343 // so we rotate screen delta by 180 degrees
344 const double FLIP_Y_SCREEN = -1.0;
345
346 double screenX2 = m_screenInputs.at (0).x () + FLIP_Y_SCREEN * d0To2ScreenX;
347 double screenY2 = m_screenInputs.at (0).y () + FLIP_Y_SCREEN * d0To2ScreenY;
348 double graphX2 = m_graphOutputs.at (0).x () + d0To2GraphX;
349 double graphY2 = m_graphOutputs.at (0).y () + d0To2GraphY;
350
351 // Screen coordinates
352 m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), screenX2,
353 m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), screenY2,
354 1.0 , 1.0 , 1.0 );
355
356 // Graph coordinates
357 m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), graphX2,
358 m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), graphY2,
359 1.0 , 1.0 , 1.0 );
360 }
361
loadTransforms3()362 void CallbackAxisPointsAbstract::loadTransforms3 ()
363 {
364 // Screen coordinates
365 m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), m_screenInputs.at(2).x(),
366 m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), m_screenInputs.at(2).y(),
367 1.0 , 1.0 , 1.0 );
368
369 // Graph coordinates
370 m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), m_graphOutputs.at(2).x(),
371 m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), m_graphOutputs.at(2).y(),
372 1.0 , 1.0 , 1.0 );
373 }
374
loadTransforms4()375 void CallbackAxisPointsAbstract::loadTransforms4 ()
376 {
377 double x1Screen = m_screenInputsX.at(0).x();
378 double y1Screen = m_screenInputsX.at(0).y();
379 double x2Screen = m_screenInputsX.at(1).x();
380 double y2Screen = m_screenInputsX.at(1).y();
381 double x3Screen = m_screenInputsY.at(0).x();
382 double y3Screen = m_screenInputsY.at(0).y();
383 double x4Screen = m_screenInputsY.at(1).x();
384 double y4Screen = m_screenInputsY.at(1).y();
385
386 // Each of the four axes points has only one coordinate
387 double x1Graph = m_graphOutputsX.at(0);
388 double x2Graph = m_graphOutputsX.at(1);
389 double y3Graph = m_graphOutputsY.at(0);
390 double y4Graph = m_graphOutputsY.at(1);
391
392 // Intersect the two lines of the two axes. The lines are defined parametrically for the screen coordinates, with
393 // points 1 and 2 on the x axis and points 3 and 4 on the y axis, as:
394 // x = (1 - sx) * x1 + sx * x2
395 // y = (1 - sx) * y1 + sx * y2
396 // x = (1 - sy) * x3 + sy * x4
397 // y = (1 - sy) * y3 + sy * y4
398 // Intersection of the 2 lines is at (x,y). Solving for sx and sy using Cramer's rule where Ax=b
399 // (x1 - x3) (x1 - x2 x4 - x3) (sx)
400 // (y1 - y3) = (y1 - y2 y4 - y3) (sy)
401 double A00 = x1Screen - x2Screen;
402 double A01 = x4Screen - x3Screen;
403 double A10 = y1Screen - y2Screen;
404 double A11 = y4Screen - y3Screen;
405 double b0 = x1Screen - x3Screen;
406 double b1 = y1Screen - y3Screen;
407 double numeratorx = (b0 * A11 - A01 * b1);
408 double numeratory = (A00 * b1 - b0 * A10);
409 double denominator = (A00 * A11 - A01 * A10);
410 double sx = numeratorx / denominator;
411 double sy = numeratory / denominator;
412
413 // Intersection point. For the graph coordinates, the initial implementation assumes cartesian coordinates
414 double xIntScreen = (1.0 - sx) * x1Screen + sx * x2Screen;
415 double yIntScreen = (1.0 - sy) * y3Screen + sy * y4Screen;
416 double xIntGraph, yIntGraph;
417 if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR) {
418 xIntGraph = (1.0 - sx) * x1Graph + sx * x2Graph;
419 } else {
420 xIntGraph = qExp ((1.0 - sx) * qLn (x1Graph) + sx * qLn (x2Graph));
421 }
422 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR) {
423 yIntGraph = (1.0 - sy) * y3Graph + sy * y4Graph;
424 } else {
425 yIntGraph = qExp ((1.0 - sy) * qLn (y3Graph) + sy * qLn (y4Graph));
426 }
427
428 // Distances of 4 axis points from interception
429 double distance1 = qSqrt ((x1Screen - xIntScreen) * (x1Screen - xIntScreen) +
430 (y1Screen - yIntScreen) * (y1Screen - yIntScreen));
431 double distance2 = qSqrt ((x2Screen - xIntScreen) * (x2Screen - xIntScreen) +
432 (y2Screen - yIntScreen) * (y2Screen - yIntScreen));
433 double distance3 = qSqrt ((x3Screen - xIntScreen) * (x3Screen - xIntScreen) +
434 (y3Screen - yIntScreen) * (y3Screen - yIntScreen));
435 double distance4 = qSqrt ((x4Screen - xIntScreen) * (x4Screen - xIntScreen) +
436 (y4Screen - yIntScreen) * (y4Screen - yIntScreen));
437
438 // We now have too many data points with both x and y coordinates:
439 // (xInt,yInt) (xInt,y3) (xInt,y4) (x1,yInt) (x2,yInt)
440 // so we pick just 3, making sure that those 3 are widely separated
441 // (xInt,yInt) (x axis point furthest from xInt,yInt) (y axis point furthest from xInt,yInt)
442 double xFurthestXAxisScreen, yFurthestXAxisScreen, xFurthestYAxisScreen, yFurthestYAxisScreen;
443 double xFurthestXAxisGraph, yFurthestXAxisGraph, xFurthestYAxisGraph, yFurthestYAxisGraph;
444 if (distance1 < distance2) {
445 xFurthestXAxisScreen = x2Screen;
446 yFurthestXAxisScreen = y2Screen;
447 xFurthestXAxisGraph = x2Graph;
448 yFurthestXAxisGraph = yIntGraph;
449 } else {
450 xFurthestXAxisScreen = x1Screen;
451 yFurthestXAxisScreen = y1Screen;
452 xFurthestXAxisGraph = x1Graph;
453 yFurthestXAxisGraph = yIntGraph;
454 }
455 if (distance3 < distance4) {
456 xFurthestYAxisScreen = x4Screen;
457 yFurthestYAxisScreen = y4Screen;
458 xFurthestYAxisGraph = xIntGraph;
459 yFurthestYAxisGraph = y4Graph;
460 } else {
461 xFurthestYAxisScreen = x3Screen;
462 yFurthestYAxisScreen = y3Screen;
463 xFurthestYAxisGraph = xIntGraph;
464 yFurthestYAxisGraph = y3Graph;
465 }
466
467 // Screen coordinates
468 m_screenInputsTransform = QTransform (xIntScreen, xFurthestXAxisScreen, xFurthestYAxisScreen,
469 yIntScreen, yFurthestXAxisScreen, yFurthestYAxisScreen,
470 1.0 , 1.0 , 1.0 );
471
472 // Graph coordinates
473 m_graphOutputsTransform = QTransform (xIntGraph, xFurthestXAxisGraph, xFurthestYAxisGraph,
474 yIntGraph, yFurthestXAxisGraph, yFurthestYAxisGraph,
475 1.0 , 1.0 , 1.0 );
476 }
477
logXGraph() const478 CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logXGraph () const
479 {
480 return m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
481 }
482
logYGraph() const483 CallbackAxisPointsAbstract::LinearOrLog CallbackAxisPointsAbstract::logYGraph () const
484 {
485 return m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG ? COORD_IS_LOG : COORD_IS_LINEAR;
486 }
487
matrixGraph() const488 QTransform CallbackAxisPointsAbstract::matrixGraph () const
489 {
490 return m_graphOutputsTransform;
491 }
492
matrixScreen() const493 QTransform CallbackAxisPointsAbstract::matrixScreen () const
494 {
495 return m_screenInputsTransform;
496 }
497
numberAxisPoints() const498 unsigned int CallbackAxisPointsAbstract::numberAxisPoints () const
499 {
500 if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_2) {
501 return unsigned (m_screenInputs.count());
502 } else if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
503 return unsigned (m_screenInputs.count());
504 } else {
505 return unsigned (m_screenInputsX.count() + m_screenInputsY.count());
506 }
507 }
508
threePointsAreCollinear(const QTransform & transformIn,LinearOrLog logX,LinearOrLog logY) const509 bool CallbackAxisPointsAbstract::threePointsAreCollinear (const QTransform &transformIn,
510 LinearOrLog logX,
511 LinearOrLog logY) const
512 {
513 double m11 = (logX == COORD_IS_LOG) ? qLn (transformIn.m11()) : transformIn.m11();
514 double m12 = (logX == COORD_IS_LOG) ? qLn (transformIn.m12()) : transformIn.m12();
515 double m13 = (logX == COORD_IS_LOG) ? qLn (transformIn.m13()) : transformIn.m13();
516 double m21 = (logY == COORD_IS_LOG) ? qLn (transformIn.m21()) : transformIn.m21();
517 double m22 = (logY == COORD_IS_LOG) ? qLn (transformIn.m22()) : transformIn.m22();
518 double m23 = (logY == COORD_IS_LOG) ? qLn (transformIn.m23()) : transformIn.m23();
519 double m31 = transformIn.m31();
520 double m32 = transformIn.m32();
521 double m33 = transformIn.m33();
522
523 QTransform transform (m11, m12, m13,
524 m21, m22, m23,
525 m31, m32, m33);
526
527 return !transform.isInvertible ();
528 }
529