Marble

SphericalScanlineTextureMapper.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2007 Torsten Rahn <tackat@kde.org>
4// SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
5//
6
7#include "SphericalScanlineTextureMapper.h"
8
9#include <cmath>
10
11#include <QRunnable>
12#include <qmath.h>
13
14#include "GeoDataPolygon.h"
15#include "GeoPainter.h"
16#include "MarbleDebug.h"
17#include "MathHelper.h"
18#include "Quaternion.h"
19#include "ScanlineTextureMapperContext.h"
20#include "StackedTile.h"
21#include "StackedTileLoader.h"
22#include "TextureColorizer.h"
23#include "ViewportParams.h"
24
25using namespace Marble;
26
27class SphericalScanlineTextureMapper::RenderJob : public QRunnable
28{
29public:
30 RenderJob(StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom);
31
32 void run() override;
33
34private:
35 StackedTileLoader *const m_tileLoader;
36 const int m_tileLevel;
37 QImage *const m_canvasImage;
38 const ViewportParams *const m_viewport;
39 const MapQuality m_mapQuality;
40 int const m_yTop;
41 int const m_yBottom;
42};
43
44SphericalScanlineTextureMapper::RenderJob::RenderJob(StackedTileLoader *tileLoader,
45 int tileLevel,
46 QImage *canvasImage,
47 const ViewportParams *viewport,
48 MapQuality mapQuality,
49 int yTop,
50 int yBottom)
51 : m_tileLoader(tileLoader)
52 , m_tileLevel(tileLevel)
53 , m_canvasImage(canvasImage)
54 , m_viewport(viewport)
55 , m_mapQuality(mapQuality)
56 , m_yTop(yTop)
57 , m_yBottom(yBottom)
58{
59}
60
61SphericalScanlineTextureMapper::SphericalScanlineTextureMapper(StackedTileLoader *tileLoader)
62 : TextureMapperInterface()
63 , m_tileLoader(tileLoader)
64 , m_radius(0)
65 , m_threadPool()
66{
67}
68
69void SphericalScanlineTextureMapper::mapTexture(GeoPainter *painter,
70 const ViewportParams *viewport,
71 int tileZoomLevel,
72 const QRect &dirtyRect,
73 TextureColorizer *texColorizer)
74{
75 if (m_canvasImage.size() != viewport->size() || m_radius != viewport->radius()) {
76 const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat(viewport);
77
78 if (m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat) {
79 m_canvasImage = QImage(viewport->size(), optimalFormat);
80 }
81
82 if (!viewport->mapCoversViewport()) {
83 m_canvasImage.fill(0);
84 }
85
86 m_radius = viewport->radius();
87 m_repaintNeeded = true;
88 }
89
90 if (m_repaintNeeded) {
91 mapTexture(viewport, tileZoomLevel, painter->mapQuality());
92
93 if (texColorizer) {
94 texColorizer->colorize(&m_canvasImage, viewport, painter->mapQuality());
95 }
96
97 m_repaintNeeded = false;
98 }
99
100 const int radius = viewport->radius();
101
102 QRect rect(viewport->width() / 2 - radius, viewport->height() / 2 - radius, 2 * radius, 2 * radius);
103 rect = rect.intersected(dirtyRect);
104 painter->drawImage(rect, m_canvasImage, rect);
105}
106
107void SphericalScanlineTextureMapper::mapTexture(const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality)
108{
109 // Reset backend
110 m_tileLoader->resetTilehash();
111
112 // Initialize needed constants:
113
114 const int imageHeight = m_canvasImage.height();
115 const qint64 radius = viewport->radius();
116
117 // Calculate the actual y-range of the map on the screen
118 const int skip = (mapQuality == LowQuality) ? 1 : 0;
119 const int yTop = (imageHeight / 2 - radius >= 0) ? imageHeight / 2 - radius : 0;
120 const int yBottom = (yTop == 0) ? imageHeight - skip : yTop + radius + radius - skip;
121
122 const int numThreads = m_threadPool.maxThreadCount();
123 const int yStep = qCeil(qreal(yBottom - yTop) / qreal(numThreads));
124 for (int i = 0; i < numThreads; ++i) {
125 const int yStart = yTop + i * yStep;
126 const int yEnd = qMin(yBottom, yTop + (i + 1) * yStep);
127 QRunnable *const job = new RenderJob(m_tileLoader, tileZoomLevel, &m_canvasImage, viewport, mapQuality, yStart, yEnd);
128 m_threadPool.start(job);
129 }
130
131 m_threadPool.waitForDone();
132
133 m_tileLoader->cleanupTilehash();
134}
135
136void SphericalScanlineTextureMapper::RenderJob::run()
137{
138 const int imageHeight = m_canvasImage->height();
139 const int imageWidth = m_canvasImage->width();
140 const qint64 radius = m_viewport->radius();
141 const qreal inverseRadius = 1.0 / (qreal)(radius);
142
143 const bool interlaced = (m_mapQuality == LowQuality);
144 const bool highQuality = (m_mapQuality == HighQuality || m_mapQuality == PrintQuality);
145 const bool printQuality = (m_mapQuality == PrintQuality);
146
147 // Evaluate the degree of interpolation
148 const int n = ScanlineTextureMapperContext::interpolationStep(m_viewport, m_mapQuality);
149
150 // Calculate north pole position to decrease pole distortion later on
151 Quaternion northPole = Quaternion::fromSpherical(0.0, M_PI * 0.5);
152 northPole.rotateAroundAxis(m_viewport->planetAxis().inverse());
153 const int northPoleX = imageWidth / 2 + (int)(radius * northPole.v[Q_X]);
154 const int northPoleY = imageHeight / 2 - (int)(radius * northPole.v[Q_Y]);
155
156 // Calculate axis matrix to represent the planet's rotation.
157 matrix planetAxisMatrix;
158 m_viewport->planetAxis().toMatrix(planetAxisMatrix);
159
160 // initialize needed variables that are modified during texture mapping:
161
162 ScanlineTextureMapperContext context(m_tileLoader, m_tileLevel);
163 qreal lon = 0.0;
164 qreal lat = 0.0;
165
166 // Scanline based algorithm to texture map a sphere
167 for (int y = m_yTop; y < m_yBottom; ++y) {
168 // Evaluate coordinates for the 3D position vector of the current pixel
169 const qreal qy = inverseRadius * (qreal)(imageHeight / 2 - y);
170 const qreal qr = 1.0 - qy * qy;
171
172 // rx is the radius component in x direction
173 const int rx = (int)sqrt((qreal)(radius * radius - ((y - imageHeight / 2) * (y - imageHeight / 2))));
174
175 // Calculate the actual x-range of the map within the current scanline.
176 //
177 // If the circular border of the earth disk is still visible then xLeft
178 // equals the scanline position of the most left pixel that gets covered
179 // by the earth disk. In terms of math this equals the half image width minus
180 // the radius component on the current scanline in x direction ("rx").
181 //
182 // If the zoom factor is high enough then the whole screen gets covered
183 // by the earth and the border of the earth disk isn't visible anymore.
184 // In that situation xLeft equals zero.
185 // For xRight the situation is similar.
186
187 const int xLeft = (imageWidth / 2 - rx > 0) ? imageWidth / 2 - rx : 0;
188 const int xRight = (imageWidth / 2 - rx > 0) ? xLeft + rx + rx : imageWidth;
189
190 QRgb *scanLine = (QRgb *)(m_canvasImage->scanLine(y)) + xLeft;
191
192 const int xIpLeft = (imageWidth / 2 - rx > 0) ? n * (int)(xLeft / n + 1) : 1;
193 const int xIpRight = (imageWidth / 2 - rx > 0) ? n * (int)(xRight / n - 1) : n * (int)(xRight / n - 1) + 1;
194
195 // Decrease pole distortion due to linear approximation ( y-axis )
196 bool crossingPoleArea = false;
197 if (northPole.v[Q_Z] > 0 && northPoleY - (n * 0.75) <= y && northPoleY + (n * 0.75) >= y) {
198 crossingPoleArea = true;
199 }
200
201 int ncount = 0;
202
203 for (int x = xLeft; x < xRight; ++x) {
204 // Prepare for interpolation
205
206 const int leftInterval = xIpLeft + ncount * n;
207
208 bool interpolate = false;
209 if (x >= xIpLeft && x <= xIpRight) {
210 // Decrease pole distortion due to linear approximation ( x-axis )
211 // mDebug() << QStringLiteral("NorthPole X: %1, LeftInterval: %2").arg( northPoleX ).arg( leftInterval );
212 if (crossingPoleArea && northPoleX >= leftInterval + n && northPoleX < leftInterval + 2 * n && x < leftInterval + 3 * n) {
213 interpolate = false;
214 } else {
215 x += n - 1;
216 interpolate = !printQuality;
217 ++ncount;
218 }
219 } else
220 interpolate = false;
221
222 // Evaluate more coordinates for the 3D position vector of
223 // the current pixel.
224 const qreal qx = (qreal)(x - imageWidth / 2) * inverseRadius;
225 const qreal qr2z = qr - qx * qx;
226 const qreal qz = (qr2z > 0.0) ? sqrt(qr2z) : 0.0;
227
228 // Create Quaternion from vector coordinates and rotate it
229 // around globe axis
230 Quaternion qpos(0.0, qx, qy, qz);
231 qpos.rotateAroundAxis(planetAxisMatrix);
232
233 qpos.getSpherical(lon, lat);
234 // mDebug() << QStringLiteral("lon: %1 lat: %2").arg(lon).arg(lat);
235 // Approx for n-1 out of n pixels within the boundary of
236 // xIpLeft to xIpRight
237
238 if (interpolate) {
239 if (highQuality)
240 context.pixelValueApproxF(lon, lat, scanLine, n);
241 else
242 context.pixelValueApprox(lon, lat, scanLine, n);
243
244 scanLine += (n - 1);
245 }
246
247 // Comment out the pixelValue line and run Marble if you want
248 // to understand the interpolation:
249
250 // Uncomment the crossingPoleArea line to check precise
251 // rendering around north pole:
252
253 // if ( !crossingPoleArea )
254 if (x < imageWidth) {
255 if (highQuality)
256 context.pixelValueF(lon, lat, scanLine);
257 else
258 context.pixelValue(lon, lat, scanLine);
259 }
260
261 ++scanLine;
262 }
263
264 // copy scanline to improve performance
265 if (interlaced && y + 1 < m_yBottom) {
266 const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
267
268 memcpy(m_canvasImage->scanLine(y + 1) + xLeft * pixelByteSize,
269 m_canvasImage->scanLine(y) + xLeft * pixelByteSize,
270 (xRight - xLeft) * pixelByteSize);
271 ++y;
272 }
273 }
274}
This file contains the headers for ViewportParams.
A painter that allows to draw geometric primitives on the map.
Definition GeoPainter.h:86
MapQuality mapQuality() const
Returns the map quality.
void drawImage(const GeoDataCoordinates &centerPosition, const QImage &image)
Draws an image at the given position. The image is placed with its center located at the given center...
Tile loading from a quad tree.
void cleanupTilehash()
Cleans up the internal tile hash.
void resetTilehash()
Resets the internal tile hash.
A public class that controls what is visible in the viewport of a Marble map.
Binds a QML item to a specific geodetic location in screen coordinates.
MapQuality
This enum is used to choose the map quality shown in the view.
@ HighQuality
High quality (e.g. antialiasing for lines)
@ PrintQuality
Print quality.
@ LowQuality
Low resolution (e.g. interlaced)
qsizetype bytesPerLine() const const
void fill(Qt::GlobalColor color)
Format format() const const
int height() const const
uchar * scanLine(int i)
QSize size() const const
int width() const const
void start(Callable &&callableToRun, int priority)
bool waitForDone(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:37:04 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.