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 }