• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 15291801018

28 May 2025 04:52AM UTC coverage: 11.931% (-0.02%) from 11.951%
15291801018

push

github

alex-w
Added new set of navigational stars (XIX century)

0 of 6 new or added lines in 2 files covered. (0.0%)

14124 existing lines in 74 files now uncovered.

14635 of 122664 relevant lines covered (11.93%)

18291.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/plugins/LensDistortionEstimator/src/LensDistortionEstimator.cpp
1
/*
2
 * Copyright (C) 2023 Ruslan Kabatsayev
3
 *
4
 * This program is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU General Public License
6
 * as published by the Free Software Foundation; either version 2
7
 * of the License, or (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
17
 */
18

19
#include "LensDistortionEstimator.hpp"
20
#include "LensDistortionEstimatorDialog.hpp"
21
#include "StelApp.hpp"
22
#include "StelGui.hpp"
23
#include "StelCore.hpp"
24
#include "StelSRGB.hpp"
25
#include "StelUtils.hpp"
26
#include "StelPainter.hpp"
27
#include "StelFileMgr.hpp"
28
#include "StelGuiItems.hpp"
29
#include "StelModuleMgr.hpp"
30
#include "StelObjectMgr.hpp"
31
#include "StelTextureMgr.hpp"
32
#include "StelTranslator.hpp"
33

34
#include <QDebug>
35

36
namespace
37
{
38
constexpr float POINT_MARKER_RADIUS = 7;
39
constexpr int FS_QUAD_COORDS_PER_VERTEX = 2;
40
constexpr int SKY_VERTEX_ATTRIB_INDEX = 0;
41
constexpr char defaultImageAxesColor[] = "1,0.5,0";
42
constexpr char defaultPointMarkerColor[] = "1,0,0";
43
constexpr char defaultSelectedPointMarkerColor[] = "0,0.75,0";
44
constexpr char defaultProjectionCenterMarkerColor[] = "0.25,0.5,0.5";
45
}
46

47
StelModule* LensDistortionEstimatorStelPluginInterface::getStelModule() const
×
48
{
49
        return new LensDistortionEstimator();
×
50
}
51

52
StelPluginInfo LensDistortionEstimatorStelPluginInterface::getPluginInfo() const
×
53
{
54
        // Allow to load the resources when used as a static plugin
55
        Q_INIT_RESOURCE(LensDistortionEstimator);
×
56

57
        StelPluginInfo info;
×
58
        info.id = "LensDistortionEstimator";
×
59
        info.displayedName = N_("Lens distortion estimator");
×
60
        info.authors = "Ruslan Kabatsayev";
×
61
        info.contact = STELLARIUM_DEV_URL;
×
62
        info.description = N_("Estimates lens distortion by letting the user match stars to a photo and computing a fit.");
×
63
        info.version = LENSDISTORTIONESTIMATOR_PLUGIN_VERSION;
×
64
        info.license = LENSDISTORTIONESTIMATOR_PLUGIN_LICENSE;
×
65
        return info;
×
66
}
×
67

68
LensDistortionEstimator::LensDistortionEstimator()
×
69
        : conf_(StelApp::getInstance().getSettings())
×
70
{
71
        setObjectName("LensDistortionEstimator");
×
72
}
×
73

74
LensDistortionEstimator::~LensDistortionEstimator()
×
75
{
76
}
×
77

78
double LensDistortionEstimator::getCallOrder(StelModuleActionName actionName) const
×
79
{
80
        if(actionName==StelModule::ActionDraw)
×
81
                return StelApp::getInstance().getModuleMgr().getModule("StarMgr")->getCallOrder(actionName)-1;
×
82
        if(actionName==StelModule::ActionHandleMouseClicks)
×
83
                return -11;
×
84
        return 0;
×
85
}
86

87
Vec2d LensDistortionEstimator::screenPointToImagePoint(const int x, const int y) const
×
88
{
89
        const auto prj = StelApp::getInstance().getCore()->getProjection(StelCore::FrameAltAz, StelCore::RefractionOff);
×
90
        Vec3d point = Vec3d(0,0,0);
×
91
        prj->unProject(x, y, point);
×
92

93
        const auto viewDir = point;
×
94
        const auto imageCenterShift = dialog_->imageCenterShift();
×
95
        const auto imageSmallerSideFoV = M_PI/180 * dialog_->imageSmallerSideFoV();
×
96
        const auto projectionCenterDir = dialog_->projectionCenterDir();
×
97
        const auto imageUpDir = dialog_->imageUpDir();
×
98
        const double W = imageTex_->width();
×
99
        const double H = imageTex_->height();
×
100
        const double minSize = std::min(W,H);
×
101
        const Vec2d normCoordToTexCoordCoefs = (minSize-1)*Vec2d(1./W,1./H);
×
102

103
        using namespace std;
104
        const auto imageRightDir = normalize(projectionCenterDir ^ imageUpDir);
×
105
        const auto R = acos(clamp(dot(viewDir, projectionCenterDir), -1., 1.));
×
106
        const auto angle = atan2(dot(viewDir, imageUpDir),
×
107
                                 dot(viewDir, imageRightDir));
108
        const auto normalizedR = tan(R) / tan(imageSmallerSideFoV/2);
×
109
        const auto distortedR = dialog_->applyDistortion(normalizedR);
×
110
        const Vec2d centeredTexCoord = normCoordToTexCoordCoefs * (distortedR * Vec2d(cos(angle), sin(angle)) + imageCenterShift);
×
111
        const Vec2d posInImage((0.5 + 0.5 * centeredTexCoord[0]) * W - 0.5,
×
112
                               (0.5 - 0.5 * centeredTexCoord[1]) * H - 0.5);
×
113
        return posInImage;
×
114
}
×
115

