File indexing completed on 2024-04-14 03:48:03
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2007 Carlos Licea <carlos _licea@hotmail.com> 0004 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de> 0005 // 0006 0007 #include "ScanlineTextureMapperContext.h" 0008 0009 #include "GeoSceneAbstractTileProjection.h" 0010 #include "MarbleDebug.h" 0011 #include "StackedTile.h" 0012 #include "StackedTileLoader.h" 0013 #include "TileId.h" 0014 #include "ViewParams.h" 0015 #include "ViewportParams.h" 0016 0017 0018 using namespace Marble; 0019 0020 ScanlineTextureMapperContext::ScanlineTextureMapperContext( StackedTileLoader * const tileLoader, int tileLevel ) 0021 : m_tileLoader( tileLoader ), 0022 m_textureProjection(tileLoader->tileProjection()->type()), // cache texture projection 0023 m_tileSize( tileLoader->tileSize() ), // cache tile size 0024 m_tileLevel( tileLevel ), 0025 m_globalWidth( m_tileSize.width() * m_tileLoader->tileColumnCount( m_tileLevel ) ), 0026 m_globalHeight( m_tileSize.height() * m_tileLoader->tileRowCount( m_tileLevel ) ), 0027 m_normGlobalWidth( m_globalWidth / ( 2 * M_PI ) ), 0028 m_normGlobalHeight( m_globalHeight / M_PI ), 0029 m_tile( nullptr ), 0030 m_tilePosX( 65535 ), 0031 m_tilePosY( 65535 ), 0032 m_toTileCoordinatesLon( 0.5 * m_globalWidth - m_tilePosX ), 0033 m_toTileCoordinatesLat( 0.5 * m_globalHeight - m_tilePosY ), 0034 m_prevLat( 0.0 ), 0035 m_prevLon( 0.0 ), 0036 m_prevPixelX( 0.0 ), 0037 m_prevPixelY( 0.0 ) 0038 { 0039 } 0040 0041 void ScanlineTextureMapperContext::pixelValueF( const qreal lon, const qreal lat, 0042 QRgb* const scanLine ) 0043 { 0044 // The same method using integers performs about 33% faster. 0045 // However we need the qreal version to create the high quality mode. 0046 0047 // Convert the lon and lat coordinates of the position on the scanline 0048 // measured in radian to the pixel position of the requested 0049 // coordinate on the current tile. 0050 0051 m_prevPixelX = rad2PixelX( lon ); 0052 m_prevPixelY = rad2PixelY( lat ); 0053 0054 qreal posX = m_toTileCoordinatesLon + m_prevPixelX; 0055 qreal posY = m_toTileCoordinatesLat + m_prevPixelY; 0056 0057 // Most of the time while moving along the scanLine we'll stay on the 0058 // same tile. However at the tile border we might "fall off". If that 0059 // happens we need to find out the next tile that needs to be loaded. 0060 0061 if ( posX >= (qreal)( m_tileSize.width() ) 0062 || posX < 0.0 0063 || posY >= (qreal)( m_tileSize.height() ) 0064 || posY < 0.0 ) 0065 { 0066 nextTile( posX, posY ); 0067 } 0068 if ( m_tile ) { 0069 *scanLine = m_tile->pixelF( posX, posY ); 0070 } 0071 else { 0072 *scanLine = 0; 0073 } 0074 0075 m_prevLon = lon; 0076 m_prevLat = lat; // preparing for interpolation 0077 } 0078 0079 void ScanlineTextureMapperContext::pixelValue( const qreal lon, const qreal lat, 0080 QRgb* const scanLine ) 0081 { 0082 // The same method using integers performs about 33% faster. 0083 // However we need the qreal version to create the high quality mode. 0084 0085 // Convert the lon and lat coordinates of the position on the scanline 0086 // measured in radian to the pixel position of the requested 0087 // coordinate on the current tile. 0088 0089 m_prevPixelX = rad2PixelX( lon ); 0090 m_prevPixelY = rad2PixelY( lat ); 0091 int iPosX = (int)( m_toTileCoordinatesLon + m_prevPixelX ); 0092 int iPosY = (int)( m_toTileCoordinatesLat + m_prevPixelY ); 0093 0094 // Most of the time while moving along the scanLine we'll stay on the 0095 // same tile. However at the tile border we might "fall off". If that 0096 // happens we need to find out the next tile that needs to be loaded. 0097 0098 if ( iPosX >= m_tileSize.width() 0099 || iPosX < 0 0100 || iPosY >= m_tileSize.height() 0101 || iPosY < 0 ) 0102 { 0103 nextTile( iPosX, iPosY ); 0104 } 0105 0106 if ( m_tile ) { 0107 *scanLine = m_tile->pixel( iPosX, iPosY ); 0108 } 0109 else { 0110 *scanLine = 0; 0111 } 0112 0113 m_prevLon = lon; 0114 m_prevLat = lat; // preparing for interpolation 0115 } 0116 0117 // This method interpolates color values for skipped pixels in a scanline. 0118 // 0119 // While moving along the scanline we don't move from pixel to pixel but 0120 // leave out n pixels each time and calculate the exact position and 0121 // color value for the new pixel. The pixel values in between get 0122 // approximated through linear interpolation across the direct connecting 0123 // line on the original tiles directly. 0124 // This method will do by far most of the calculations for the 0125 // texturemapping, so we move towards integer math to improve speed. 0126 0127 void ScanlineTextureMapperContext::pixelValueApproxF( const qreal lon, const qreal lat, 0128 QRgb *scanLine, const int n ) 0129 { 0130 // stepLon/Lat: Distance between two subsequent approximated positions 0131 0132 qreal stepLat = lat - m_prevLat; 0133 qreal stepLon = lon - m_prevLon; 0134 0135 // As long as the distance is smaller than 180 deg we can assume that 0136 // we didn't cross the dateline. 0137 0138 const qreal nInverse = 1.0 / (qreal)(n); 0139 0140 if ( fabs(stepLon) < M_PI ) { 0141 const qreal itStepLon = ( rad2PixelX( lon ) - m_prevPixelX ) * nInverse; 0142 const qreal itStepLat = ( rad2PixelY( lat ) - m_prevPixelY ) * nInverse; 0143 0144 // To improve speed we unroll 0145 // AbstractScanlineTextureMapper::pixelValue(...) here and 0146 // calculate the performance critical issues via integers 0147 0148 qreal itLon = m_prevPixelX + m_toTileCoordinatesLon; 0149 qreal itLat = m_prevPixelY + m_toTileCoordinatesLat; 0150 0151 const int tileWidth = m_tileSize.width(); 0152 const int tileHeight = m_tileSize.height(); 0153 0154 // int oldR = 0; 0155 // int oldG = 0; 0156 // int oldB = 0; 0157 0158 QRgb oldRgb = qRgb( 0, 0, 0 ); 0159 0160 qreal oldPosX = -1; 0161 qreal oldPosY = 0; 0162 0163 const bool alwaysCheckTileRange = 0164 isOutOfTileRangeF( itLon, itLat, itStepLon, itStepLat, n ); 0165 0166 for ( int j=1; j < n; ++j ) { 0167 qreal posX = itLon + itStepLon * j; 0168 qreal posY = itLat + itStepLat * j; 0169 if ( alwaysCheckTileRange ) 0170 if ( posX >= tileWidth 0171 || posX < 0.0 0172 || posY >= tileHeight 0173 || posY < 0.0 ) 0174 { 0175 nextTile( posX, posY ); 0176 itLon = m_prevPixelX + m_toTileCoordinatesLon; 0177 itLat = m_prevPixelY + m_toTileCoordinatesLat; 0178 posX = qBound <qreal>( 0.0, (itLon + itStepLon * j), tileWidth-1.0 ); 0179 posY = qBound <qreal>( 0.0, (itLat + itStepLat * j), tileHeight-1.0 ); 0180 oldPosX = -1; 0181 } 0182 0183 *scanLine = m_tile->pixelF( posX, posY ); 0184 0185 // Just perform bilinear interpolation if there's a color change compared to the 0186 // last pixel that was evaluated. This speeds up things greatly for maps like OSM 0187 if ( *scanLine != oldRgb ) { 0188 if ( oldPosX != -1 ) { 0189 *(scanLine - 1) = m_tile->pixelF( oldPosX, oldPosY, *(scanLine - 1) ); 0190 oldPosX = -1; 0191 } 0192 oldRgb = m_tile->pixelF( posX, posY, *scanLine ); 0193 *scanLine = oldRgb; 0194 } 0195 else { 0196 oldPosX = posX; 0197 oldPosY = posY; 0198 } 0199 0200 // if ( needsFilter( *scanLine, oldR, oldB, oldG ) ) { 0201 // *scanLine = m_tile->pixelF( posX, posY ); 0202 // } 0203 0204 ++scanLine; 0205 } 0206 } 0207 0208 // For the case where we cross the dateline between (lon, lat) and 0209 // (prevlon, prevlat) we need a more sophisticated calculation. 0210 // However as this will happen rather rarely, we use 0211 // pixelValue(...) directly to make the code more readable. 0212 0213 else { 0214 stepLon = ( TWOPI - fabs(stepLon) ) * nInverse; 0215 stepLat = stepLat * nInverse; 0216 // We need to distinguish two cases: 0217 // crossing the dateline from east to west ... 0218 0219 if ( m_prevLon < lon ) { 0220 0221 for ( int j = 1; j < n; ++j ) { 0222 m_prevLat += stepLat; 0223 m_prevLon -= stepLon; 0224 if ( m_prevLon <= -M_PI ) 0225 m_prevLon += TWOPI; 0226 pixelValueF( m_prevLon, m_prevLat, scanLine ); 0227 ++scanLine; 0228 } 0229 } 0230 0231 // ... and vice versa: from west to east. 0232 0233 else { 0234 qreal curStepLon = lon - n * stepLon; 0235 0236 for ( int j = 1; j < n; ++j ) { 0237 m_prevLat += stepLat; 0238 curStepLon += stepLon; 0239 qreal evalLon = curStepLon; 0240 if ( curStepLon <= -M_PI ) 0241 evalLon += TWOPI; 0242 pixelValueF( evalLon, m_prevLat, scanLine ); 0243 ++scanLine; 0244 } 0245 } 0246 } 0247 } 0248 0249 0250 bool ScanlineTextureMapperContext::isOutOfTileRangeF( const qreal itLon, const qreal itLat, 0251 const qreal itStepLon, const qreal itStepLat, 0252 const int n ) const 0253 { 0254 const qreal minIPosX = itLon + itStepLon; 0255 const qreal minIPosY = itLat + itStepLat; 0256 const qreal maxIPosX = itLon + itStepLon * ( n - 1 ); 0257 const qreal maxIPosY = itLat + itStepLat * ( n - 1 ); 0258 return ( maxIPosX >= m_tileSize.width() || maxIPosX < 0 0259 || maxIPosY >= m_tileSize.height() || maxIPosY < 0 0260 || minIPosX >= m_tileSize.width() || minIPosX < 0 0261 || minIPosY >= m_tileSize.height() || minIPosY < 0 ); 0262 } 0263 0264 0265 void ScanlineTextureMapperContext::pixelValueApprox( const qreal lon, const qreal lat, 0266 QRgb *scanLine, const int n ) 0267 { 0268 // stepLon/Lat: Distance between two subsequent approximated positions 0269 0270 qreal stepLat = lat - m_prevLat; 0271 qreal stepLon = lon - m_prevLon; 0272 0273 // As long as the distance is smaller than 180 deg we can assume that 0274 // we didn't cross the dateline. 0275 0276 const qreal nInverse = 1.0 / (qreal)(n); 0277 0278 if ( fabs(stepLon) < M_PI ) { 0279 const int itStepLon = (int)( ( rad2PixelX( lon ) - m_prevPixelX ) * nInverse * 128.0 ); 0280 const int itStepLat = (int)( ( rad2PixelY( lat ) - m_prevPixelY ) * nInverse * 128.0 ); 0281 0282 // To improve speed we unroll 0283 // AbstractScanlineTextureMapper::pixelValue(...) here and 0284 // calculate the performance critical issues via integers 0285 0286 int itLon = (int)( ( m_prevPixelX + m_toTileCoordinatesLon ) * 128.0 ); 0287 int itLat = (int)( ( m_prevPixelY + m_toTileCoordinatesLat ) * 128.0 ); 0288 0289 const int tileWidth = m_tileSize.width(); 0290 const int tileHeight = m_tileSize.height(); 0291 0292 const bool alwaysCheckTileRange = 0293 isOutOfTileRange( itLon, itLat, itStepLon, itStepLat, n ); 0294 0295 if ( !alwaysCheckTileRange ) { 0296 int iPosXf = itLon; 0297 int iPosYf = itLat; 0298 for ( int j = 1; j < n; ++j ) { 0299 iPosXf += itStepLon; 0300 iPosYf += itStepLat; 0301 *scanLine = m_tile->pixel( iPosXf >> 7, iPosYf >> 7 ); 0302 ++scanLine; 0303 } 0304 } 0305 else { 0306 for ( int j = 1; j < n; ++j ) { 0307 int iPosX = ( itLon + itStepLon * j ) >> 7; 0308 int iPosY = ( itLat + itStepLat * j ) >> 7; 0309 0310 if ( iPosX >= tileWidth 0311 || iPosX < 0 0312 || iPosY >= tileHeight 0313 || iPosY < 0 ) 0314 { 0315 nextTile( iPosX, iPosY ); 0316 itLon = (int)( ( m_prevPixelX + m_toTileCoordinatesLon ) * 128.0 ); 0317 itLat = (int)( ( m_prevPixelY + m_toTileCoordinatesLat ) * 128.0 ); 0318 iPosX = ( itLon + itStepLon * j ) >> 7; 0319 iPosY = ( itLat + itStepLat * j ) >> 7; 0320 // Bug 453332: Crash on Panning with Equirectangular Projection: 0321 // https://bugs.kde.org/show_bug.cgi?id=453332 0322 // Apparently at the edge of the latitude range a rounding error can move 0323 // iPosY out of tile coords and nextTile will just reload the same tile. 0324 // So let's just ensure that we stay in the expected range: 0325 if (iPosY >= tileHeight) iPosY = tileHeight - 1; 0326 if (iPosY < 0) iPosY = 0; 0327 } 0328 0329 *scanLine = m_tile->pixel( iPosX, iPosY ); 0330 ++scanLine; 0331 } 0332 } 0333 } 0334 0335 // For the case where we cross the dateline between (lon, lat) and 0336 // (prevlon, prevlat) we need a more sophisticated calculation. 0337 // However as this will happen rather rarely, we use 0338 // pixelValue(...) directly to make the code more readable. 0339 0340 else { 0341 stepLon = ( TWOPI - fabs(stepLon) ) * nInverse; 0342 stepLat = stepLat * nInverse; 0343 // We need to distinguish two cases: 0344 // crossing the dateline from east to west ... 0345 0346 if ( m_prevLon < lon ) { 0347 0348 for ( int j = 1; j < n; ++j ) { 0349 m_prevLat += stepLat; 0350 m_prevLon -= stepLon; 0351 if ( m_prevLon <= -M_PI ) 0352 m_prevLon += TWOPI; 0353 pixelValue( m_prevLon, m_prevLat, scanLine ); 0354 ++scanLine; 0355 } 0356 } 0357 0358 // ... and vice versa: from west to east. 0359 0360 else { 0361 qreal curStepLon = lon - n * stepLon; 0362 0363 for ( int j = 1; j < n; ++j ) { 0364 m_prevLat += stepLat; 0365 curStepLon += stepLon; 0366 qreal evalLon = curStepLon; 0367 if ( curStepLon <= -M_PI ) 0368 evalLon += TWOPI; 0369 pixelValue( evalLon, m_prevLat, scanLine ); 0370 ++scanLine; 0371 } 0372 } 0373 } 0374 } 0375 0376 0377 bool ScanlineTextureMapperContext::isOutOfTileRange( const int itLon, const int itLat, 0378 const int itStepLon, const int itStepLat, 0379 const int n ) const 0380 { 0381 const int minIPosX = ( itLon + itStepLon ) >> 7; 0382 const int minIPosY = ( itLat + itStepLat ) >> 7; 0383 const int maxIPosX = ( itLon + itStepLon * ( n - 1 ) ) >> 7; 0384 const int maxIPosY = ( itLat + itStepLat * ( n - 1 ) ) >> 7; 0385 return ( maxIPosX >= m_tileSize.width() || maxIPosX < 0 0386 || maxIPosY >= m_tileSize.height() || maxIPosY < 0 0387 || minIPosX >= m_tileSize.width() || minIPosX < 0 0388 || minIPosY >= m_tileSize.height() || minIPosY < 0 ); 0389 } 0390 0391 0392 int ScanlineTextureMapperContext::interpolationStep( const ViewportParams *viewport, MapQuality mapQuality ) 0393 { 0394 if ( mapQuality == PrintQuality ) { 0395 return 1; // Don't interpolate for print quality. 0396 } 0397 0398 if ( ! viewport->mapCoversViewport() ) { 0399 return 8; 0400 } 0401 0402 // Find the optimal interpolation interval m_nBest for the 0403 // current image canvas width 0404 const int width = viewport->width(); 0405 0406 int nBest = 2; 0407 int nEvalMin = width - 1; 0408 for ( int it = 1; it < 48; ++it ) { 0409 int nEval = ( width - 1 ) / it + ( width - 1 ) % it; 0410 if ( nEval < nEvalMin ) { 0411 nEvalMin = nEval; 0412 nBest = it; 0413 } 0414 } 0415 0416 return nBest; 0417 } 0418 0419 0420 QImage::Format ScanlineTextureMapperContext::optimalCanvasImageFormat( const ViewportParams *viewport ) 0421 { 0422 // If the globe covers fully the screen then we can use the faster 0423 // RGB32 as there are no translucent areas involved. 0424 QImage::Format imageFormat = ( viewport->mapCoversViewport() ) 0425 ? QImage::Format_RGB32 0426 : QImage::Format_ARGB32_Premultiplied; 0427 0428 return imageFormat; 0429 } 0430 0431 0432 void ScanlineTextureMapperContext::nextTile( int &posX, int &posY ) 0433 { 0434 // Move from tile coordinates to global texture coordinates 0435 // ( with origin in upper left corner, measured in pixel) 0436 0437 int lon = posX + m_tilePosX; 0438 if ( lon >= m_globalWidth ) 0439 lon -= m_globalWidth; 0440 else if ( lon < 0 ) 0441 lon += m_globalWidth; 0442 0443 int lat = posY + m_tilePosY; 0444 if ( lat >= m_globalHeight ) 0445 lat -= m_globalHeight; 0446 else if ( lat < 0 ) 0447 lat += m_globalHeight; 0448 0449 // tileCol counts the tile columns left from the current tile. 0450 // tileRow counts the tile rows on the top from the current tile. 0451 0452 const int tileCol = lon / m_tileSize.width(); 0453 const int tileRow = lat / m_tileSize.height(); 0454 0455 m_tile = m_tileLoader->loadTile( TileId( 0, m_tileLevel, tileCol, tileRow ) ); 0456 0457 // Update position variables: 0458 // m_tilePosX/Y stores the position of the tiles in 0459 // global texture coordinates 0460 // ( origin upper left, measured in pixels ) 0461 0462 m_tilePosX = tileCol * m_tileSize.width(); 0463 m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX); 0464 posX = lon - m_tilePosX; 0465 0466 m_tilePosY = tileRow * m_tileSize.height(); 0467 m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY); 0468 posY = lat - m_tilePosY; 0469 } 0470 0471 void ScanlineTextureMapperContext::nextTile( qreal &posX, qreal &posY ) 0472 { 0473 // Move from tile coordinates to global texture coordinates 0474 // ( with origin in upper left corner, measured in pixel) 0475 0476 int lon = (int)(posX + m_tilePosX); 0477 if ( lon >= m_globalWidth ) 0478 lon -= m_globalWidth; 0479 else if ( lon < 0 ) 0480 lon += m_globalWidth; 0481 0482 int lat = (int)(posY + m_tilePosY); 0483 if ( lat >= m_globalHeight ) 0484 lat -= m_globalHeight; 0485 else if ( lat < 0 ) 0486 lat += m_globalHeight; 0487 0488 // tileCol counts the tile columns left from the current tile. 0489 // tileRow counts the tile rows on the top from the current tile. 0490 0491 const int tileCol = lon / m_tileSize.width(); 0492 const int tileRow = lat / m_tileSize.height(); 0493 0494 m_tile = m_tileLoader->loadTile( TileId( 0, m_tileLevel, tileCol, tileRow ) ); 0495 0496 // Update position variables: 0497 // m_tilePosX/Y stores the position of the tiles in 0498 // global texture coordinates 0499 // ( origin upper left, measured in pixels ) 0500 0501 m_tilePosX = tileCol * m_tileSize.width(); 0502 m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX); 0503 posX = lon - m_tilePosX; 0504 0505 m_tilePosY = tileRow * m_tileSize.height(); 0506 m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY); 0507 posY = lat - m_tilePosY; 0508 }