Marble

ScanlineTextureMapperContext.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2007 Carlos Licea <carlos _licea@hotmail.com>
4// SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
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
18using namespace Marble;
19
20ScanlineTextureMapperContext::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
41void 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
79void 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
127void 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
250bool 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
265void 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
377bool 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
392int 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
420QImage::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() )
426 : QImage::Format_ARGB32_Premultiplied;
427
428 return imageFormat;
429}
430
431
432void 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
471void 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}
This file contains the headers for ViewParameters.
This file contains the headers for ViewportParams.
Tile loading from a quad tree.
const StackedTile * loadTile(TileId const &stackedTileId)
Loads a tile and returns it.
uint pixelF(qreal x, qreal y) const
Returns the color value of the result tile at a given floating point position.
uint pixel(int x, int y) const
Returns the color value of the result tile at the given integer position.
A public class that controls what is visible in the viewport of a Marble map.
Type type(const QSqlDatabase &db)
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.
@ PrintQuality
Print quality.
int height() const const
int width() const const
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.