116
void LensDistortionEstimator::handleMouseClicks(QMouseEvent* event)
×
117
{
118
        if(!dialog_ || !dialog_->initialized() || !imageTex_)
×
119
        {
120
                event->setAccepted(false);
×
121
                return;
×
122
        }
123

124
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
125
        const double x = event->position().x(), y = event->position().y();
×
126
#else
127
        const double x = event->x(), y = event->y();
128
#endif
129
        const auto posInImage = screenPointToImagePoint(x,y);
×
130
        if(dialog_->isPickingAPoint())
×
131
        {
132
                draggingImage_ = false; // Just in case
×
133

134
                if(event->type() == QEvent::MouseButtonPress)
×
135
                {
136
                        event->setAccepted(true);
×
137
                        return;
×
138
                }
139
                if(event->type() != QEvent::MouseButtonRelease)
×
140
                {
141
                        event->setAccepted(false);
×
142
                        return;
×
143
                }
144

145
                const auto objs = StelApp::getInstance().getStelObjectMgr().getSelectedObject();
×
146
                if(objs.isEmpty())
×
147
                {
148
                        qWarning() << "Picked a point while nothing was selected... dropping the result";
×
149
                        event->setAccepted(false);
×
150
                        return;
×
151
                }
152
                if(!objs.last())
×
153
                {
154
                        qWarning() << "NULL object is selected... dropping the result of picking";
×
155
                        event->setAccepted(false);
×
156
                        return;
×
157
                }
158
                dialog_->resetImagePointPicking();
×
159
                dialog_->registerImagePoint(*objs[0], posInImage);
×
160
                event->setAccepted(true);
×
161
                return;
×
162
        }
×
163

164
        if(draggingImage_ && event->type() == QEvent::MouseButtonRelease)
×
165
        {
166
                draggingImage_ = false;
×
167
                imgPointToRotateAbout_ = screenPointToImagePoint(x,y);
×
168
                event->setAccepted(true);
×
169
                return;
×
170
        }
171

172
        // If the modifiers are not what we use for dragging, or the button is wrong, we have nothing to do
173
        const auto modifiers = qApp->keyboardModifiers() & (Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier);
×
174
        if(modifiers != (Qt::ShiftModifier|Qt::ControlModifier))
×
175
        {
176
                event->setAccepted(false);
×
177
                return;
×
178
        }
179

180
        // To start dragging the image, the cursor must be inside
181
        if(posInImage[0] < -0.5 || posInImage[0] > imageTex_->width() - 0.5 ||
×
182
           posInImage[1] < -0.5 || posInImage[1] > imageTex_->height() - 0.5)
×
183
        {
184
                event->setAccepted(false);
×
185
                return;
×
186
        }
187

188
        if(event->button() == Qt::LeftButton)
×
189
        {
190
                dragStartProjCenterElevation_ = dialog_->projectionCenterElevation();
×
191
                dragStartProjCenterAzimuth_ = dialog_->projectionCenterAzimuth();
×
192
                dragStartImgFieldRotation_ = dialog_->imageFieldRotation();
×
193
                dragStartPoint_ = QPointF(x,y);
×
194
                draggingImage_ = true;
×
195
                rotatingImage_ = false;
×
196
                event->setAccepted(true);
×
197
        }
198
        else if(event->button() == Qt::RightButton)
×
199
        {
200
                dragStartImgSmallerSideFoV_ = dialog_->imageSmallerSideFoV();
×
201
                dragStartProjCenterElevation_ = dialog_->projectionCenterElevation();
×
202
                dragStartProjCenterAzimuth_ = dialog_->projectionCenterAzimuth();
×
203
                dragStartImgFieldRotation_ = dialog_->imageFieldRotation();
×
204
                dragStartPoint_ = QPointF(x,y);
×
205
                draggingImage_ = false;
×
206
                rotatingImage_ = true;
×
207
                event->setAccepted(true);
×
208
        }
209
}
210

211
void LensDistortionEstimator::dragImage(const Vec2d& src, const Vec2d& dst)
×
212
{
213
        // These are used for computations, so initialize them to the starting values
214
        dialog_->setProjectionCenterElevation(dragStartProjCenterElevation_);
×
215
        dialog_->setProjectionCenterAzimuth(dragStartProjCenterAzimuth_);
×
216
        dialog_->setImageFieldRotation(dragStartImgFieldRotation_);
×
217

218
        using namespace std;
219
        // Trying to preserve the screen-up direction of the image
220
        const auto prj = StelApp::getInstance().getCore()->getProjection(StelCore::FrameAltAz, StelCore::RefractionOff);
×
221
        Vec3d srcDir1(0,0,0), srcDir2(0,0,0);
×
222
        Vec3d dstDir1(0,0,0), dstDir2(0,0,0);
×
223
        prj->unProject(src[0],src[1]  , srcDir1);
×
224
        prj->unProject(src[0],src[1]-1, srcDir2);
×
225
        prj->unProject(dst[0],dst[1]  , dstDir1);
×
226
        prj->unProject(dst[0],dst[1]-1, dstDir2);
×
227

228
              auto crossDst = dstDir1 ^ dstDir2;
×
229
        const auto crossSrc = srcDir1 ^ srcDir2;
×
230

231
        // Make sure angle between destination vectors is the same as between the source ones
232
        const Mat3d rotToMatchAngle = Mat4d::rotation(normalize(crossDst),
×
233
                                                      asin(crossSrc.norm())-asin(crossDst.norm())).upper3x3();
×
234
        dstDir2 = rotToMatchAngle * dstDir2;
×
235
        // Recompute cross product
236
        crossDst = dstDir1 ^ dstDir2;
×
237

238
        // Find the matrix to rotate two points to the desired directions in space
239
        const auto preRotSrc = Mat3d( srcDir1[0], srcDir1[1], srcDir1[2],
×
240
                                      srcDir2[0], srcDir2[1], srcDir2[2],
×
241
                                     crossSrc[0],crossSrc[1],crossSrc[2]);
×
242
        const auto preRotDst = Mat3d( dstDir1[0],  dstDir1[1],  dstDir1[2],
×
243
                                      dstDir2[0],  dstDir2[1],  dstDir2[2],
×
244
                                     crossDst[0], crossDst[1], crossDst[2]);
×
245
        const auto rotator = preRotDst * preRotSrc.inverse();
×
246

247
        const auto origUpDir = dialog_->imageUpDir();
×
248
        const auto origCenterDir = dialog_->projectionCenterDir();
×
249
        const auto newCenterDir = normalize(rotator * origCenterDir);
×
250
        const auto elevation = asin(clamp(newCenterDir[2], -1.,1.));
×
251
        const auto azimuth = atan2(newCenterDir[1], -newCenterDir[0]);
×
252
        dialog_->setProjectionCenterAzimuth(180/M_PI * azimuth);
×
253
        dialog_->setProjectionCenterElevation(180/M_PI * elevation);
×
254

255
        const auto origFromCenterToTop = normalize(origCenterDir + 1e-8 * origUpDir);
×
256
        const auto newFromCenterToTop = normalize(rotator * origFromCenterToTop);
×
257
        // Desired up direction
258
        const auto upDirNew = normalize(newFromCenterToTop - newCenterDir);
×
259
        // Renewed up direction takes into account the new center direction but not the desired orientation yet
260
        dialog_->setImageFieldRotation(0);
×
261
        const auto renewedUpDir = dialog_->imageUpDir();
×
262
        const auto upDirCross = renewedUpDir ^ upDirNew;
×
263
        const auto upDirDot = dot(renewedUpDir, upDirNew);
×
264
        const auto dirSign = dot(upDirCross, newCenterDir) > 0 ? -1. : 1.;
×
265
        const auto upDirSinAngle = dirSign * (dirSign * upDirCross).norm();
×
266
        const auto upDirCosAngle = upDirDot;
×
267
        const auto fieldRotation = atan2(upDirSinAngle, upDirCosAngle);
×
268
        dialog_->setImageFieldRotation(180/M_PI * fieldRotation);
×
269
}
×
270

