Marble

SphericalScanlineTextureMapper.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Torsten Rahn <[email protected]>
4 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <[email protected]>
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 
27 using namespace Marble;
28 
29 class SphericalScanlineTextureMapper::RenderJob : public QRunnable
30 {
31 public:
32  RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );
33 
34  void run() override;
35 
36 private:
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 
46 SphericalScanlineTextureMapper::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 
57 SphericalScanlineTextureMapper::SphericalScanlineTextureMapper( StackedTileLoader *tileLoader )
58  : TextureMapperInterface()
59  , m_tileLoader( tileLoader )
60  , m_radius( 0 )
61  , m_threadPool()
62 {
63 }
64 
65 void 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 
104 void 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 
136 void 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 }
MapQuality mapQuality() const
Returns the map quality.
Definition: GeoPainter.cpp:211
@ LowQuality
Low resolution (e.g. interlaced)
Definition: MarbleGlobal.h:76
Tile loading from a quad tree.
A public class that controls what is visible in the viewport of a Marble map.
MapQuality
This enum is used to choose the map quality shown in the view.
Definition: MarbleGlobal.h:74
Binds a QML item to a specific geodetic location in screen coordinates.
KIOWIDGETS_EXPORT bool run(const QUrl &_url, bool _is_local)
A painter that allows to draw geometric primitives on the map.
Definition: GeoPainter.h:88
@ HighQuality
High quality (e.g. antialiasing for lines)
Definition: MarbleGlobal.h:78
@ PrintQuality
Print quality.
Definition: MarbleGlobal.h:79
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...
Definition: GeoPainter.cpp:466
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Oct 4 2023 04:09:43 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.