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 {
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
78  switch (modelCoords.coordUnitsTheta())
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 ());
122  switch (modelCoords.coordUnitsTheta())
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 
154 void 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 
247 ostringstream &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 strOuter;
257 }
258 
259 void 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 
287 double 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 
296 void 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 
308 void 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 
402 void 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 
421 void Transformation::transformScreenToRawGraph (const QPointF &coordScreen,
422  QPointF &coordGraph) const
423 {
424  QPointF pointLinearCartesianGraph;
426  pointLinearCartesianGraph);
427  transformLinearCartesianGraphToRawGraph (pointLinearCartesianGraph,
428  coordGraph);
429 }
430 
431 void 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 
465 void 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 }
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:331
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:646
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 ...
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.