271
void LensDistortionEstimator::rotateImage(const Vec2d& dragSrcPoint, const Vec2d& dragDstPoint)
×
272
{
273
        // These are used for computations, so initialize them to the starting values
274
        dialog_->setImageSmallerSideFoV(dragStartImgSmallerSideFoV_);
×
275
        dialog_->setProjectionCenterElevation(dragStartProjCenterElevation_);
×
276
        dialog_->setProjectionCenterAzimuth(dragStartProjCenterAzimuth_);
×
277
        dialog_->setImageFieldRotation(dragStartImgFieldRotation_);
×
278

279
        using namespace std;
280

281
        const auto srcPointInImg = screenPointToImagePoint(dragSrcPoint[0],dragSrcPoint[1]);
×
282
        auto srcDir = dialog_->computeImagePointDir(srcPointInImg);
×
283
        const auto desiredFixedDir = dialog_->computeImagePointDir(imgPointToRotateAbout_);
×
284
        const auto prj = StelApp::getInstance().getCore()->getProjection(StelCore::FrameAltAz, StelCore::RefractionOff);
×
285
        Vec3d dstDir(0,0,0);
×
286
        prj->unProject(dragDstPoint[0],dragDstPoint[1], dstDir);
×
287

288
        // Find the correct FoV to make angle between the two image points correct
289
        const auto desiredAngleBetweenDirs = acos(clamp(dot(desiredFixedDir, dstDir), -1., 1.));
×
290
        double fovMax = 0.999*M_PI;
×
291
        double fovMin = 0;
×
292
        double fov = (fovMin + fovMax) / 2.;
×
293
        for(int n = 0; n < 53 && fovMin != fovMax; ++n)
×
294
        {
295
                const auto angleBetweenImagePoints = dialog_->computeAngleBetweenImagePoints(imgPointToRotateAbout_,
×
296
                                                                                             srcPointInImg, fov);
297
                if(angleBetweenImagePoints > desiredAngleBetweenDirs)
×
298
                        fovMax = fov;
×
299
                else
300
                        fovMin = fov;
×
301
                fov = (fovMin + fovMax) / 2.;
×
302
        }
303
        dialog_->setImageSmallerSideFoV(180/M_PI * fov);
×
304

305
        // Recompute the directions after FoV update
306
        const auto scaledImageFixedDir = dialog_->computeImagePointDir(imgPointToRotateAbout_);
×
307
        srcDir = dialog_->computeImagePointDir(srcPointInImg);
×
308

309
        // Find the matrix to rotate two image points to corresponding directions in space
310
        const auto crossDst = desiredFixedDir ^ dstDir;
×
311
        const auto crossSrc = scaledImageFixedDir ^ srcDir;
×
312
        const auto preRotSrc = Mat3d(scaledImageFixedDir[0], scaledImageFixedDir[1], scaledImageFixedDir[2],
×
313
                                                  srcDir[0],              srcDir[1],              srcDir[2],
×
314
                                                crossSrc[0],            crossSrc[1],            crossSrc[2]);
×
315
        const auto preRotDst = Mat3d(desiredFixedDir[0], desiredFixedDir[1], desiredFixedDir[2],
×
316
                                              dstDir[0],          dstDir[1],          dstDir[2],
×
317
                                            crossDst[0],        crossDst[1],        crossDst[2]);
×
318
        const auto rotator = preRotDst * preRotSrc.inverse();
×
319

320
        const auto origUpDir = dialog_->imageUpDir();
×
321
        const auto origCenterDir = dialog_->projectionCenterDir();
×
322
        const auto newCenterDir = normalize(rotator * origCenterDir);
×
323
        const auto elevation = asin(clamp(newCenterDir[2], -1.,1.));
×
324
        const auto azimuth = atan2(newCenterDir[1], -newCenterDir[0]);
×
325
        dialog_->setProjectionCenterAzimuth(180/M_PI * azimuth);
×
326
        dialog_->setProjectionCenterElevation(180/M_PI * elevation);
×
327

328
        const auto origFromCenterToTop = normalize(origCenterDir + 1e-8 * origUpDir);
×
329
        const auto newFromCenterToTop = normalize(rotator * origFromCenterToTop);
×
330
        // Desired up direction
331
        const auto upDirNew = normalize(newFromCenterToTop - newCenterDir);
×
332
        // Renewed up direction takes into account the new center direction but not the desired orientation yet
333
        dialog_->setImageFieldRotation(0);
×
334
        const auto renewedUpDir = dialog_->imageUpDir();
×
335
        const auto upDirCross = renewedUpDir ^ upDirNew;
×
336
        const auto upDirDot = dot(renewedUpDir, upDirNew);
×
337
        const auto dirSign = dot(upDirCross, newCenterDir) > 0 ? -1. : 1.;
×
338
        const auto upDirSinAngle = dirSign * (dirSign * upDirCross).norm();
×
339
        const auto upDirCosAngle = upDirDot;
×
340
        const auto fieldRotation = atan2(upDirSinAngle, upDirCosAngle);
×
341
        dialog_->setImageFieldRotation(180/M_PI * fieldRotation);
×
342
}
×
343

