Engauge Digitizer 2
Loading...
Searching...
No Matches
GridLineFactory.cpp
Go to the documentation of this file.
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 "Document.h"
10#include "EngaugeAssert.h"
11#include "EnumsToQt.h"
12#include "GraphicsArcItem.h"
13#include "GridLineFactory.h"
14#include "GridLineLimiter.h"
15#include "GridLines.h"
16#include "GridLineStyle.h"
17#include "Logger.h"
18#include "MainWindowModel.h"
19#include <QGraphicsScene>
20#include <qmath.h>
21#include <QTextStream>
22#include "QtToString.h"
23#include "Transformation.h"
24
25const int Z_VALUE_IN_FRONT = 100;
26
27// To emphasize that the axis lines are still there, we make these checker somewhat transparent
28const double CHECKER_OPACITY = 0.6;
29
30const double PI = 3.1415926535;
31const double TWO_PI = 2.0 * PI;
32const double DEGREES_TO_RADIANS = PI / 180.0;
33const double RADIANS_TO_TICS = 5760 / TWO_PI;
34
36 const DocumentModelCoords &modelCoords) :
37 m_scene (scene),
38 m_pointRadius (0.0),
39 m_modelCoords (modelCoords),
40 m_isChecker (false)
41{
42 LOG4CPP_DEBUG_S ((*mainCat)) << "GridLineFactory::GridLineFactory";
43}
44
46 int pointRadius,
48 const DocumentModelCoords &modelCoords) :
49 m_scene (scene),
50 m_pointRadius (pointRadius),
51 m_pointsToIsolate (pointsToIsolate),
52 m_modelCoords (modelCoords),
53 m_isChecker (true)
54{
55 LOG4CPP_DEBUG_S ((*mainCat)) << "GridLineFactory::GridLineFactory"
56 << " pointRadius=" << pointRadius
57 << " pointsToIsolate=" << pointsToIsolate.count();
58}
59
60void GridLineFactory::bindItemToScene(QGraphicsItem *item) const
61{
62 LOG4CPP_DEBUG_S ((*mainCat)) << "GridLineFactory::bindItemToScene";
63
64 item->setOpacity (CHECKER_OPACITY);
65 item->setZValue (Z_VALUE_IN_FRONT);
66 if (m_isChecker) {
67 item->setToolTip (QObject::tr ("Axes checker. If this does not align with the axes, then the axes points should be checked"));
68 }
69
70 m_scene.addItem (item);
71}
72
74 double yFrom,
75 double xTo,
76 double yTo,
77 const Transformation &transformation)
78{
79 LOG4CPP_DEBUG_S ((*mainCat)) << "GridLineFactory::createGridLine"
80 << " xFrom=" << xFrom
81 << " yFrom=" << yFrom
82 << " xTo=" << xTo
83 << " yTo=" << yTo;
84
85 GridLine *gridLine = new GridLine ();
86
87 // Originally a complicated algorithm tried to intercept a straight line from (xFrom,yFrom) to (xTo,yTo). That did not work well since:
88 // 1) Calculations for mostly orthogonal cartesian coordinates worked less well with non-orthogonal polar coordinates
89 // 2) Ambiguity in polar coordinates between the shorter and longer paths between (theta0,radius) and (theta1,radius)
90 //
91 // Current algorithm breaks up the interval between (xMin,yMin) and (xMax,yMax) into many smaller pieces and stitches the
92 // desired pieces together. For straight lines in linear graphs this algorithm is very much overkill, but there is no significant
93 // penalty and this approach works in every situation
94
95 // Should give single-pixel resolution on most images, and 'good enough' resolution on extremely large images
96 const int NUM_STEPS = 1000;
97
98 bool stateSegmentIsActive = false;
100
101 // Loop through steps. Final step i=NUM_STEPS does final processing if a segment is active
102 for (int i = 0; i <= NUM_STEPS; i++) {
103
104 double s = double (i) / double (NUM_STEPS);
105
106 // Interpolate coordinates assuming normal linear scaling
107 double xGraph = (1.0 - s) * xFrom + s * xTo;
108 double yGraph = (1.0 - s) * yFrom + s * yTo;
109
110 // Replace interpolated coordinates using log scaling if appropriate, preserving the same ranges
111 if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LOG) {
112 xGraph = qExp ((1.0 - s) * qLn (xFrom) + s * qLn (xTo));
113 }
114 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
115 yGraph = qExp ((1.0 - s) * qLn (yFrom) + s * qLn (yTo));
116 }
117
121
122 double distanceToNearestPoint = minScreenDistanceFromPoints (pointScreen);
123 if ((distanceToNearestPoint < m_pointRadius) ||
124 (i == NUM_STEPS)) {
125
126 // Too close to point, so point is not included in side. Or this is the final iteration of the loop
128
129 // State transition
130 finishActiveGridLine (posStartScreen,
132 yFrom,
133 yTo,
134 transformation,
135 *gridLine);
136 stateSegmentIsActive = false;
137
138 }
139 } else {
140
141 // Outside point, so include point in side
143
144 // State transition
147
148 }
149 }
150 }
151
152 return gridLine;
153}
154
156 const Document &document,
157 const MainWindowModel &modelMainWindow,
158 const Transformation &transformation,
160{
161 // At a minimum the transformation must be defined. Also, there is a brief interval between the definition of
162 // the transformation and the initialization of modelGridDisplay (at which point this method gets called again) and
163 // we do not want to create grid lines during that brief interval
164 if (transformation.transformIsDefined() &&
165 modelGridDisplay.stable()) {
166
167 double startX = modelGridDisplay.startX ();
168 double startY = modelGridDisplay.startY ();
169 double stepX = modelGridDisplay.stepX ();
170 double stepY = modelGridDisplay.stepY ();
171 double stopX = modelGridDisplay.stopX ();
172 double stopY = modelGridDisplay.stopY ();
173
174 // Limit the number of grid lines. This is a noop if the limit is not exceeded
177 transformation,
178 m_modelCoords,
179 modelMainWindow,
180 modelGridDisplay,
181 startX,
182 stepX,
183 stopX);
184 gridLineLimiter.limitForYRadius (document,
185 transformation,
186 m_modelCoords,
187 modelMainWindow,
188 modelGridDisplay,
189 startY,
190 stepY,
191 stopY);
192
193 // Apply if possible
194 bool isLinearX = (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR);
195 bool isLinearY = (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR);
196 if (stepX > (isLinearX ? 0 : 1) &&
197 stepY > (isLinearY ? 0 : 1) &&
198 (isLinearX || (startX > 0)) &&
199 (isLinearY || (startY > 0))) {
200
201 QColor color (ColorPaletteToQColor (modelGridDisplay.paletteColor()));
202 QPen pen (QPen (color,
205
206 for (double x = startX; x <= stopX; (isLinearX ? x += stepX : x *= stepX)) {
207
208 GridLine *gridLine = createGridLine (x, startY, x, stopY, transformation);
209 gridLine->setPen (pen);
210 gridLines.add (gridLine);
211 }
212
213 for (double y = startY; y <= stopY; (isLinearY ? y += stepY : y *= stepY)) {
214
215 GridLine *gridLine = createGridLine (startX, y, stopX, y, transformation);
216 gridLine->setPen (pen);
217 gridLines.add (gridLine);
218 }
219 }
220 }
221}
222
223void GridLineFactory::createTransformAlign (const Transformation &transformation,
227 double &ellipseXAxis,
228 double &ellipseYAxis) const
229{
230 // LOG4CPP_INFO_S is below
231
232 // Compute a minimal transformation that aligns the graph x and y axes with the screen x and y axes. Specifically, shear,
233 // translation and rotation are allowed but not scaling. Scaling is bad since it messes up the line thickness of the drawn arc.
234 //
235 // Assumptions:
236 // 1) Keep the graph origin at the same screen coordinates
237 // 2) Keep the (+radius,0) the same pixel distance from the origin but moved to the same pixel row as the origin
238 // 3) Keep the (0,+radius) the same pixel distance from the origin but moved to the same pixel column as the origin
239
240 // Get (+radius,0) and (0,+radius) points
247
248 // Compute arc/ellipse parameters
252 deltaXRadiusY0.y () * deltaXRadiusY0.y ());
254 deltaX0YRadius.y () * deltaX0YRadius.y ());
255
256 // Compute the aligned coordinates, constrained by the rules listed above
259
266
267 // Use \n rather than endl to prevent compiler warning "nonnull argument t compared to null"
268 LOG4CPP_INFO_S ((*mainCat)) << "GridLineFactory::createTransformAlign"
269 << " transformation=" << QTransformToString (transformation.transformMatrix()).toLatin1().data() << "\n"
270 << " radiusLinearCartesian=" << radiusLinearCartesian
271 << " posXRadiusY0Screen=" << QPointFToString (posXRadiusY0Screen).toLatin1().data()
272 << " posX0YRadiusScreen=" << QPointFToString (posX0YRadiusScreen).toLatin1().data()
273 << " ellipseXAxis=" << ellipseXAxis
274 << " ellipseYAxis=" << ellipseYAxis
275 << " posXRadiusY0AlignedScreen=" << QPointFToString (posXRadiusY0AlignedScreen).toLatin1().data()
276 << " posX0YRadiusAlignedScreen=" << QPointFToString (posX0YRadiusAlignedScreen).toLatin1().data()
277 << " transformAlign=" << QTransformToString (transformAlign).toLatin1().data();
278}
279
280QGraphicsItem *GridLineFactory::ellipseItem (const Transformation &transformation,
282 const QPointF &posStartScreen,
283 const QPointF &posEndScreen) const
284{
285 // LOG4CPP_INFO_S is below
286
288
293
294 // Get the angles about the origin of the start and end points
297 if (angleEnd < angleStart) {
298 angleEnd += TWO_PI;
299 }
300 double angleSpan = angleEnd - angleStart;
301
302 // Get origin
306
307 LOG4CPP_INFO_S ((*mainCat)) << "GridLineFactory::ellipseItem"
308 << " radiusLinearCartesian=" << radiusLinearCartesian
309 << " posStartScreen=" << QPointFToString (posStartScreen).toLatin1().data()
310 << " posEndScreen=" << QPointFToString (posEndScreen).toLatin1().data()
311 << " posOriginScreen=" << QPointFToString (posOriginScreen).toLatin1().data()
312 << " angleStart=" << angleStart / DEGREES_TO_RADIANS
313 << " angleEnd=" << angleEnd / DEGREES_TO_RADIANS
314 << " transformation=" << transformation;
315
316 // Compute rotate/shear transform that aligns linear cartesian graph coordinates with screen coordinates, and ellipse parameters.
317 // Transform does not include scaling since that messes up the thickness of the drawn line, and does not include
318 // translation since that is not important
321 createTransformAlign (transformation,
327
328 // Create a circle in graph space with the specified radius
329 QRectF boundingRect (-1.0 * ellipseXAxis + posOriginScreen.x(),
330 -1.0 * ellipseYAxis + posOriginScreen.y(),
331 2 * ellipseXAxis,
332 2 * ellipseYAxis);
333 GraphicsArcItem *item = new GraphicsArcItem (boundingRect);
334 item->setStartAngle (qFloor (angleStart * RADIANS_TO_TICS));
335 item->setSpanAngle (qFloor (angleSpan * RADIANS_TO_TICS));
336
337 item->setTransform (transformAlign.transposed ().inverted ());
338
339 return item;
340}
341
342void GridLineFactory::finishActiveGridLine (const QPointF &posStartScreen,
343 const QPointF &posEndScreen,
344 double yFrom,
345 double yTo,
346 const Transformation &transformation,
347 GridLine &gridLine) const
348{
349 LOG4CPP_DEBUG_S ((*mainCat)) << "GridLineFactory::finishActiveGridLine"
350 << " posStartScreen=" << QPointFToString (posStartScreen).toLatin1().data()
351 << " posEndScreen=" << QPointFToString (posEndScreen).toLatin1().data()
352 << " yFrom=" << yFrom
353 << " yTo=" << yTo;
354
355 QGraphicsItem *item;
356 if ((m_modelCoords.coordsType() == COORDS_TYPE_POLAR) &&
357 (yFrom == yTo)) {
358
359 // Linear cartesian radius
361 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
363 m_modelCoords.originRadius());
364 } else {
365 radiusLinearCartesian -= m_modelCoords.originRadius();
366 }
367
368 // Draw along an arc since this is a side of constant radius, and we have polar coordinates
369 item = ellipseItem (transformation,
373
374 } else {
375
376 // Draw straight line
377 item = lineItem (posStartScreen,
379 }
380
381 gridLine.add (item);
382 bindItemToScene (item);
383}
384
385QGraphicsItem *GridLineFactory::lineItem (const QPointF &posStartScreen,
386 const QPointF &posEndScreen) const
387{
388 LOG4CPP_DEBUG_S ((*mainCat)) << "GridLineFactory::lineItem"
389 << " posStartScreen=" << QPointFToString (posStartScreen).toLatin1().data()
390 << " posEndScreen=" << QPointFToString (posEndScreen).toLatin1().data();
391
392 return new QGraphicsLineItem (QLineF (posStartScreen,
393 posEndScreen));
394}
395
396double GridLineFactory::minScreenDistanceFromPoints (const QPointF &posScreen)
397{
398 double minDistance = 0;
399 for (int i = 0; i < m_pointsToIsolate.count (); i++) {
400 const Point &pointCenter = m_pointsToIsolate.at (i);
401
402 double dx = posScreen.x() - pointCenter.posScreen().x();
403 double dy = posScreen.y() - pointCenter.posScreen().y();
404
405 double distance = qSqrt (dx * dx + dy * dy);
406 if (i == 0 || distance < minDistance) {
408 }
409 }
410
411 return minDistance;
412}
@ COORD_SCALE_LINEAR
Definition CoordScale.h:13
@ COORD_SCALE_LOG
Definition CoordScale.h:14
@ COORDS_TYPE_POLAR
Definition CoordsType.h:14
const int INNER_RADIUS_MIN
const double TWO_PI
QColor ColorPaletteToQColor(ColorPalette color)
Definition EnumsToQt.cpp:15
const double TWO_PI
const double DEGREES_TO_RADIANS
const double CHECKER_OPACITY
const double PI
const int Z_VALUE_IN_FRONT
const double RADIANS_TO_TICS
const Qt::PenStyle GRID_LINE_STYLE
const int GRID_LINE_WIDTH
log4cpp::Category * mainCat
Definition Logger.cpp:14
QString QTransformToString(const QTransform &transform)
QString QPointFToString(const QPointF &pos)
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
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.
Model for DlgSettingsGridDisplay and CmdSettingsGridDisplay.
double startX() const
Get method for x grid line lower bound (inclusive).
double stepX() const
Get method for x grid line increment.
double stopX() const
Get method for x grid line upper bound (inclusive).
double stopY() const
Get method for y grid line upper bound (inclusive).
bool stable() const
Get method for stable flag.
ColorPalette paletteColor() const
Get method for color.
double stepY() const
Get method for y grid line increment.
double startY() const
Get method for y grid line lower bound (inclusive).
Storage of one imported image and the data attached to that image.
Definition Document.h:42
Draw an arc as an ellipse but without lines from the center to the start and end points.
void createGridLinesForEvenlySpacedGrid(const DocumentModelGridDisplay &modelGridDisplay, const Document &document, const MainWindowModel &modelMainWindow, const Transformation &transformation, GridLines &gridLines)
Create a rectangular (cartesian) or annular (polar) grid of evenly spaced grid lines.
GridLine * createGridLine(double xFrom, double yFrom, double xTo, double yTo, const Transformation &transformation)
Create grid line, either along constant X/theta or constant Y/radius side.
GridLineFactory(QGraphicsScene &scene, const DocumentModelCoords &modelCoords)
Simple constructor for general use (i.e. not by Checker)
Limit the number of grid lines so a bad combination of start/step/stop value will not lead to extreme...
void limitForXTheta(const Document &document, const Transformation &transformation, const DocumentModelCoords &modelCoords, const MainWindowModel &modelMainWindow, const DocumentModelGridDisplay &modelGrid, double &startX, double &stepX, double &stopX) const
Limit step value for x/theta coordinate. This is a noop if the maximum grid line limit in MainWindowM...
Single grid line drawn a straight or curved line.
Definition GridLine.h:21
Container class for GridLine objects.
Definition GridLines.h:19
Model for DlgSettingsMainWindow.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition Point.h:26
Affine transformation between screen and graph coordinates, based on digitized axis points.
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 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.
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...
bool transformIsDefined() const
Transform is defined when at least three axis points have been digitized.
void transformLinearCartesianGraphToScreen(const QPointF &coordGraph, QPointF &coordScreen) const
Transform from linear cartesian graph coordinates to cartesian pixel screen coordinates.
#define LOG4CPP_INFO_S(logger)
Definition convenience.h:18
#define LOG4CPP_DEBUG_S(logger)
Definition convenience.h:20