Engauge Digitizer  2
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;
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  xThetaFormatted,
195  yRadiusFormatted,
196  *this);
197 
198  coordsGraph = QString ("(%1, %2)")
199  .arg (xThetaFormatted)
200  .arg (yRadiusFormatted);
201 
202  resolutionsGraph = QString ("(%1, %2)")
203  .arg (resolutionXGraph, UNCONSTRAINED_FIELD_WIDTH, FORMAT, PRECISION_DIGITS)
204  .arg (resolutionYGraph, UNCONSTRAINED_FIELD_WIDTH, FORMAT, PRECISION_DIGITS);
205 
206  } else {
207 
208  coordsGraph = "<font color=\"red\">Need more axis points</font>";
209  resolutionsGraph = coordsGraph;
210 
211  }
212  }
213 }
214 
216 {
217  // Initialize assuming points (0,0) (1,0) (0,1)
218  m_transformIsDefined = true;
219 
220  QTransform ident;
221  m_transform = ident;
222 }
223 
225 {
226  return qLn (xy);
227 }
228 
230  double rCenter)
231 {
232  return qLn (r) - qLn (rCenter);
233 }
234 
236 {
237  return m_modelCoords;
238 }
239 
240 const Transformation &operator<<(ostringstream &strOuter,
241  const Transformation &transformation)
242 {
243  QString text;
244  QTextStream strInner (&text);
245  transformation.printStream ("", strInner);
246 
247  strOuter << text.toLatin1().data ();
248 
249  return transformation;
250 }
251 
252 void Transformation::printStream (QString indentation,
253  QTextStream &str) const
254 {
255  str << "Transformation\n";
256 
257  indentation += INDENTATION_DELTA;
258 
259  if (m_transformIsDefined) {
260 
261  str << indentation << "affine=" << (m_transform.isAffine() ? "yes" : "no") << " matrix=("
262  << m_transform.m11() << ", " << m_transform.m12() << ", " << m_transform.m13() << ", "
263  << m_transform.m21() << ", " << m_transform.m22() << ", " << m_transform.m23() << ", "
264  << m_transform.m31() << ", " << m_transform.m32() << ", " << m_transform.m33() << ")";
265 
266  } else {
267 
268  str << indentation << "undefined";
269 
270  }
271 }
272 
274 {
275  LOG4CPP_INFO_S ((*mainCat)) << "Transformation::resetOnLoad";
276 
277  m_transformIsDefined = false;
278 }
279 
280 double Transformation::roundOffSmallValues (double value, double range)
281 {
282  if (qAbs (value) < range / qPow (10.0, PRECISION_DIGITS)) {
283  value = 0.0;
284  }
285 
286  return value;
287 }
288 
289 void Transformation::setModelCoords (const DocumentModelCoords &modelCoords)
290 {
291  m_modelCoords = modelCoords;
292 }
293 
294 void Transformation::transformLinearCartesianGraphToRawGraph (const QPointF &pointLinearCartesianGraph,
295  QPointF &pointRawGraph) const
296 {
297  // WARNING - the code in this method must mirror the code in transformRawGraphToLinearCartesianGraph. In
298  // other words, making a change here without a corresponding change there will produce a bug
299 
300  pointRawGraph = pointLinearCartesianGraph;
301 
302  // Apply polar coordinates if appropriate
303  if (m_modelCoords.coordsType() == COORDS_TYPE_POLAR) {
304  pointRawGraph = cartesianOrPolarFromCartesian (m_modelCoords,
305  pointRawGraph);
306  }
307 
308  // Apply linear offset to radius if appropriate
309  if ((m_modelCoords.coordsType() == COORDS_TYPE_POLAR) &&
310  (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR)) {
311  pointRawGraph.setY (pointRawGraph.y() + m_modelCoords.originRadius());
312  }
313 
314  // Apply log scaling if appropriate
315  if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG) {
316  pointRawGraph.setX (qExp (pointRawGraph.x()));
317  }
318 
319  if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
320  double offset;
321  if (m_modelCoords.coordsType() == COORDS_TYPE_CARTESIAN) {
322  // Cartesian
323  offset = ZERO_OFFSET_AFTER_LOG;
324  } else {
325  // Polar radius
326  offset = m_modelCoords.originRadius();
327  }
328 
329  pointRawGraph.setY (qExp (pointRawGraph.y() + qLn (offset)));
330  }
331 }
332 
334  QPointF &coordScreen) const
335 {
336  ENGAUGE_ASSERT (m_transformIsDefined);
337 
338  coordScreen = m_transform.inverted ().transposed ().map (coordGraph);
339 }
340 
342 {
343  return m_transform;
344 }
345 
347  QPointF &pointLinearCartesian) const
348 {
349  // WARNING - the code in this method must mirror the code in transformLinearCartesianGraphToRawGraph. In
350  // other words, making a change here without a corresponding change there will produce a bug
351 
352  double x = pointRaw.x();
353  double y = pointRaw.y();
354 
355  // Apply linear offset to radius if appropriate
356  if ((m_modelCoords.coordsType() == COORDS_TYPE_POLAR) &&
357  (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR)) {
358  y -= m_modelCoords.originRadius();
359  }
360 
361  // Apply log scaling if appropriate
362  if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG) {
363  x = logToLinearCartesian (x);
364  }
365 
366  if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
367  if (m_modelCoords.coordsType() == COORDS_TYPE_POLAR) {
368  y = logToLinearRadius (y,
369  m_modelCoords.originRadius());
370  } else {
371  y = logToLinearRadius (y,
372  ZERO_OFFSET_AFTER_LOG);
373  }
374  }
375 
376  // Apply polar coordinates if appropriate. Note range coordinate has just been transformed if it has log scaling
377  if (m_modelCoords.coordsType() == COORDS_TYPE_POLAR) {
378  QPointF pointCart = cartesianFromCartesianOrPolar (m_modelCoords,
379  QPointF (x, y));
380  x = pointCart.x();
381  y = pointCart.y();
382  }
383 
384  pointLinearCartesian.setX (x);
385  pointLinearCartesian.setY (y);
386 }
387 
388 void Transformation::transformRawGraphToScreen (const QPointF &pointRaw,
389  QPointF &pointScreen) const
390 {
391  QPointF pointLinearCartesianGraph;
392 
394  pointLinearCartesianGraph);
395  transformLinearCartesianGraphToScreen (pointLinearCartesianGraph,
396  pointScreen);
397 }
398 
400  QPointF &coordGraph) const
401 {
402  ENGAUGE_ASSERT (m_transformIsDefined);
403 
404  coordGraph = m_transform.transposed ().map (coordScreen);
405 }
406 
407 void Transformation::transformScreenToRawGraph (const QPointF &coordScreen,
408  QPointF &coordGraph) const
409 {
410  QPointF pointLinearCartesianGraph;
412  pointLinearCartesianGraph);
413  transformLinearCartesianGraphToRawGraph (pointLinearCartesianGraph,
414  coordGraph);
415 }
416 
417 void Transformation::update (bool fileIsLoaded,
418  const CmdMediator &cmdMediator)
419 {
420  LOG4CPP_DEBUG_S ((*mainCat)) << "Transformation::update";
421 
422  if (!fileIsLoaded) {
423 
424  m_transformIsDefined = false;
425 
426  } else {
427 
428  setModelCoords (cmdMediator.document().modelCoords());
429 
430  CallbackUpdateTransform ftor (m_modelCoords);
431 
432  Functor2wRet<const QString &, const Point&, CallbackSearchReturn> ftorWithCallback = functor_ret (ftor,
434  cmdMediator.iterateThroughCurvePointsAxes (ftorWithCallback);
435 
436  if (ftor.transformIsDefined ()) {
437 
438  updateTransformFromMatrices (ftor.matrixScreen(),
439  ftor.matrixGraph());
440  }
441  }
442 }
443 
444 void Transformation::updateTransformFromMatrices (const QTransform &matrixScreen,
445  const QTransform &matrixGraph)
446 {
447  // LOG4CPP_INFO_S is below
448 
449  m_transformIsDefined = true;
450 
451  // Extract points from 3x3 matrices
452  QPointF pointGraphRaw0 (matrixGraph.m11(),
453  matrixGraph.m21());
454  QPointF pointGraphRaw1 (matrixGraph.m12(),
455  matrixGraph.m22());
456  QPointF pointGraphRaw2 (matrixGraph.m13(),
457  matrixGraph.m23());
458 
459  QPointF pointGraphLinearCart0, pointGraphLinearCart1, pointGraphLinearCart2;
461  pointGraphLinearCart0);
463  pointGraphLinearCart1);
465  pointGraphLinearCart2);
466 
467  // Calculate the transform
468  m_transform = calculateTransformFromLinearCartesianPoints (QPointF (matrixScreen.m11(), matrixScreen.m21()),
469  QPointF (matrixScreen.m12(), matrixScreen.m22()),
470  QPointF (matrixScreen.m13(), matrixScreen.m23()),
471  QPointF (pointGraphLinearCart0.x(), pointGraphLinearCart0.y()),
472  QPointF (pointGraphLinearCart1.x(), pointGraphLinearCart1.y()),
473  QPointF (pointGraphLinearCart2.x(), pointGraphLinearCart2.y()));
474 
475  // Logging
476  QTransform matrixGraphLinear (pointGraphLinearCart0.x(),
477  pointGraphLinearCart1.x(),
478  pointGraphLinearCart2.x(),
479  pointGraphLinearCart0.y(),
480  pointGraphLinearCart1.y(),
481  pointGraphLinearCart2.y(),
482  1.0,
483  1.0);
484 
485  QPointF pointScreenRoundTrip0, pointScreenRoundTrip1, pointScreenRoundTrip2;
486  transformRawGraphToScreen (pointGraphRaw0,
487  pointScreenRoundTrip0);
488  transformRawGraphToScreen (pointGraphRaw1,
489  pointScreenRoundTrip1);
490  transformRawGraphToScreen (pointGraphRaw2,
491  pointScreenRoundTrip2);
492 
493  QPointF pointScreen0 (matrixScreen.m11(),
494  matrixScreen.m21());
495  QPointF pointScreen1 (matrixScreen.m12(),
496  matrixScreen.m22());
497  QPointF pointScreen2 (matrixScreen.m13(),
498  matrixScreen.m23());
499 
500  LOG4CPP_INFO_S ((*mainCat)) << "Transformation::updateTransformFromMatrices"
501  << " matrixScreen=\n" << QTransformToString (matrixScreen).toLatin1().data () << " "
502  << " matrixGraphRaw=\n" << QTransformToString (matrixGraph).toLatin1().data() << " "
503  << " matrixGraphLinear=\n" << QTransformToString (matrixGraphLinear).toLatin1().data() << "\n"
504  << " originalScreen0=" << QPointFToString (pointScreen0).toLatin1().data() << "\n"
505  << " originalScreen1=" << QPointFToString (pointScreen1).toLatin1().data() << "\n"
506  << " originalScreen2=" << QPointFToString (pointScreen2).toLatin1().data() << "\n"
507  << " roundTripScreen0=" << QPointFToString (pointScreenRoundTrip0).toLatin1().data() << "\n"
508  << " roundTripScreen1=" << QPointFToString (pointScreenRoundTrip1).toLatin1().data() << "\n"
509  << " roundTripScreen2=" << QPointFToString (pointScreenRoundTrip2).toLatin1().data() << "\n";
510 }
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:447
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Definition: CmdMediator.cpp:61
void unformattedToFormatted(double xThetaUnformatted, double yRadiusUnformatted, const DocumentModelCoords &modelCoords, QString &xThetaFormatted, QString &yRadiusFormatted, const Transformation &transformation) const
Convert unformatted numeric value to formatted string. Transformation is used to determine best resol...
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.
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.
CoordsType coordsType() const
Get method for coordinates type.
void update(bool fileIsLoaded, const CmdMediator &cmdMediator)
Update transform by iterating through the axis points.
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 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.