344
bool LensDistortionEstimator::handleMouseMoves(const int x, const int y, const Qt::MouseButtons buttons)
×
345
{
346
        if(!dialog_ || !dialog_->initialized() || !imageTex_) return false;
×
347
        if(draggingImage_ && (buttons & Qt::LeftButton))
×
348
        {
349
                dragImage(Vec2d(dragStartPoint_.x(),dragStartPoint_.y()), Vec2d(x,y));
×
350
                return true;
×
351
        }
352
        if(rotatingImage_ && (buttons & Qt::RightButton))
×
353
        {
354
                rotateImage(Vec2d(dragStartPoint_.x(),dragStartPoint_.y()), Vec2d(x,y));
×
355
                return true;
×
356
        }
357

358
        draggingImage_ = false;
×
359
        rotatingImage_ = false;
×
360
        return false;
×
361
}
362

363
void LensDistortionEstimator::setupCurrentVAO()
×
364
{
365
        auto& gl = *QOpenGLContext::currentContext()->functions();
×
366
        vbo_->bind();
×
367
        gl.glVertexAttribPointer(0, FS_QUAD_COORDS_PER_VERTEX, GL_FLOAT, false, 0, 0);
×
368
        vbo_->release();
×
369
        gl.glEnableVertexAttribArray(SKY_VERTEX_ATTRIB_INDEX);
×
370
}
×
371

372
void LensDistortionEstimator::bindVAO()
×
373
{
374
        if(vao_->isCreated())
×
375
                vao_->bind();
×
376
        else
377
                setupCurrentVAO();
×
378
}
×
379

380
void LensDistortionEstimator::releaseVAO()
×
381
{
382
        if(vao_->isCreated())
×
383
        {
384
                vao_->release();
×
385
        }
386
        else
387
        {
388
                auto& gl = *QOpenGLContext::currentContext()->functions();
×
389
                gl.glDisableVertexAttribArray(SKY_VERTEX_ATTRIB_INDEX);
×
390
        }
391
}
×
392

