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