Marble

ScanlineTextureMapperContext.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Carlos Licea <carlos [email protected]>
4 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <[email protected]>
5 //
6 
7 #include "ScanlineTextureMapperContext.h"
8 
9 #include "GeoSceneAbstractTileProjection.h"
10 #include "MarbleDebug.h"
11 #include "StackedTile.h"
12 #include "StackedTileLoader.h"
13 #include "TileId.h"
14 #include "ViewParams.h"
15 #include "ViewportParams.h"
16 
17 
18 using namespace Marble;
19 
20 ScanlineTextureMapperContext::ScanlineTextureMapperContext( StackedTileLoader * const tileLoader, int tileLevel )
21  : m_tileLoader( tileLoader ),
22  m_textureProjection(tileLoader->tileProjection()->type()), // cache texture projection
23  m_tileSize( tileLoader->tileSize() ), // cache tile size
24  m_tileLevel( tileLevel ),
25  m_globalWidth( m_tileSize.width() * m_tileLoader->tileColumnCount( m_tileLevel ) ),
26  m_globalHeight( m_tileSize.height() * m_tileLoader->tileRowCount( m_tileLevel ) ),
27  m_normGlobalWidth( m_globalWidth / ( 2 * M_PI ) ),
28  m_normGlobalHeight( m_globalHeight / M_PI ),
29  m_tile( nullptr ),
30  m_tilePosX( 65535 ),
31  m_tilePosY( 65535 ),
32  m_toTileCoordinatesLon( 0.5 * m_globalWidth - m_tilePosX ),
33  m_toTileCoordinatesLat( 0.5 * m_globalHeight - m_tilePosY ),
34  m_prevLat( 0.0 ),
35  m_prevLon( 0.0 ),
36  m_prevPixelX( 0.0 ),
37  m_prevPixelY( 0.0 )
38 {
39 }
40 
41 void ScanlineTextureMapperContext::pixelValueF( const qreal lon, const qreal lat,
42  QRgb* const scanLine )
43 {
44  // The same method using integers performs about 33% faster.
45  // However we need the qreal version to create the high quality mode.
46 
47  // Convert the lon and lat coordinates of the position on the scanline
48  // measured in radian to the pixel position of the requested
49  // coordinate on the current tile.
50 
51  m_prevPixelX = rad2PixelX( lon );
52  m_prevPixelY = rad2PixelY( lat );
53 
54  qreal posX = m_toTileCoordinatesLon + m_prevPixelX;
55  qreal posY = m_toTileCoordinatesLat + m_prevPixelY;
56 
57  // Most of the time while moving along the scanLine we'll stay on the
58  // same tile. However at the tile border we might "fall off". If that
59  // happens we need to find out the next tile that needs to be loaded.
60 
61  if ( posX >= (qreal)( m_tileSize.width() )
62  || posX < 0.0
63  || posY >= (qreal)( m_tileSize.height() )
64  || posY < 0.0 )
65  {
66  nextTile( posX, posY );
67  }
68  if ( m_tile ) {
69  *scanLine = m_tile->pixelF( posX, posY );
70  }
71  else {
72  *scanLine = 0;
73  }
74 
75  m_prevLon = lon;
76  m_prevLat = lat; // preparing for interpolation
77 }
78 
79 void ScanlineTextureMapperContext::pixelValue( const qreal lon, const qreal lat,
80  QRgb* const scanLine )
81 {
82  // The same method using integers performs about 33% faster.
83  // However we need the qreal version to create the high quality mode.
84 
85  // Convert the lon and lat coordinates of the position on the scanline
86  // measured in radian to the pixel position of the requested
87  // coordinate on the current tile.
88 
89  m_prevPixelX = rad2PixelX( lon );
90  m_prevPixelY = rad2PixelY( lat );
91  int iPosX = (int)( m_toTileCoordinatesLon + m_prevPixelX );
92  int iPosY = (int)( m_toTileCoordinatesLat + m_prevPixelY );
93 
94  // Most of the time while moving along the scanLine we'll stay on the
95  // same tile. However at the tile border we might "fall off". If that
96  // happens we need to find out the next tile that needs to be loaded.
97 
98  if ( iPosX >= m_tileSize.width()
99  || iPosX < 0
100  || iPosY >= m_tileSize.height()
101  || iPosY < 0 )
102  {
103  nextTile( iPosX, iPosY );
104  }
105 
106  if ( m_tile ) {
107  *scanLine = m_tile->pixel( iPosX, iPosY );
108  }
109  else {
110  *scanLine = 0;
111  }
112 
113  m_prevLon = lon;
114  m_prevLat = lat; // preparing for interpolation
115 }
116 
117 // This method interpolates color values for skipped pixels in a scanline.
118 //
119 // While moving along the scanline we don't move from pixel to pixel but
120 // leave out n pixels each time and calculate the exact position and
121 // color value for the new pixel. The pixel values in between get
122 // approximated through linear interpolation across the direct connecting
123 // line on the original tiles directly.
124 // This method will do by far most of the calculations for the
125 // texturemapping, so we move towards integer math to improve speed.
126 
127 void ScanlineTextureMapperContext::pixelValueApproxF( const qreal lon, const qreal lat,
128  QRgb *scanLine, const int n )
129 {
130  // stepLon/Lat: Distance between two subsequent approximated positions
131 
132  qreal stepLat = lat - m_prevLat;
133  qreal stepLon = lon - m_prevLon;
134 
135  // As long as the distance is smaller than 180 deg we can assume that
136  // we didn't cross the dateline.
137 
138  const qreal nInverse = 1.0 / (qreal)(n);
139 
140  if ( fabs(stepLon) < M_PI ) {
141  const qreal itStepLon = ( rad2PixelX( lon ) - m_prevPixelX ) * nInverse;
142  const qreal itStepLat = ( rad2PixelY( lat ) - m_prevPixelY ) * nInverse;
143 
144  // To improve speed we unroll
145  // AbstractScanlineTextureMapper::pixelValue(...) here and
146  // calculate the performance critical issues via integers
147 
148  qreal itLon = m_prevPixelX + m_toTileCoordinatesLon;
149  qreal itLat = m_prevPixelY + m_toTileCoordinatesLat;
150 
151  const int tileWidth = m_tileSize.width();
152  const int tileHeight = m_tileSize.height();
153 
154  // int oldR = 0;
155  // int oldG = 0;
156  // int oldB = 0;
157 
158  QRgb oldRgb = qRgb( 0, 0, 0 );
159 
160  qreal oldPosX = -1;
161  qreal oldPosY = 0;
162 
163  const bool alwaysCheckTileRange =
164  isOutOfTileRangeF( itLon, itLat, itStepLon, itStepLat, n );
165 
166  for ( int j=1; j < n; ++j ) {
167  qreal posX = itLon + itStepLon * j;
168  qreal posY = itLat + itStepLat * j;
169  if ( alwaysCheckTileRange )
170  if ( posX >= tileWidth
171  || posX < 0.0
172  || posY >= tileHeight
173  || posY < 0.0 )
174  {
175  nextTile( posX, posY );
176  itLon = m_prevPixelX + m_toTileCoordinatesLon;
177  itLat = m_prevPixelY + m_toTileCoordinatesLat;
178  posX = qBound <qreal>( 0.0, (itLon + itStepLon * j), tileWidth-1.0 );
179  posY = qBound <qreal>( 0.0, (itLat + itStepLat * j), tileHeight-1.0 );
180  oldPosX = -1;
181  }
182 
183  *scanLine = m_tile->pixelF( posX, posY );
184 
185  // Just perform bilinear interpolation if there's a color change compared to the
186  // last pixel that was evaluated. This speeds up things greatly for maps like OSM
187  if ( *scanLine != oldRgb ) {
188  if ( oldPosX != -1 ) {
189  *(scanLine - 1) = m_tile->pixelF( oldPosX, oldPosY, *(scanLine - 1) );
190  oldPosX = -1;
191  }
192  oldRgb = m_tile->pixelF( posX, posY, *scanLine );
193  *scanLine = oldRgb;
194  }
195  else {
196  oldPosX = posX;
197  oldPosY = posY;
198  }
199 
200  // if ( needsFilter( *scanLine, oldR, oldB, oldG ) ) {
201  // *scanLine = m_tile->pixelF( posX, posY );
202  // }
203 
204  ++scanLine;
205  }
206  }
207 
208  // For the case where we cross the dateline between (lon, lat) and
209  // (prevlon, prevlat) we need a more sophisticated calculation.
210  // However as this will happen rather rarely, we use
211  // pixelValue(...) directly to make the code more readable.
212 
213  else {
214  stepLon = ( TWOPI - fabs(stepLon) ) * nInverse;
215  stepLat = stepLat * nInverse;
216  // We need to distinguish two cases:
217  // crossing the dateline from east to west ...
218 
219  if ( m_prevLon < lon ) {
220 
221  for ( int j = 1; j < n; ++j ) {
222  m_prevLat += stepLat;
223  m_prevLon -= stepLon;
224  if ( m_prevLon <= -M_PI )
225  m_prevLon += TWOPI;
226  pixelValueF( m_prevLon, m_prevLat, scanLine );
227  ++scanLine;
228  }
229  }
230 
231  // ... and vice versa: from west to east.
232 
233  else {
234  qreal curStepLon = lon - n * stepLon;
235 
236  for ( int j = 1; j < n; ++j ) {
237  m_prevLat += stepLat;
238  curStepLon += stepLon;
239  qreal evalLon = curStepLon;
240  if ( curStepLon <= -M_PI )
241  evalLon += TWOPI;
242  pixelValueF( evalLon, m_prevLat, scanLine );
243  ++scanLine;
244  }
245  }
246  }
247 }
248 
249 
250 bool ScanlineTextureMapperContext::isOutOfTileRangeF( const qreal itLon, const qreal itLat,
251  const qreal itStepLon, const qreal itStepLat,
252  const int n ) const
253 {
254  const qreal minIPosX = itLon + itStepLon;
255  const qreal minIPosY = itLat + itStepLat;
256  const qreal maxIPosX = itLon + itStepLon * ( n - 1 );
257  const qreal maxIPosY = itLat + itStepLat * ( n - 1 );
258  return ( maxIPosX >= m_tileSize.width() || maxIPosX < 0
259  || maxIPosY >= m_tileSize.height() || maxIPosY < 0
260  || minIPosX >= m_tileSize.width() || minIPosX < 0
261  || minIPosY >= m_tileSize.height() || minIPosY < 0 );
262 }
263 
264 
265 void ScanlineTextureMapperContext::pixelValueApprox( const qreal lon, const qreal lat,
266  QRgb *scanLine, const int n )
267 {
268  // stepLon/Lat: Distance between two subsequent approximated positions
269 
270  qreal stepLat = lat - m_prevLat;
271  qreal stepLon = lon - m_prevLon;
272 
273  // As long as the distance is smaller than 180 deg we can assume that
274  // we didn't cross the dateline.
275 
276  const qreal nInverse = 1.0 / (qreal)(n);
277 
278  if ( fabs(stepLon) < M_PI ) {
279  const int itStepLon = (int)( ( rad2PixelX( lon ) - m_prevPixelX ) * nInverse * 128.0 );
280  const int itStepLat = (int)( ( rad2PixelY( lat ) - m_prevPixelY ) * nInverse * 128.0 );
281 
282  // To improve speed we unroll
283  // AbstractScanlineTextureMapper::pixelValue(...) here and
284  // calculate the performance critical issues via integers
285 
286  int itLon = (int)( ( m_prevPixelX + m_toTileCoordinatesLon ) * 128.0 );
287  int itLat = (int)( ( m_prevPixelY + m_toTileCoordinatesLat ) * 128.0 );
288 
289  const int tileWidth = m_tileSize.width();
290  const int tileHeight = m_tileSize.height();
291 
292  const bool alwaysCheckTileRange =
293  isOutOfTileRange( itLon, itLat, itStepLon, itStepLat, n );
294 
295  if ( !alwaysCheckTileRange ) {
296  int iPosXf = itLon;
297  int iPosYf = itLat;
298  for ( int j = 1; j < n; ++j ) {
299  iPosXf += itStepLon;
300  iPosYf += itStepLat;
301  *scanLine = m_tile->pixel( iPosXf >> 7, iPosYf >> 7 );
302  ++scanLine;
303  }
304  }
305  else {
306  for ( int j = 1; j < n; ++j ) {
307  int iPosX = ( itLon + itStepLon * j ) >> 7;
308  int iPosY = ( itLat + itStepLat * j ) >> 7;
309 
310  if ( iPosX >= tileWidth
311  || iPosX < 0
312  || iPosY >= tileHeight
313  || iPosY < 0 )
314  {
315  nextTile( iPosX, iPosY );
316  itLon = (int)( ( m_prevPixelX + m_toTileCoordinatesLon ) * 128.0 );
317  itLat = (int)( ( m_prevPixelY + m_toTileCoordinatesLat ) * 128.0 );
318  iPosX = ( itLon + itStepLon * j ) >> 7;
319  iPosY = ( itLat + itStepLat * j ) >> 7;
320  // Bug 453332: Crash on Panning with Equirectangular Projection:
321  // https://bugs.kde.org/show_bug.cgi?id=453332
322  // Apparently at the edge of the latitude range a rounding error can move
323  // iPosY out of tile coords and nextTile will just reload the same tile.
324  // So let's just ensure that we stay in the expected range:
325  if (iPosY >= tileHeight) iPosY = tileHeight - 1;
326  if (iPosY < 0) iPosY = 0;
327  }
328 
329  *scanLine = m_tile->pixel( iPosX, iPosY );
330  ++scanLine;
331  }
332  }
333  }
334 
335  // For the case where we cross the dateline between (lon, lat) and
336  // (prevlon, prevlat) we need a more sophisticated calculation.
337  // However as this will happen rather rarely, we use
338  // pixelValue(...) directly to make the code more readable.
339 
340  else {
341  stepLon = ( TWOPI - fabs(stepLon) ) * nInverse;
342  stepLat = stepLat * nInverse;
343  // We need to distinguish two cases:
344  // crossing the dateline from east to west ...
345 
346  if ( m_prevLon < lon ) {
347 
348  for ( int j = 1; j < n; ++j ) {
349  m_prevLat += stepLat;
350  m_prevLon -= stepLon;
351  if ( m_prevLon <= -M_PI )
352  m_prevLon += TWOPI;
353  pixelValue( m_prevLon, m_prevLat, scanLine );
354  ++scanLine;
355  }
356  }
357 
358  // ... and vice versa: from west to east.
359 
360  else {
361  qreal curStepLon = lon - n * stepLon;
362 
363  for ( int j = 1; j < n; ++j ) {
364  m_prevLat += stepLat;
365  curStepLon += stepLon;
366  qreal evalLon = curStepLon;
367  if ( curStepLon <= -M_PI )
368  evalLon += TWOPI;
369  pixelValue( evalLon, m_prevLat, scanLine );
370  ++scanLine;
371  }
372  }
373  }
374 }
375 
376 
377 bool ScanlineTextureMapperContext::isOutOfTileRange( const int itLon, const int itLat,
378  const int itStepLon, const int itStepLat,
379  const int n ) const
380 {
381  const int minIPosX = ( itLon + itStepLon ) >> 7;
382  const int minIPosY = ( itLat + itStepLat ) >> 7;
383  const int maxIPosX = ( itLon + itStepLon * ( n - 1 ) ) >> 7;
384  const int maxIPosY = ( itLat + itStepLat * ( n - 1 ) ) >> 7;
385  return ( maxIPosX >= m_tileSize.width() || maxIPosX < 0
386  || maxIPosY >= m_tileSize.height() || maxIPosY < 0
387  || minIPosX >= m_tileSize.width() || minIPosX < 0
388  || minIPosY >= m_tileSize.height() || minIPosY < 0 );
389 }
390 
391 
392 int ScanlineTextureMapperContext::interpolationStep( const ViewportParams *viewport, MapQuality mapQuality )
393 {
394  if ( mapQuality == PrintQuality ) {
395  return 1; // Don't interpolate for print quality.
396  }
397 
398  if ( ! viewport->mapCoversViewport() ) {
399  return 8;
400  }
401 
402  // Find the optimal interpolation interval m_nBest for the
403  // current image canvas width
404  const int width = viewport->width();
405 
406  int nBest = 2;
407  int nEvalMin = width - 1;
408  for ( int it = 1; it < 48; ++it ) {
409  int nEval = ( width - 1 ) / it + ( width - 1 ) % it;
410  if ( nEval < nEvalMin ) {
411  nEvalMin = nEval;
412  nBest = it;
413  }
414  }
415 
416  return nBest;
417 }
418 
419 
420 QImage::Format ScanlineTextureMapperContext::optimalCanvasImageFormat( const ViewportParams *viewport )
421 {
422  // If the globe covers fully the screen then we can use the faster
423  // RGB32 as there are no translucent areas involved.
424  QImage::Format imageFormat = ( viewport->mapCoversViewport() )
427 
428  return imageFormat;
429 }
430 
431 
432 void ScanlineTextureMapperContext::nextTile( int &posX, int &posY )
433 {
434  // Move from tile coordinates to global texture coordinates
435  // ( with origin in upper left corner, measured in pixel)
436 
437  int lon = posX + m_tilePosX;
438  if ( lon >= m_globalWidth )
439  lon -= m_globalWidth;
440  else if ( lon < 0 )
441  lon += m_globalWidth;
442 
443  int lat = posY + m_tilePosY;
444  if ( lat >= m_globalHeight )
445  lat -= m_globalHeight;
446  else if ( lat < 0 )
447  lat += m_globalHeight;
448 
449  // tileCol counts the tile columns left from the current tile.
450  // tileRow counts the tile rows on the top from the current tile.
451 
452  const int tileCol = lon / m_tileSize.width();
453  const int tileRow = lat / m_tileSize.height();
454 
455  m_tile = m_tileLoader->loadTile( TileId( 0, m_tileLevel, tileCol, tileRow ) );
456 
457  // Update position variables:
458  // m_tilePosX/Y stores the position of the tiles in
459  // global texture coordinates
460  // ( origin upper left, measured in pixels )
461 
462  m_tilePosX = tileCol * m_tileSize.width();
463  m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX);
464  posX = lon - m_tilePosX;
465 
466  m_tilePosY = tileRow * m_tileSize.height();
467  m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY);
468  posY = lat - m_tilePosY;
469 }
470 
471 void ScanlineTextureMapperContext::nextTile( qreal &posX, qreal &posY )
472 {
473  // Move from tile coordinates to global texture coordinates
474  // ( with origin in upper left corner, measured in pixel)
475 
476  int lon = (int)(posX + m_tilePosX);
477  if ( lon >= m_globalWidth )
478  lon -= m_globalWidth;
479  else if ( lon < 0 )
480  lon += m_globalWidth;
481 
482  int lat = (int)(posY + m_tilePosY);
483  if ( lat >= m_globalHeight )
484  lat -= m_globalHeight;
485  else if ( lat < 0 )
486  lat += m_globalHeight;
487 
488  // tileCol counts the tile columns left from the current tile.
489  // tileRow counts the tile rows on the top from the current tile.
490 
491  const int tileCol = lon / m_tileSize.width();
492  const int tileRow = lat / m_tileSize.height();
493 
494  m_tile = m_tileLoader->loadTile( TileId( 0, m_tileLevel, tileCol, tileRow ) );
495 
496  // Update position variables:
497  // m_tilePosX/Y stores the position of the tiles in
498  // global texture coordinates
499  // ( origin upper left, measured in pixels )
500 
501  m_tilePosX = tileCol * m_tileSize.width();
502  m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX);
503  posX = lon - m_tilePosX;
504 
505  m_tilePosY = tileRow * m_tileSize.height();
506  m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY);
507  posY = lat - m_tilePosY;
508 }
Type type(const QSqlDatabase &db)
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.
@ PrintQuality
Print quality.
Definition: MarbleGlobal.h:79
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Sep 27 2023 04:09:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.