393
void LensDistortionEstimator::init()
×
394
{
395
        dialog_.reset(new LensDistortionEstimatorDialog(this));
×
396

397
        if(!conf_->childGroups().contains("LensDistortionEstimator"))
×
398
                restoreDefaultSettings();
×
399
        loadSettings();
×
400

401
        addAction("action_LensDistortionEstimator_togglePointPicking", N_("Lens Distortion Estimator"),
×
402
                  N_("Toggle pick-a-point mode"), this,
403
                  [this]{ dialog_->togglePointPickingMode(); }, "Ctrl+V");
×
404
        addAction("actionShow_LensDistortionEstimator_dialog", N_("Lens Distortion Estimator"),
×
405
                  N_("Show lens distortion estimator dialog"), "dialogVisible", "Ctrl+E");
406
        // Make sure that opening/closing the dialog by other means than dialogVisible property is reflected on this property
407
        connect(dialog_.get(), &StelDialog::visibleChanged, this, [this](const bool visible)
×
408
                {
409
                    if(visible != dialogVisible)
×
410
                    {
411
                        dialogVisible = visible;
×
412
                        emit dialogToggled(visible);
×
413
                    }
414
                });
×
415

416
        toolbarButton_ = new StelButton(nullptr,
×
417
                                        QPixmap(":/LensDistortionEstimator/bt_LensDistortionEstimator_On.png"),
×
418
                                        QPixmap(":/LensDistortionEstimator/bt_LensDistortionEstimator_Off.png"),
×
419
                                        QPixmap(":/graphicGui/miscGlow32x32.png"),
×
420
                                        "actionShow_LensDistortionEstimator_dialog",
421
                                        false,
422
                                        nullptr);
×
423
        if(const auto gui = dynamic_cast<StelGui*>(StelApp::getInstance().getGui()))
×
424
        {
425
                gui->getButtonBar()->addButton(toolbarButton_, "065-pluginsGroup");
×
426
        }
427

428
        auto& gl = *QOpenGLContext::currentContext()->functions();
×
429

430
        vbo_.reset(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
×
431
        vbo_->create();
×
432
        vbo_->bind();
×
433
        const GLfloat vertices[]=
×
434
        {
435
                // full screen quad
436
                -1, -1,
437
                 1, -1,
438
                -1,  1,
439
                 1,  1,
440
        };
441
        gl.glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
×
442
        vbo_->release();
×
443

444
        vao_.reset(new QOpenGLVertexArrayObject);
×
445
        vao_->create();
×
446
        bindVAO();
×
447
        setupCurrentVAO();
×
448
        releaseVAO();
×
449
}
×
450

451
bool LensDistortionEstimator::configureGui(const bool show)
×
452
{
453
        if(show)
×
454
        {
455
                dialog_->setVisible(show);
×
456
                dialog_->showSettingsPage();
×
457
        }
458
        return true;
×
459
}
460

461
QCursor LensDistortionEstimator::getPointPickCursor() const
×
462
{
463
        const int size = 32;
×
464
        const QPointF center(size/2-0.5,size/2-0.5);
×
465
        QPixmap pixmap(size,size);
×
466
        pixmap.fill(Qt::transparent);
×
467

468
        QPainter painter(&pixmap);
×
469

470
        const float scale = StelApp::getInstance().getDevicePixelsPerPixel();
×
471
        const float radius = 0.5 + std::min((size-1)/2.f, POINT_MARKER_RADIUS * scale);
×
472

473
        painter.setRenderHint(QPainter::Antialiasing);
×
474
        const auto lineWidth = scale * std::clamp(radius/7., 1., 2.5);
×
475

476
        for(float lineScale : {2.f, 1.f})
×
477
        {
478
                painter.setPen(QPen(lineScale==1 ? Qt::black : Qt::white, lineWidth*lineScale));
×
479
                painter.drawEllipse(center, radius, radius);
×
480

481
                const float x = center.x(), y = center.y();
×
482
                const QPointF endpoints[] = {{x-radius+lineWidth/2, y},
×
483
                                             {x-radius*0.7f, y},
×
484
                                             {x+radius-lineWidth/2, y},
×
485
                                             {x+radius*0.7f, y},
×
486
                                             {x, y-radius+lineWidth/2},
×
487
                                             {x, y-radius*0.7f},
×
488
                                             {x, y+radius-lineWidth/2},
×
489
                                             {x, y+radius*0.7f}};
×
490
                painter.drawLines(endpoints, std::size(endpoints)/2);
×
491
        }
492

493
        return QCursor(pixmap, center.x(), center.y());
×
494
}
×
495

496
void LensDistortionEstimator::renderPointMarker(StelPainter& sPainter, const float x, const float y,
×
497
                                                float radius, const Vec3f& color) const
498
{
499
        const auto scale = sPainter.getProjector()->getDevicePixelsPerPixel();
×
500
        radius *= scale;
×
501

502
        sPainter.setBlending(true);
×
503
        sPainter.setLineSmooth(true);
×
504
        sPainter.setLineWidth(scale * std::clamp(radius/7, 1.f, 2.5f));
×
505
        sPainter.setColor(color);
×
506

507
        sPainter.drawCircle(x, y, radius);
×
508

509
        sPainter.enableClientStates(true);
×
510
        const float vertexData[] = {x-radius     , y,
×
511
                                    x-radius*0.5f, y,
×
512
                                    x+radius     , y,
×
513
                                    x+radius*0.5f, y,
×
514
                                    x, y-radius     ,
×
515
                                    x, y-radius*0.5f,
×
516
                                    x, y+radius     ,
×
517
                                    x, y+radius*0.5f};
×
518
        const auto vertCount = std::size(vertexData) / 2;
×
519
        sPainter.setVertexPointer(2, GL_FLOAT, vertexData);
×
520
        sPainter.drawFromArray(StelPainter::Lines, vertCount, 0, false);
×
521
        sPainter.enableClientStates(false);
×
522
}
×
523

524
void LensDistortionEstimator::renderProjectionCenterMarker(StelPainter& sPainter, const float x, const float y,
×
525
                                                             float radius, const Vec3f& color) const
526
{
527
        const auto scale = sPainter.getProjector()->getDevicePixelsPerPixel();
×
528
        radius *= scale;
×
529

530
        sPainter.setBlending(true);
×
531
        sPainter.setLineSmooth(true);
×
532
        sPainter.setLineWidth(scale * std::clamp(radius/7, 1.f, 2.5f));
×
533
        sPainter.setColor(color);
×
534

535
        sPainter.drawCircle(x, y, radius);
×
536

537
        sPainter.enableClientStates(true);
×
538
        const auto size = radius / std::sqrt(2.f);
×
539
        const float vertexData[] = {x-size     , y-size     ,
×
540
                                    x-size*0.1f, y-size*0.1f,
×
541
                                    x+size     , y+size     ,
×
542
                                    x+size*0.1f, y+size*0.1f,
×
543
                                    x+size     , y-size     ,
×
544
                                    x+size*0.1f, y-size*0.1f,
×
545
                                    x-size     , y+size     ,
×
546
                                    x-size*0.1f, y+size*0.1f};
×
547
        const auto vertCount = std::size(vertexData) / 2;
×
548
        sPainter.setVertexPointer(2, GL_FLOAT, vertexData);
×
549
        sPainter.drawFromArray(StelPainter::Lines, vertCount, 0, false);
×
550
        sPainter.enableClientStates(false);
×
551
}
×
552

553
void LensDistortionEstimator::draw(StelCore* core)
×
554
{
555
        if(!imageEnabled) return;
×
556

557
        if(dialog_->imageChanged())
×
558
        {
559
                const auto image = dialog_->image();
×
560
                if(image.isNull()) return;
×
561

562
                imageTex_.reset(new QOpenGLTexture(image));
×
563
                imageTex_->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
×
564
                imageTex_->setWrapMode(QOpenGLTexture::ClampToBorder);
×
565
                imgPointToRotateAbout_ = Vec2d(imageTex_->width(),imageTex_->height()) / 2.;
×
566
                dialog_->resetImageChangeFlag();
×
567
        }
×
568
        if(!imageTex_) return;
×
569

570
        imageTex_->bind(0);
×
571

572
        const auto projector = core->getProjection(StelCore::FrameAltAz, StelCore::RefractionOff);
×
573

574
        if(!renderProgram_ || !prevProjector_ || !projector->isSameProjection(*prevProjector_))
×
575
        {
576
                renderProgram_.reset(new QOpenGLShaderProgram);
×
577
                prevProjector_ = projector;
×
578

579
                const auto vert =
580
                        StelOpenGL::globalShaderPrefix(StelOpenGL::VERTEX_SHADER) +
×
581
                        R"(
×
582
ATTRIBUTE highp vec3 vertex;
583
VARYING highp vec3 ndcPos;
584
void main()
585
{
586
        gl_Position = vec4(vertex, 1.);
587
        ndcPos = vertex;
588
}
589
)";
590
                bool ok = renderProgram_->addShaderFromSourceCode(QOpenGLShader::Vertex, vert);
×
591
                if(!renderProgram_->log().isEmpty())
×
592
                        qWarning().noquote() << "LensDistortionEstimator: warnings while compiling vertex shader:\n" << renderProgram_->log();
×
593
                if(!ok) return;
×
594

595
                const auto frag =
596
                        StelOpenGL::globalShaderPrefix(StelOpenGL::FRAGMENT_SHADER) +
×
597
                        projector->getUnProjectShader() +
×
598
                        makeSRGBUtilsShader() +
×
599
                        R"(
×
600
VARYING highp vec3 ndcPos;
601
uniform sampler2D imageTex;
602
uniform mat4 projectionMatrixInverse;
603
uniform vec2 normCoordToTexCoordCoefs;
604
uniform float distNormalizationCoef;
605
uniform vec3 projectionCenterDir;
606
uniform vec3 imageUpDir;
607
uniform bool imageAxesEnabled;
608
uniform vec3 imageAxesColor;
609
uniform vec2 imageCenterShift;
610
uniform vec4 bgToSubtract;
611
uniform float brightness;
612
#define MODEL_POLY3 0
613
#define MODEL_POLY5 1
614
#define MODEL_PTLENS 2
615
uniform int distortionModel;
616
uniform float distortionTerm1;
617
uniform float distortionTerm2;
618
uniform float distortionTerm3;
619
uniform float maxUndistortedR;
620
void main()
621
{
622
        const float PI = 3.14159265;
623
        vec4 winPos = projectionMatrixInverse * vec4(ndcPos, 1);
624
        bool unprojOK = false;
625
        vec3 modelPos = unProject(winPos.x, winPos.y, unprojOK).xyz;
626
        vec3 viewDir = normalize(modelPos);
627

628
        vec3 imageRightDir = normalize(cross(projectionCenterDir, imageUpDir));
629
        float viewProjCenterDot = dot(viewDir, projectionCenterDir);
630
        float viewProjCenterCross = length(cross(viewDir, projectionCenterDir));
631
        float angle = atan(dot(viewDir, imageUpDir), dot(viewDir, imageRightDir));
632
        // Undistorted distance
633
        float ru = distNormalizationCoef * tan(atan(viewProjCenterCross,viewProjCenterDot));
634

635
        float ru2 = ru*ru;
636
        float ru3 = ru2*ru;
637
        float ru4 = ru2*ru2;
638
        float k1 = distortionTerm1, k2 = distortionTerm2;
639
        float a = distortionTerm1, b = distortionTerm2, c = distortionTerm3;
640
        float distortedR = 0.;
641
        if(distortionModel == MODEL_POLY3)
642
                distortedR = ru*(1.-k1+k1*ru*ru);
643
        else if(distortionModel == MODEL_POLY5)
644
                distortedR = ru*(1+k1*ru2+k2*ru4);
645
        else if(distortionModel == MODEL_PTLENS)
646
                distortedR = ru*(a*ru3+b*ru2+c*ru+1-a-b-c);
647

648
        vec2 centeredTexCoord = normCoordToTexCoordCoefs * (distortedR * vec2(cos(angle), sin(angle)) + imageCenterShift);
649

650
        vec3 texColor = texture2D(imageTex, 0.5 + 0.5 * centeredTexCoord).rgb;
651
        texColor = linearToSRGB(brightness * (srgbToLinear(texColor) - srgbToLinear(bgToSubtract.rgb))
652
                                                                       /
653
                                              max(1. - srgbToLinear(bgToSubtract.rgb), vec3(1./255.)));
654
        bool oppositeToImgDir = viewProjCenterDot < 0.;
655
        FRAG_COLOR = oppositeToImgDir ? vec4(0) : vec4(texColor, 1);
656

657
        if(imageAxesEnabled && !oppositeToImgDir)
658
        {
659
                vec2 sGrad = vec2(dFdx(centeredTexCoord.s), dFdy(centeredTexCoord.s));
660
                vec2 tGrad = vec2(dFdx(centeredTexCoord.t), dFdy(centeredTexCoord.t));
661
                if(abs(centeredTexCoord.s) <= 1. && abs(centeredTexCoord.t) <= 1. &&
662
                    (centeredTexCoord.t*centeredTexCoord.t < dot(tGrad,tGrad) ||
663
                     centeredTexCoord.s*centeredTexCoord.s < dot(sGrad,sGrad)))
664
                {
665
                        FRAG_COLOR = vec4(imageAxesColor,1);
666
                }
667
        }
668

669
        if(!unprojOK || ru > maxUndistortedR)
670
        {
671
                FRAG_COLOR = vec4(0);
672
        }
673
}
674
)";
675
                ok = renderProgram_->addShaderFromSourceCode(QOpenGLShader::Fragment, frag);
×
676
                if(!renderProgram_->log().isEmpty())
×
677
                        qWarning().noquote() << "LensDistortionEstimator: warnings while compiling fragment shader:\n" << renderProgram_->log();
×
678

679
                if(!ok) return;
×
680

681
                renderProgram_->bindAttributeLocation("vertex", SKY_VERTEX_ATTRIB_INDEX);
×
682

683
                if(!StelPainter::linkProg(renderProgram_.get(), "LensDistortionEstimator image render program"))
×
684
                        return;
×
685

686
                renderProgram_->bind();
×
687
                shaderVars.imageTex = renderProgram_->uniformLocation("imageTex");
×
688
                shaderVars.imageUpDir = renderProgram_->uniformLocation("imageUpDir");
×
689
                shaderVars.projectionCenterDir = renderProgram_->uniformLocation("projectionCenterDir");
×
690
                shaderVars.distNormalizationCoef = renderProgram_->uniformLocation("distNormalizationCoef");
×
691
                shaderVars.normCoordToTexCoordCoefs = renderProgram_->uniformLocation("normCoordToTexCoordCoefs");
×
692
                shaderVars.imageAxesEnabled = renderProgram_->uniformLocation("imageAxesEnabled");
×
693
                shaderVars.imageAxesColor = renderProgram_->uniformLocation("imageAxesColor");
×
694
                shaderVars.imageCenterShift = renderProgram_->uniformLocation("imageCenterShift");
×
695
                shaderVars.bgToSubtract = renderProgram_->uniformLocation("bgToSubtract");
×
696
                shaderVars.imageBrightness = renderProgram_->uniformLocation("brightness");
×
697
                shaderVars.distortionModel = renderProgram_->uniformLocation("distortionModel");
×
698
                shaderVars.distortionTerm1 = renderProgram_->uniformLocation("distortionTerm1");
×
699
                shaderVars.distortionTerm2 = renderProgram_->uniformLocation("distortionTerm2");
×
700
                shaderVars.distortionTerm3 = renderProgram_->uniformLocation("distortionTerm3");
×
701
                shaderVars.maxUndistortedR = renderProgram_->uniformLocation("maxUndistortedR");
×
702
                shaderVars.projectionMatrixInverse = renderProgram_->uniformLocation("projectionMatrixInverse");
×
703
                renderProgram_->release();
×
704
        }
