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
8#include "SphericalScanlineTextureMapper.h"
9
10#include <cmath>
11
12#include <qmath.h>
13#include <QRunnable>
14
15#include "GeoPainter.h"
16#include "GeoDataPolygon.h"
17#include "MarbleDebug.h"
18#include "Quaternion.h"
19#include "ScanlineTextureMapperContext.h"
20#include "StackedTileLoader.h"
21#include "StackedTile.h"
22#include "TextureColorizer.h"
23#include "ViewportParams.h"
24#include "MathHelper.h"
25
26
27using namespace Marble;
28
29class SphericalScanlineTextureMapper::RenderJob : public QRunnable
30{
31public:
32 RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );
33
34 void run() override;
35
36private:
37 StackedTileLoader *const m_tileLoader;
38 const int m_tileLevel;
39 QImage *const m_canvasImage;
40 const ViewportParams *const m_viewport;
41 const MapQuality m_mapQuality;
42 int const m_yTop;
43 int const m_yBottom;
44};
45
46SphericalScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
47 : m_tileLoader( tileLoader ),
48 m_tileLevel( tileLevel ),
49 m_canvasImage( canvasImage ),
50 m_viewport( viewport ),
51 m_mapQuality( mapQuality ),
52 m_yTop( yTop ),
53 m_yBottom( yBottom )
54{
55}
56
57SphericalScanlineTextureMapper::SphericalScanlineTextureMapper( StackedTileLoader *tileLoader )
58 : TextureMapperInterface()
59 , m_tileLoader( tileLoader )
60 , m_radius( 0 )
61 , m_threadPool()
62{
63}
64
65void SphericalScanlineTextureMapper::mapTexture( GeoPainter *painter,
66 const ViewportParams *viewport,
67 int tileZoomLevel,
68 const QRect &dirtyRect,
69 TextureColorizer *texColorizer )
70{
71 if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
72 const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );
73
74 if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
75 m_canvasImage = QImage( viewport->size(), optimalFormat );
76 }
77
78 if ( !viewport->mapCoversViewport() ) {
79 m_canvasImage.fill( 0 );
80 }
81
82 m_radius = viewport->radius();
83 m_repaintNeeded = true;
84 }
85
86 if ( m_repaintNeeded ) {
87 mapTexture( viewport, tileZoomLevel, painter->mapQuality() );
88
89 if ( texColorizer ) {
90 texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
91 }
92
93 m_repaintNeeded = false;
94 }
95
96 const int radius = viewport->radius();
97
98 QRect rect( viewport->width() / 2 - radius, viewport->height() / 2 - radius,
99 2 * radius, 2 * radius);
100 rect = rect.intersected( dirtyRect );
101 painter->drawImage( rect, m_canvasImage, rect );
102}
103
104void SphericalScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
105{
106 // Reset backend
107 m_tileLoader->resetTilehash();
108
109 // Initialize needed constants:
110
111 const int imageHeight = m_canvasImage.height();
112 const qint64 radius = viewport->radius();
113
114 // Calculate the actual y-range of the map on the screen
115 const int skip = ( mapQuality == LowQuality ) ? 1
116 : 0;
117 const int yTop = ( imageHeight / 2 - radius >= 0 ) ? imageHeight / 2 - radius
118 : 0;
119 const int yBottom = ( yTop == 0 ) ? imageHeight - skip
120 : 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
145 || m_mapQuality == PrintQuality );
146 const bool printQuality = ( m_mapQuality == PrintQuality );
147
148 // Evaluate the degree of interpolation
149 const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );
150
151 // Calculate north pole position to decrease pole distortion later on
152 Quaternion northPole = Quaternion::fromSpherical( 0.0, M_PI * 0.5 );
153 northPole.rotateAroundAxis( m_viewport->planetAxis().inverse() );
154 const int northPoleX = imageWidth / 2 + (int)( radius * northPole.v[Q_X] );
155 const int northPoleY = imageHeight / 2 - (int)( radius * northPole.v[Q_Y] );
156
157 // Calculate axis matrix to represent the planet's rotation.
158 matrix planetAxisMatrix;
159 m_viewport->planetAxis().toMatrix( planetAxisMatrix );
160
161 // initialize needed variables that are modified during texture mapping:
162
163 ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
164 qreal lon = 0.0;
165 qreal lat = 0.0;
166
167 // Scanline based algorithm to texture map a sphere
168 for ( int y = m_yTop; y < m_yBottom ; ++y ) {
169
170 // Evaluate coordinates for the 3D position vector of the current pixel
171 const qreal qy = inverseRadius * (qreal)( imageHeight / 2 - y );
172 const qreal qr = 1.0 - qy * qy;
173
174 // rx is the radius component in x direction
175 const int rx = (int)sqrt( (qreal)( radius * radius
176 - ( ( y - imageHeight / 2 )
177 * ( y - imageHeight / 2 ) ) ) );
178
179 // Calculate the actual x-range of the map within the current scanline.
180 //
181 // If the circular border of the earth disk is still visible then xLeft
182 // equals the scanline position of the most left pixel that gets covered
183 // by the earth disk. In terms of math this equals the half image width minus
184 // the radius component on the current scanline in x direction ("rx").
185 //
186 // If the zoom factor is high enough then the whole screen gets covered
187 // by the earth and the border of the earth disk isn't visible anymore.
188 // In that situation xLeft equals zero.
189 // For xRight the situation is similar.
190
191 const int xLeft = ( imageWidth / 2 - rx > 0 ) ? imageWidth / 2 - rx
192 : 0;
193 const int xRight = ( imageWidth / 2 - rx > 0 ) ? xLeft + rx + rx
194 : imageWidth;
195
196 QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) ) + xLeft;
197
198 const int xIpLeft = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xLeft / n + 1 )
199 : 1;
200 const int xIpRight = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xRight / n - 1 )
201 : n * (int)( xRight / n - 1 ) + 1;
202
203 // Decrease pole distortion due to linear approximation ( y-axis )
204 bool crossingPoleArea = false;
205 if ( northPole.v[Q_Z] > 0
206 && northPoleY - ( n * 0.75 ) <= y
207 && northPoleY + ( n * 0.75 ) >= y )
208 {
209 crossingPoleArea = true;
210 }
211
212 int ncount = 0;
213
214 for ( int x = xLeft; x < xRight; ++x ) {
215 // Prepare for interpolation
216
217 const int leftInterval = xIpLeft + ncount * n;
218
219 bool interpolate = false;
220 if ( x >= xIpLeft && x <= xIpRight ) {
221
222 // Decrease pole distortion due to linear approximation ( x-axis )
223// mDebug() << QString("NorthPole X: %1, LeftInterval: %2").arg( northPoleX ).arg( leftInterval );
224 if ( crossingPoleArea
225 && northPoleX >= leftInterval + n
226 && northPoleX < leftInterval + 2 * n
227 && x < leftInterval + 3 * n )
228 {
229 interpolate = false;
230 }
231 else {
232 x += n - 1;
233 interpolate = !printQuality;
234 ++ncount;
235 }
236 }
237 else
238 interpolate = false;
239
240 // Evaluate more coordinates for the 3D position vector of
241 // the current pixel.
242 const qreal qx = (qreal)( x - imageWidth / 2 ) * inverseRadius;
243 const qreal qr2z = qr - qx * qx;
244 const qreal qz = ( qr2z > 0.0 ) ? sqrt( qr2z ) : 0.0;
245
246 // Create Quaternion from vector coordinates and rotate it
247 // around globe axis
248 Quaternion qpos( 0.0, qx, qy, qz );
249 qpos.rotateAroundAxis( planetAxisMatrix );
250
251 qpos.getSpherical( lon, lat );
252// mDebug() << QString("lon: %1 lat: %2").arg(lon).arg(lat);
253 // Approx for n-1 out of n pixels within the boundary of
254 // xIpLeft to xIpRight
255
256 if ( interpolate ) {
257 if (highQuality)
258 context.pixelValueApproxF( lon, lat, scanLine, n );
259 else
260 context.pixelValueApprox( lon, lat, scanLine, n );
261
262 scanLine += ( n - 1 );
263 }
264
265// Comment out the pixelValue line and run Marble if you want
266// to understand the interpolation:
267
268// Uncomment the crossingPoleArea line to check precise
269// rendering around north pole:
270
271// if ( !crossingPoleArea )
272 if ( x < imageWidth ) {
273 if ( highQuality )
274 context.pixelValueF( lon, lat, scanLine );
275 else
276 context.pixelValue( lon, lat, scanLine );
277 }
278
279 ++scanLine;
280 }
281
282 // copy scanline to improve performance
283 if ( interlaced && y + 1 < m_yBottom ) {
284
285 const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
286
287 memcpy( m_canvasImage->scanLine( y + 1 ) + xLeft * pixelByteSize,
288 m_canvasImage->scanLine( y ) + xLeft * pixelByteSize,
289 ( xRight - xLeft ) * pixelByteSize );
290 ++y;
291 }
292 }
293}
This file contains the headers for ViewportParams.
A painter that allows to draw geometric primitives on the map.
Definition GeoPainter.h:89
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 Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.