Engauge Digitizer 2
Loading...
Searching...
No Matches
Transformation.cpp
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 "CallbackUpdateTransform.h"
8#include "Document.h"
9#include "EngaugeAssert.h"
10#include "FormatCoordsUnits.h"
11#include "Logger.h"
12#include <QDebug>
13#include <qmath.h>
14#include <QtGlobal>
15#include "QtToString.h"
16#include "Transformation.h"
17
18using namespace std;
19
22const int PRECISION_DIGITS = 4;
23
24const double PI = 3.1415926535;
25const double ZERO_OFFSET_AFTER_LOG = 1; // Log of this value is zero
26
28 m_transformIsDefined (false)
29{
30}
31
33{
34 m_transformIsDefined = other.transformIsDefined();
35 m_transform = other.transformMatrix ();
36
37 return *this;
38}
39
41{
42 return (m_transformIsDefined != other.transformIsDefined()) ||
43 (m_transform != other.transformMatrix ());
44}
45
47 const QPointF &posFrom1,
48 const QPointF &posFrom2,
49 const QPointF &posTo0,
50 const QPointF &posTo1,
51 const QPointF &posTo2)
52{
53 LOG4CPP_INFO_S ((*mainCat)) << "Transformation::calculateTransformFromLinearCartesianPoints";
54
55 QTransform from, to;
56 from.setMatrix (posFrom0.x(), posFrom1.x(), posFrom2.x(),
57 posFrom0.y(), posFrom1.y(), posFrom2.y(),
58 1.0, 1.0, 1.0);
59
60 to.setMatrix (posTo0.x(), posTo1.x(), posTo2.x(),
61 posTo0.y(), posTo1.y(), posTo2.y(),
62 1.0, 1.0, 1.0);
63 QTransform fromInv = from.inverted ();
64
65 return to * fromInv;
66}
67
69 const QPointF &posGraphIn)
70{
71 // Initialize assuming input coordinates are already cartesian
72 QPointF posGraphCartesian = posGraphIn;
73
74 if (modelCoords.coordsType() == COORDS_TYPE_POLAR) {
75
76 // Input coordinates are polar so convert them
77 double angleRadians = 0; // Initialized to prevent compiler warning
79 {
80 case COORD_UNITS_POLAR_THETA_DEGREES:
81 case COORD_UNITS_POLAR_THETA_DEGREES_MINUTES:
82 case COORD_UNITS_POLAR_THETA_DEGREES_MINUTES_SECONDS:
83 case COORD_UNITS_POLAR_THETA_DEGREES_MINUTES_SECONDS_NSEW:
84 angleRadians = posGraphIn.x () * PI / 180.0;
85 break;
86
87 case COORD_UNITS_POLAR_THETA_GRADIANS:
88 angleRadians = posGraphIn.x () * PI / 200.0;
89 break;
90
91 case COORD_UNITS_POLAR_THETA_RADIANS:
92 angleRadians = posGraphIn.x ();
93 break;
94
95 case COORD_UNITS_POLAR_THETA_TURNS:
96 angleRadians = posGraphIn.x () * 2.0 * PI;
97 break;
98
99 default:
100 ENGAUGE_ASSERT (false);
101 }
102
103 double radius = posGraphIn.y ();
104 posGraphCartesian.setX (radius * cos (angleRadians));
105 posGraphCartesian.setY (radius * sin (angleRadians));
106 }
107
108 return posGraphCartesian;
109}
110
112 const QPointF &posGraphIn)
113{
114 // Initialize assuming output coordinates are to be cartesian
115 QPointF posGraphCartesianOrPolar = posGraphIn;
116
117 if (modelCoords.coordsType() == COORDS_TYPE_POLAR) {
118
119 // Output coordinates are to be polar so convert them
120 double angleRadians = qAtan2 (posGraphIn.y (),
121 posGraphIn.x ());
123 {
124 case COORD_UNITS_POLAR_THETA_DEGREES:
125 case COORD_UNITS_POLAR_THETA_DEGREES_MINUTES:
126 case COORD_UNITS_POLAR_THETA_DEGREES_MINUTES_SECONDS:
127 case COORD_UNITS_POLAR_THETA_DEGREES_MINUTES_SECONDS_NSEW:
128 posGraphCartesianOrPolar.setX (angleRadians * 180.0 / PI);
129 break;
130
131 case COORD_UNITS_POLAR_THETA_GRADIANS:
132 posGraphCartesianOrPolar.setX (angleRadians * 200.0 / PI);
133 break;
134
135 case COORD_UNITS_POLAR_THETA_RADIANS:
136 posGraphCartesianOrPolar.setX (angleRadians);
137 break;
138
139 case COORD_UNITS_POLAR_THETA_TURNS:
140 posGraphCartesianOrPolar.setX (angleRadians / 2.0 / PI);
141 break;
142
143 default:
144 ENGAUGE_ASSERT (false);
145 }
146
147 double radius = qSqrt (posGraphIn.x () * posGraphIn.x () + posGraphIn.y () * posGraphIn.y ());
148 posGraphCartesianOrPolar.setY (radius);
149 }
150
151 return posGraphCartesianOrPolar;
152}
153
154void Transformation::coordTextForStatusBar (QPointF cursorScreen,
155 QString &coordsScreen,
156 QString &coordsGraph,
157 QString &resolutionsGraph)
158{
159 const int UNCONSTRAINED_FIELD_WIDTH = 0;
160 const double X_DELTA_PIXELS = 1.0, Y_DELTA_PIXELS = 1.0;
161 const char FORMAT = 'g';
162
163 if (cursorScreen.x() < 0 ||
164 cursorScreen.y() < 0) {
165
166 // Out of bounds, so return empty text
167 coordsScreen = "";
168 coordsGraph = "";
169 resolutionsGraph = "";
170
171 } else {
172
173 coordsScreen = QString("(%1, %2)")
174 .arg (cursorScreen.x ())
175 .arg (cursorScreen.y ());
176
177 if (m_transformIsDefined) {
178
179 // For resolution we compute graph coords for cursorScreen, and then for cursorScreen plus a delta
180 QPointF cursorScreenDelta (cursorScreen.x () + X_DELTA_PIXELS,
181 cursorScreen.y () + Y_DELTA_PIXELS);
182
183 // Convert to graph coordinates
184 QPointF pointGraph, pointGraphDelta;
185 transformScreenToRawGraph (cursorScreen,
186 pointGraph);
187 transformScreenToRawGraph (cursorScreenDelta,
188 pointGraphDelta);
189
190 // Compute graph resolutions at cursor
191 double resolutionXGraph = qAbs ((pointGraphDelta.x () - pointGraph.x ()) / X_DELTA_PIXELS);
192 double resolutionYGraph = qAbs ((pointGraphDelta.y () - pointGraph.y ()) / Y_DELTA_PIXELS);
193
194 // Formatting for date/time and degrees/minutes/seconds is only done on coordinates, and not on resolution
195 FormatCoordsUnits format;
196 QString xThetaFormatted, yRadiusFormatted;
197 format.unformattedToFormatted (pointGraph.x(),
198 pointGraph.y(),
199 m_modelCoords,
200 m_modelMainWindow,
201 xThetaFormatted,
202 yRadiusFormatted,
203 *this);
204
205 coordsGraph = QString ("(%1, %2)")
206 .arg (xThetaFormatted)
207 .arg (yRadiusFormatted);
208
209 resolutionsGraph = QString ("(%1, %2)")
210 .arg (resolutionXGraph, UNCONSTRAINED_FIELD_WIDTH, FORMAT, PRECISION_DIGITS)
211 .arg (resolutionYGraph, UNCONSTRAINED_FIELD_WIDTH, FORMAT, PRECISION_DIGITS);
212
213 } else {
214
215 coordsGraph = "<font color=\"red\">Need more axis points</font>";
216 resolutionsGraph = coordsGraph;
217
218 }
219 }
220}
221
223{
224 // Initialize assuming points (0,0) (1,0) (0,1)
225 m_transformIsDefined = true;
226
227 QTransform ident;
228 m_transform = ident;
229}
230
232{
233 return qLn (xy);
234}
235
237 double rCenter)
238{
239 return qLn (r) - qLn (rCenter);
240}
241
243{
244 return m_modelCoords;
245}
246
247const Transformation &operator<<(ostringstream &strOuter,
248 const Transformation &transformation)
249{
250 QString text;
251 QTextStream strInner (&text);
252 transformation.printStream ("", strInner);
253
254 strOuter << text.toLatin1().data ();
255
256 return transformation;
257}
258
259void Transformation::printStream (QString indentation,
260 QTextStream &str) const
261{
262 str << "Transformation\n";
263
264 indentation += INDENTATION_DELTA;
265
266 if (m_transformIsDefined) {
267
268 str << indentation << "affine=" << (m_transform.isAffine() ? "yes" : "no") << " matrix=("
269 << m_transform.m11() << ", " << m_transform.m12() << ", " << m_transform.m13() << ", "
270 << m_transform.m21() << ", " << m_transform.m22() << ", " << m_transform.m23() << ", "
271 << m_transform.m31() << ", " << m_transform.m32() << ", " << m_transform.m33() << ")";
272
273 } else {
274
275 str << indentation << "undefined";
276
277 }
278}
279
281{
282 LOG4CPP_INFO_S ((*mainCat)) << "Transformation::resetOnLoad";
283
284 m_transformIsDefined = false;
285}
286
287double Transformation::roundOffSmallValues (double value, double range)
288{
289 if (qAbs (value) < range / qPow (10.0, PRECISION_DIGITS)) {
290 value = 0.0;
291 }
292
293 return value;
294}
295
296void Transformation::setModelCoords (const DocumentModelCoords &modelCoords,
297 const MainWindowModel &modelMainWindow)
298{
299 m_modelCoords = modelCoords;
300 m_modelMainWindow = modelMainWindow;
301}
302
304{
305 return m_transformIsDefined;
306}
307
308void Transformation::transformLinearCartesianGraphToRawGraph (const QPointF &pointLinearCartesianGraph,
309 QPointF &pointRawGraph) const
310{
311 // WARNING - the code in this method must mirror the code in transformRawGraphToLinearCartesianGraph. In
312 // other words, making a change here without a corresponding change there will produce a bug
313
314 pointRawGraph = pointLinearCartesianGraph;
315
316 // Apply polar coordinates if appropriate
317 if (m_modelCoords.coordsType() == COORDS_TYPE_POLAR) {
318 pointRawGraph = cartesianOrPolarFromCartesian (m_modelCoords,
319 pointRawGraph);
320 }
321
322 // Apply linear offset to radius if appropriate
323 if ((m_modelCoords.coordsType() == COORDS_TYPE_POLAR) &&
324 (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR)) {
325 pointRawGraph.setY (pointRawGraph.y() + m_modelCoords.originRadius());
326 }
327
328 // Apply log scaling if appropriate
329 if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG) {
330 pointRawGraph.setX (qExp (pointRawGraph.x()));
331 }
332
333 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
334 double offset;
335 if (m_modelCoords.coordsType() == COORDS_TYPE_CARTESIAN) {
336 // Cartesian
337 offset = ZERO_OFFSET_AFTER_LOG;
338 } else {
339 // Polar radius
340 offset = m_modelCoords.originRadius();
341 }
342
343 pointRawGraph.setY (qExp (pointRawGraph.y() + qLn (offset)));
344 }
345}
346
348 QPointF &coordScreen) const
349{
350 ENGAUGE_ASSERT (m_transformIsDefined);
351
352 coordScreen = m_transform.inverted ().transposed ().map (coordGraph);
353}
354
356{
357 return m_transform;
358}
359
361 QPointF &pointLinearCartesian) const
362{
363 // WARNING - the code in this method must mirror the code in transformLinearCartesianGraphToRawGraph. In
364 // other words, making a change here without a corresponding change there will produce a bug
365
366 double x = pointRaw.x();
367 double y = pointRaw.y();
368
369 // Apply linear offset to radius if appropriate
370 if ((m_modelCoords.coordsType() == COORDS_TYPE_POLAR) &&
371 (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR)) {
372 y -= m_modelCoords.originRadius();
373 }
374
375 // Apply log scaling if appropriate
376 if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG) {
377 x = logToLinearCartesian (x);
378 }
379
380 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
381 if (m_modelCoords.coordsType() == COORDS_TYPE_POLAR) {
382 y = logToLinearRadius (y,
383 m_modelCoords.originRadius());
384 } else {
385 y = logToLinearRadius (y,
386 ZERO_OFFSET_AFTER_LOG);
387 }
388 }
389
390 // Apply polar coordinates if appropriate. Note range coordinate has just been transformed if it has log scaling
391 if (m_modelCoords.coordsType() == COORDS_TYPE_POLAR) {
392 QPointF pointCart = cartesianFromCartesianOrPolar (m_modelCoords,
393 QPointF (x, y));
394 x = pointCart.x();
395 y = pointCart.y();
396 }
397
398 pointLinearCartesian.setX (x);
399 pointLinearCartesian.setY (y);
400}
401
402void Transformation::transformRawGraphToScreen (const QPointF &pointRaw,
403 QPointF &pointScreen) const
404{
405 QPointF pointLinearCartesianGraph;
406
408 pointLinearCartesianGraph);
409 transformLinearCartesianGraphToScreen (pointLinearCartesianGraph,
410 pointScreen);
411}
412
414 QPointF &coordGraph) const
415{
416 ENGAUGE_ASSERT (m_transformIsDefined);
417
418 coordGraph = m_transform.transposed ().map (coordScreen);
419}
420
421void Transformation::transformScreenToRawGraph (const QPointF &coordScreen,
422 QPointF &coordGraph) const
423{
424 QPointF pointLinearCartesianGraph;
426 pointLinearCartesianGraph);
427 transformLinearCartesianGraphToRawGraph (pointLinearCartesianGraph,
428 coordGraph);
429}
430
431void Transformation::update (bool fileIsLoaded,
432 const CmdMediator &cmdMediator,
433 const MainWindowModel &modelMainWindow)
434{
435 LOG4CPP_DEBUG_S ((*mainCat)) << "Transformation::update";
436
437 if (!fileIsLoaded) {
438
439 m_transformIsDefined = false;
440
441 } else {
442
443 setModelCoords (cmdMediator.document().modelCoords(),
444 modelMainWindow);
445
446 CallbackUpdateTransform ftor (m_modelCoords,
447 cmdMediator.document().documentAxesPointsRequired());
448
449 Functor2wRet<const QString &, const Point&, CallbackSearchReturn> ftorWithCallback = functor_ret (ftor,
451 cmdMediator.iterateThroughCurvePointsAxes (ftorWithCallback);
452
453 if (ftor.transformIsDefined ()) {
454
455 updateTransformFromMatrices (ftor.matrixScreen(),
456 ftor.matrixGraph());
457 } else {
458
459 m_transformIsDefined = false;
460
461 }
462 }
463}
464
465void Transformation::updateTransformFromMatrices (const QTransform &matrixScreen,
466 const QTransform &matrixGraph)
467{
468 // LOG4CPP_INFO_S is below
469
470 m_transformIsDefined = true;
471
472 // Extract points from 3x3 matrices
473 QPointF pointGraphRaw0 (matrixGraph.m11(),
474 matrixGraph.m21());
475 QPointF pointGraphRaw1 (matrixGraph.m12(),
476 matrixGraph.m22());
477 QPointF pointGraphRaw2 (matrixGraph.m13(),
478 matrixGraph.m23());
479
480 QPointF pointGraphLinearCart0, pointGraphLinearCart1, pointGraphLinearCart2;
482 pointGraphLinearCart0);
484 pointGraphLinearCart1);
486 pointGraphLinearCart2);
487
488 // Calculate the transform
489 m_transform = calculateTransformFromLinearCartesianPoints (QPointF (matrixScreen.m11(), matrixScreen.m21()),
490 QPointF (matrixScreen.m12(), matrixScreen.m22()),
491 QPointF (matrixScreen.m13(), matrixScreen.m23()),
492 QPointF (pointGraphLinearCart0.x(), pointGraphLinearCart0.y()),
493 QPointF (pointGraphLinearCart1.x(), pointGraphLinearCart1.y()),
494 QPointF (pointGraphLinearCart2.x(), pointGraphLinearCart2.y()));
495
496 // Logging
497 QTransform matrixGraphLinear (pointGraphLinearCart0.x(),
498 pointGraphLinearCart1.x(),
499 pointGraphLinearCart2.x(),
500 pointGraphLinearCart0.y(),
501 pointGraphLinearCart1.y(),
502 pointGraphLinearCart2.y(),
503 1.0,
504 1.0);
505
506 QPointF pointScreenRoundTrip0, pointScreenRoundTrip1, pointScreenRoundTrip2;
507 transformRawGraphToScreen (pointGraphRaw0,
508 pointScreenRoundTrip0);
509 transformRawGraphToScreen (pointGraphRaw1,
510 pointScreenRoundTrip1);
511 transformRawGraphToScreen (pointGraphRaw2,
512 pointScreenRoundTrip2);
513
514 QPointF pointScreen0 (matrixScreen.m11(),
515 matrixScreen.m21());
516 QPointF pointScreen1 (matrixScreen.m12(),
517 matrixScreen.m22());
518 QPointF pointScreen2 (matrixScreen.m13(),
519 matrixScreen.m23());
520
521 LOG4CPP_INFO_S ((*mainCat)) << "Transformation::updateTransformFromMatrices"
522 << " matrixScreen=\n" << QTransformToString (matrixScreen).toLatin1().data () << " "
523 << " matrixGraphRaw=\n" << QTransformToString (matrixGraph).toLatin1().data() << " "
524 << " matrixGraphLinear=\n" << QTransformToString (matrixGraphLinear).toLatin1().data() << "\n"
525 << " originalScreen0=" << QPointFToString (pointScreen0).toLatin1().data() << "\n"
526 << " originalScreen1=" << QPointFToString (pointScreen1).toLatin1().data() << "\n"
527 << " originalScreen2=" << QPointFToString (pointScreen2).toLatin1().data() << "\n"
528 << " roundTripScreen0=" << QPointFToString (pointScreenRoundTrip0).toLatin1().data() << "\n"
529 << " roundTripScreen1=" << QPointFToString (pointScreenRoundTrip1).toLatin1().data() << "\n"
530 << " roundTripScreen2=" << QPointFToString (pointScreenRoundTrip2).toLatin1().data() << "\n";
531}
QTransform matrixGraph() const
Returns graph coordinates matrix after transformIsDefined has already indicated success.
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.
QTransform matrixScreen() const
Returns screen coordinates matrix after transformIsDefined has already indicated success.
Callback for collecting axis points and then calculating the current transform from those axis points...
bool transformIsDefined() const
True if enough Points were available to create a Transformation.
Command queue stack.
Definition CmdMediator.h:24
void iterateThroughCurvePointsAxes(const Functor2wRet< const QString &, const Point &, CallbackSearchReturn > &ftorWithCallback)
See Curve::iterateThroughCurvePoints, for the single axes curve.
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
CoordUnitsPolarTheta coordUnitsTheta() const
Get method for theta unit.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
CoordsType coordsType() const
Get method for coordinates type.
double originRadius() const
Get method for origin radius in polar mode.
DocumentModelCoords modelCoords() const
Get method for DocumentModelCoords.
Definition Document.cpp:611
DocumentAxesPointsRequired documentAxesPointsRequired() const
Get method for DocumentAxesPointsRequired.
Definition Document.cpp:326
Highest-level wrapper around other Formats classes.
void unformattedToFormatted(double xThetaUnformatted, double yRadiusUnformatted, const DocumentModelCoords &modelCoords, const MainWindowModel &mainWindowModel, QString &xThetaFormatted, QString &yRadiusFormatted, const Transformation &transformation) const
Convert unformatted numeric value to formatted string. Transformation is used to determine best resol...
Model for DlgSettingsMainWindow.
Affine transformation between screen and graph coordinates, based on digitized axis points.
void identity()
Identity transformation.
bool operator!=(const Transformation &other)
Inequality operator. This is marked as defined.
void printStream(QString indentation, QTextStream &str) const
Debugging method that supports print method of this class and printStream method of some other class(...
static QTransform calculateTransformFromLinearCartesianPoints(const QPointF &posFrom0, const QPointF &posFrom1, const QPointF &posFrom2, const QPointF &posTo0, const QPointF &posTo1, const QPointF &posTo2)
Calculate QTransform using from/to points that have already been adjusted for, when applicable,...
Transformation()
Default constructor. This is marked as undefined until the proper number of axis points are added.
void transformRawGraphToScreen(const QPointF &pointRaw, QPointF &pointScreen) const
Transform from raw graph coordinates to linear cartesian graph coordinates, then to screen coordinate...
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.
void transformLinearCartesianGraphToRawGraph(const QPointF &coordGraph, QPointF &coordScreen) const
Transform from linear cartesian graph coordinates to cartesian, polar, linear, log coordinates.
static double logToLinearCartesian(double xy)
Convert cartesian scaling from log to linear. Calling code is responsible for determining if this is ...
static QPointF cartesianOrPolarFromCartesian(const DocumentModelCoords &modelCoords, const QPointF &posGraphIn)
Output cartesian or polar coordinates from input cartesian coordinates. This is static for easier use...
Transformation & operator=(const Transformation &other)
Assignment operator.
static QPointF cartesianFromCartesianOrPolar(const DocumentModelCoords &modelCoords, const QPointF &posGraphIn)
Output cartesian coordinates from input cartesian or polar coordinates. This is static for easier use...
void coordTextForStatusBar(QPointF cursorScreen, QString &coordsScreen, QString &coordsGraph, QString &resolutionGraph)
Return string descriptions of cursor coordinates for status bar.
void resetOnLoad()
Reset, when loading a document after the first, to same state that first document was at when loaded.
DocumentModelCoords modelCoords() const
Get method for DocumentModelCoords.
QTransform transformMatrix() const
Get method for copying only, for the transform matrix.
static double logToLinearRadius(double r, double rCenter)
Convert radius scaling from log to linear. Calling code is responsible for determining if this is nec...
void transformRawGraphToLinearCartesianGraph(const QPointF &pointRaw, QPointF &pointLinearCartesian) const
Convert graph coordinates (linear or log, cartesian or polar) to linear cartesian coordinates.
bool transformIsDefined() const
Transform is defined when at least three axis points have been digitized.
void update(bool fileIsLoaded, const CmdMediator &cmdMediator, const MainWindowModel &modelMainWindow)
Update transform by iterating through the axis points.
void transformScreenToLinearCartesianGraph(const QPointF &pointScreen, QPointF &pointLinearCartesian) const
Transform screen coordinates to linear cartesian coordinates.
void transformLinearCartesianGraphToScreen(const QPointF &coordGraph, QPointF &coordScreen) const
Transform from linear cartesian graph coordinates to cartesian pixel screen coordinates.