×
705
        if(!renderProgram_ || !renderProgram_->isLinked())
×
706
                return;
×
707

708
        renderProgram_->bind();
×
709

710
        const int imageTexSampler = 0;
×
711
        imageTex_->bind(imageTexSampler);
×
712
        renderProgram_->setUniformValue(shaderVars.imageTex, imageTexSampler);
×
713
        renderProgram_->setUniformValue(shaderVars.imageUpDir, dialog_->imageUpDir().toQVector());
×
714
        renderProgram_->setUniformValue(shaderVars.projectionCenterDir, dialog_->projectionCenterDir().toQVector());
×
715
        const float W = imageTex_->width();
×
716
        const float H = imageTex_->height();
×
717
        const float minSize = std::min(W,H);
×
718
        const double imageSmallerSideFoV = M_PI/180 * dialog_->imageSmallerSideFoV();
×
719
        renderProgram_->setUniformValue(shaderVars.distNormalizationCoef,
×
720
                                        float(1. / (std::tan(imageSmallerSideFoV / 2.))));
×
721
        renderProgram_->setUniformValue(shaderVars.imageAxesEnabled, imageAxesEnabled);
×
722
        renderProgram_->setUniformValue(shaderVars.imageAxesColor, imageAxesColor.toQVector());
×
723
        renderProgram_->setUniformValue(shaderVars.imageCenterShift, dialog_->imageCenterShift().toQVector());
