File indexing completed on 2024-04-21 03:49:56

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2007 Torsten Rahn <tackat@kde.org>
0004 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0005 //
0006 
0007 
0008 #include "SphericalScanlineTextureMapper.h"
0009 
0010 #include <cmath>
0011 
0012 #include <qmath.h>
0013 #include <QRunnable>
0014 
0015 #include "GeoPainter.h"
0016 #include "GeoDataPolygon.h"
0017 #include "MarbleDebug.h"
0018 #include "Quaternion.h"
0019 #include "ScanlineTextureMapperContext.h"
0020 #include "StackedTileLoader.h"
0021 #include "StackedTile.h"
0022 #include "TextureColorizer.h"
0023 #include "ViewportParams.h"
0024 #include "MathHelper.h"
0025 
0026 
0027 using namespace Marble;
0028 
0029 class SphericalScanlineTextureMapper::RenderJob : public QRunnable
0030 {
0031 public:
0032     RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );
0033 
0034     void run() override;
0035 
0036 private:
0037     StackedTileLoader *const m_tileLoader;
0038     const int m_tileLevel;
0039     QImage *const m_canvasImage;
0040     const ViewportParams *const m_viewport;
0041     const MapQuality m_mapQuality;
0042     int const m_yTop;
0043     int const m_yBottom;
0044 };
0045 
0046 SphericalScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
0047     : m_tileLoader( tileLoader ),
0048       m_tileLevel( tileLevel ),
0049       m_canvasImage( canvasImage ),
0050       m_viewport( viewport ),
0051       m_mapQuality( mapQuality ),
0052       m_yTop( yTop ),
0053       m_yBottom( yBottom )
0054 {
0055 }
0056 
0057 SphericalScanlineTextureMapper::SphericalScanlineTextureMapper( StackedTileLoader *tileLoader )
0058     : TextureMapperInterface()
0059     , m_tileLoader( tileLoader )
0060     , m_radius( 0 )
0061     , m_threadPool()
0062 {
0063 }
0064 
0065 void SphericalScanlineTextureMapper::mapTexture( GeoPainter *painter,
0066                                                  const ViewportParams *viewport,
0067                                                  int tileZoomLevel,
0068                                                  const QRect &dirtyRect,
0069                                                  TextureColorizer *texColorizer )
0070 {
0071     if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
0072         const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );
0073 
0074         if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
0075             m_canvasImage = QImage( viewport->size(), optimalFormat );
0076         }
0077 
0078         if ( !viewport->mapCoversViewport() ) {
0079             m_canvasImage.fill( 0 );
0080         }
0081 
0082         m_radius = viewport->radius();
0083         m_repaintNeeded = true;
0084     }
0085 
0086     if ( m_repaintNeeded ) {
0087         mapTexture( viewport, tileZoomLevel, painter->mapQuality() );
0088 
0089         if ( texColorizer ) {
0090             texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
0091         }
0092 
0093         m_repaintNeeded = false;
0094     }
0095 
0096     const int radius = viewport->radius();
0097 
0098     QRect rect( viewport->width() / 2 - radius, viewport->height() / 2 - radius,
0099                 2 * radius, 2 * radius);
0100     rect = rect.intersected( dirtyRect );
0101     painter->drawImage( rect, m_canvasImage, rect );
0102 }
0103 
0104 void SphericalScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
0105 {
0106     // Reset backend
0107     m_tileLoader->resetTilehash();
0108 
0109     // Initialize needed constants:
0110 
0111     const int imageHeight = m_canvasImage.height();
0112     const qint64  radius      = viewport->radius();
0113 
0114     // Calculate the actual y-range of the map on the screen 
0115     const int skip = ( mapQuality == LowQuality ) ? 1
0116                                                   : 0;
0117     const int yTop = ( imageHeight / 2 - radius >= 0 ) ? imageHeight / 2 - radius
0118                                                        : 0;
0119     const int yBottom = ( yTop == 0 ) ? imageHeight - skip
0120                                       : yTop + radius + radius - skip;
0121 
0122     const int numThreads = m_threadPool.maxThreadCount();
0123     const int yStep = qCeil(qreal( yBottom - yTop ) / qreal(numThreads));
0124     for ( int i = 0; i < numThreads; ++i ) {
0125         const int yStart = yTop +  i      * yStep;
0126         const int yEnd   = qMin(yBottom, yTop + (i + 1) * yStep);
0127         QRunnable *const job = new RenderJob( m_tileLoader, tileZoomLevel, &m_canvasImage, viewport, mapQuality, yStart, yEnd );
0128         m_threadPool.start( job );
0129     }
0130 
0131     m_threadPool.waitForDone();
0132 
0133     m_tileLoader->cleanupTilehash();
0134 }
0135 
0136 void SphericalScanlineTextureMapper::RenderJob::run()
0137 {
0138     const int imageHeight = m_canvasImage->height();
0139     const int imageWidth  = m_canvasImage->width();
0140     const qint64  radius  = m_viewport->radius();
0141     const qreal  inverseRadius = 1.0 / (qreal)(radius);
0142 
0143     const bool interlaced   = ( m_mapQuality == LowQuality );
0144     const bool highQuality  = ( m_mapQuality == HighQuality
0145                              || m_mapQuality == PrintQuality );
0146     const bool printQuality = ( m_mapQuality == PrintQuality );
0147 
0148     // Evaluate the degree of interpolation
0149     const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );
0150 
0151     // Calculate north pole position to decrease pole distortion later on
0152     Quaternion northPole = Quaternion::fromSpherical( 0.0, M_PI * 0.5 );
0153     northPole.rotateAroundAxis( m_viewport->planetAxis().inverse() );
0154     const int northPoleX = imageWidth / 2 + (int)( radius * northPole.v[Q_X] );
0155     const int northPoleY = imageHeight / 2 - (int)( radius * northPole.v[Q_Y] );
0156 
0157     // Calculate axis matrix to represent the planet's rotation.
0158     matrix  planetAxisMatrix;
0159     m_viewport->planetAxis().toMatrix( planetAxisMatrix );
0160 
0161     // initialize needed variables that are modified during texture mapping:
0162 
0163     ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
0164     qreal  lon = 0.0;
0165     qreal  lat = 0.0;
0166 
0167     // Scanline based algorithm to texture map a sphere
0168     for ( int y = m_yTop; y < m_yBottom ; ++y ) {
0169 
0170         // Evaluate coordinates for the 3D position vector of the current pixel
0171         const qreal qy = inverseRadius * (qreal)( imageHeight / 2 - y );
0172         const qreal qr = 1.0 - qy * qy;
0173 
0174         // rx is the radius component in x direction
0175         const int rx = (int)sqrt( (qreal)( radius * radius
0176                                       - ( ( y - imageHeight / 2 )
0177                                           * ( y - imageHeight / 2 ) ) ) );
0178 
0179         // Calculate the actual x-range of the map within the current scanline.
0180         // 
0181         // If the circular border of the earth disk is still visible then xLeft
0182         // equals the scanline position of the most left pixel that gets covered
0183         // by the earth disk. In terms of math this equals the half image width minus 
0184         // the radius component on the current scanline in x direction ("rx").
0185         //
0186         // If the zoom factor is high enough then the whole screen gets covered
0187         // by the earth and the border of the earth disk isn't visible anymore.
0188         // In that situation xLeft equals zero.
0189         // For xRight the situation is similar.
0190 
0191         const int xLeft  = ( imageWidth / 2 - rx > 0 ) ? imageWidth / 2 - rx
0192                                                        : 0;
0193         const int xRight = ( imageWidth / 2 - rx > 0 ) ? xLeft + rx + rx
0194                                                        : imageWidth;
0195 
0196         QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) ) + xLeft;
0197 
0198         const int xIpLeft  = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xLeft / n + 1 )
0199                                                          : 1;
0200         const int xIpRight = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xRight / n - 1 )
0201                                                          : n * (int)( xRight / n - 1 ) + 1; 
0202 
0203         // Decrease pole distortion due to linear approximation ( y-axis )
0204         bool crossingPoleArea = false;
0205         if ( northPole.v[Q_Z] > 0
0206              && northPoleY - ( n * 0.75 ) <= y
0207              && northPoleY + ( n * 0.75 ) >= y ) 
0208         {
0209             crossingPoleArea = true;
0210         }
0211 
0212         int ncount = 0;
0213 
0214         for ( int x = xLeft; x < xRight; ++x ) {
0215             // Prepare for interpolation
0216 
0217             const int leftInterval = xIpLeft + ncount * n;
0218 
0219             bool interpolate = false;
0220             if ( x >= xIpLeft && x <= xIpRight ) {
0221 
0222                 // Decrease pole distortion due to linear approximation ( x-axis )
0223 //                mDebug() << QString("NorthPole X: %1, LeftInterval: %2").arg( northPoleX ).arg( leftInterval );
0224                 if ( crossingPoleArea
0225                      && northPoleX >= leftInterval + n
0226                      && northPoleX < leftInterval + 2 * n
0227                      && x < leftInterval + 3 * n )
0228                 {
0229                     interpolate = false;
0230                 }
0231                 else {
0232                     x += n - 1;
0233                     interpolate = !printQuality;
0234                     ++ncount;
0235                 } 
0236             }
0237             else
0238                 interpolate = false;
0239 
0240             // Evaluate more coordinates for the 3D position vector of
0241             // the current pixel.
0242             const qreal qx = (qreal)( x - imageWidth / 2 ) * inverseRadius;
0243             const qreal qr2z = qr - qx * qx;
0244             const qreal qz = ( qr2z > 0.0 ) ? sqrt( qr2z ) : 0.0;
0245 
0246             // Create Quaternion from vector coordinates and rotate it
0247             // around globe axis
0248             Quaternion qpos( 0.0, qx, qy, qz );
0249             qpos.rotateAroundAxis( planetAxisMatrix );
0250 
0251             qpos.getSpherical( lon, lat );
0252 //            mDebug() << QString("lon: %1 lat: %2").arg(lon).arg(lat);
0253             // Approx for n-1 out of n pixels within the boundary of
0254             // xIpLeft to xIpRight
0255 
0256             if ( interpolate ) {
0257                 if (highQuality)
0258                     context.pixelValueApproxF( lon, lat, scanLine, n );
0259                 else
0260                     context.pixelValueApprox( lon, lat, scanLine, n );
0261 
0262                 scanLine += ( n - 1 );
0263             }
0264 
0265 //          Comment out the pixelValue line and run Marble if you want
0266 //          to understand the interpolation:
0267 
0268 //          Uncomment the crossingPoleArea line to check precise 
0269 //          rendering around north pole:
0270 
0271 //            if ( !crossingPoleArea )
0272             if ( x < imageWidth ) {
0273                 if ( highQuality )
0274                     context.pixelValueF( lon, lat, scanLine );
0275                 else
0276                     context.pixelValue( lon, lat, scanLine );
0277             }
0278 
0279             ++scanLine;
0280         }
0281 
0282         // copy scanline to improve performance
0283         if ( interlaced && y + 1 < m_yBottom ) { 
0284 
0285             const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
0286 
0287             memcpy( m_canvasImage->scanLine( y + 1 ) + xLeft * pixelByteSize, 
0288                     m_canvasImage->scanLine( y ) + xLeft * pixelByteSize, 
0289                     ( xRight - xLeft ) * pixelByteSize );
0290             ++y;
0291         }
0292     }
0293 }