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