×
724
        renderProgram_->setUniformValue(shaderVars.bgToSubtract, dialog_->bgToSubtract());
×
725
        renderProgram_->setUniformValue(shaderVars.imageBrightness, float(dialog_->imageBrightness()));
×
726
        renderProgram_->setUniformValue(shaderVars.distortionModel, int(dialog_->distortionModel()));
×
727
        renderProgram_->setUniformValue(shaderVars.normCoordToTexCoordCoefs, (minSize-1)*QVector2D(1./W,1./H));
×
728
        renderProgram_->setUniformValue(shaderVars.distortionTerm1, float(dialog_->distortionTerm1()));
×
729
        renderProgram_->setUniformValue(shaderVars.distortionTerm2, float(dialog_->distortionTerm2()));
×
730
        renderProgram_->setUniformValue(shaderVars.distortionTerm3, float(dialog_->distortionTerm3()));
×
731
        renderProgram_->setUniformValue(shaderVars.maxUndistortedR, float(dialog_->maxUndistortedR()));
×
732
        renderProgram_->setUniformValue(shaderVars.projectionMatrixInverse, projector->getProjectionMatrix().toQMatrix().inverted());
×
733
        projector->setUnProjectUniforms(*renderProgram_);
×
734

735
        auto& gl = *QOpenGLContext::currentContext()->functions();
×
736

737
        gl.glEnable(GL_BLEND);
×
738
        gl.glBlendFunc(GL_ONE,GL_ONE); // allow colored sky background
×
739
        bindVAO();
×
740
        gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
×
741
        releaseVAO();
×
742
        gl.glDisable(GL_BLEND);
×
743

744
        if(!pointMarkersEnabled && !projectionCenterMarkerEnabled)
×
745
                return;
×
746

747
        StelPainter sPainter(core->getProjection(StelCore::FrameAltAz, StelCore::RefractionOff));
×
748
        if(pointMarkersEnabled)
×
749
        {
750
                for(const auto& status : dialog_->imagePointDirections())
×
751
                {
752
                        const auto color = status.selected ? selectedPointMarkerColor : pointMarkerColor;
×
753
                        Vec3d win;
×
754
                        if(sPainter.getProjector()->project(status.direction, win))
×
755
                                renderPointMarker(sPainter, win[0], win[1], POINT_MARKER_RADIUS, color);
×
756
                }
×
757
        }
758

759
        if(projectionCenterMarkerEnabled)
×
760
        {
761
                const auto dir = dialog_->projectionCenterDir();
×
762
                Vec3d win;
×
763
                if(sPainter.getProjector()->project(dir, win))
×
764
                        renderProjectionCenterMarker(sPainter, win[0], win[1], 12, projectionCenterMarkerColor);
×
765
        }
766
}
×
767

768
void LensDistortionEstimator::setImageAxesColor(const Vec3f& color)
×
769
{
770
        if(imageAxesColor == color) return;
×
771

772
        imageAxesColor = color;
×
773
        conf_->setValue("LensDistortionEstimator/image_axes_color", color.toStr());
×
774
        emit imageAxesColorChanged(color);
×
775
}
776

777
void LensDistortionEstimator::setPointMarkerColor(const Vec3f& color)
×
778
{
779
        if(pointMarkerColor == color) return;
×
780

781
        pointMarkerColor = color;
×
782
        conf_->setValue("LensDistortionEstimator/point_marker_color", color.toStr());
×
783
        emit pointMarkerColorChanged(color);
×
784
}
785

786
void LensDistortionEstimator::setSelectedPointMarkerColor(const Vec3f& color)
×
787
{
788
        if(selectedPointMarkerColor == color) return;
×
789

790
        selectedPointMarkerColor = color;
×
791
        conf_->setValue("LensDistortionEstimator/selected_point_marker_color", color.toStr());
×
792
        emit selectedPointMarkerColorChanged(color);
×
793
}
794

795
void LensDistortionEstimator::setProjectionCenterMarkerColor(const Vec3f& color)
×
796
{
797
        if(projectionCenterMarkerColor == color) return;
×
798

799
        projectionCenterMarkerColor = color;
×
800
        conf_->setValue("LensDistortionEstimator/center_of_projection_marker_color", color.toStr());
×
801
        emit projectionCenterMarkerColorChanged(color);
×
802
}
803

804
void LensDistortionEstimator::setPointMarkersEnabled(const bool enable)
×
805
{
806
        if(pointMarkersEnabled == enable) return;
×
807

808
        pointMarkersEnabled = enable;
×
809
        conf_->setValue("LensDistortionEstimator/mark_picked_points", enable);
×
810
        emit pointMarkersToggled(enable);
×
811
}
812

813
void LensDistortionEstimator::setProjectionCenterMarkerEnabled(const bool enable)
×
814
{
815
        if(projectionCenterMarkerEnabled == enable) return;
×
816

817
        projectionCenterMarkerEnabled = enable;
×
818
        conf_->setValue("LensDistortionEstimator/mark_center_of_projection", enable);
×
819
        emit pointMarkersToggled(enable);
×
820
}
821

822
void LensDistortionEstimator::setImgPosResetOnLoadingEnabled(const bool enable)
×
823
{
824
        if(imgPosResetOnLoadingEnabled == enable) return;
×
825

826
        imgPosResetOnLoadingEnabled = enable;
×
827
        conf_->setValue("LensDistortionEstimator/reset_img_pos_on_loading", enable);
×
828
        emit pointMarkersToggled(enable);
×
829
}
830

831
void LensDistortionEstimator::setImageAxesEnabled(const bool enable)
×
832
{
833
        if(imageAxesEnabled == enable) return;
×
834

835
        imageAxesEnabled = enable;
×
836
        conf_->setValue("LensDistortionEstimator/show_image_axes", enable);
×
837
        emit imageAxesToggled(enable);
×
838
}
839

840
void LensDistortionEstimator::setImageEnabled(const bool enable)
×
841
{
842
        if(imageEnabled == enable) return;
×
843

844
        imageEnabled = enable;
×
845
        conf_->setValue("LensDistortionEstimator/show_image", enable);
×
846
        emit imageToggled(enable);
×
847
}
848

UNCOV
849
void LensDistortionEstimator::setDialogVisible(const bool enable)
×
850
{
UNCOV
851
        if(dialogVisible == enable) return;
×
852

853
        dialogVisible = enable;
×
854
        dialog_->setVisible(enable);
×
855
        if(enable)
×
856
                dialog_->showComputationPage();
×
857
}
858

859
void LensDistortionEstimator::loadSettings()
×
860
{
UNCOV
861
        setImageEnabled(conf_->value("LensDistortionEstimator/show_image", true).toBool());
×
862
        setImageAxesEnabled(conf_->value("LensDistortionEstimator/show_image_axes", false).toBool());
×
UNCOV
863
        setPointMarkersEnabled(conf_->value("LensDistortionEstimator/mark_picked_points", true).toBool());
×
UNCOV
864
        setProjectionCenterMarkerEnabled(conf_->value("LensDistortionEstimator/mark_center_of_projection", false).toBool());
×
865
        setImgPosResetOnLoadingEnabled(conf_->value("LensDistortionEstimator/reset_img_pos_on_loading", true).toBool());
×
UNCOV
866
        imageAxesColor = Vec3f(conf_->value("LensDistortionEstimator/image_axes_color", defaultImageAxesColor).toString());
×
867
        pointMarkerColor = Vec3f(conf_->value("LensDistortionEstimator/point_marker_color", defaultPointMarkerColor).toString());
×
UNCOV
868
        selectedPointMarkerColor = Vec3f(conf_->value("LensDistortionEstimator/selected_point_marker_color", defaultSelectedPointMarkerColor).toString());
×
869
        projectionCenterMarkerColor = Vec3f(conf_->value("LensDistortionEstimator/center_of_projection_marker_color", defaultProjectionCenterMarkerColor).toString());
×
870
}
×
871

872
void LensDistortionEstimator::restoreDefaultSettings()
×
873
{
874
        // Remove the old values...
875
        conf_->remove("LensDistortionEstimator");
×
876
        // ...load the default values...
UNCOV
877
        loadSettings();
×
878
        // But this doesn't save the colors, so:
UNCOV
879
        conf_->beginGroup("LensDistortionEstimator");
×
UNCOV
880
        conf_->setValue("image_axes_color", defaultImageAxesColor);
×
UNCOV
881
        conf_->setValue("point_marker_color", defaultPointMarkerColor);
×
UNCOV
882
        conf_->setValue("selected_point_marker_color", defaultSelectedPointMarkerColor);
×
UNCOV
883
        conf_->setValue("center_of_projection_marker_color", defaultProjectionCenterMarkerColor);
×
UNCOV
884
        conf_->endGroup();
×
UNCOV
885
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc