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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2007 Carlos Licea <carlos _licea@hotmail.com>
0004 // SPDX-FileCopyrightText: 2008 Inge Wallin <inge@lysator.liu.se>
0005 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0006 //
0007 
0008 
0009 // local
0010 #include "GenericScanlineTextureMapper.h"
0011 
0012 // Qt
0013 #include <qmath.h>
0014 #include <QRunnable>
0015 
0016 // Marble
0017 #include "GeoPainter.h"
0018 #include "MarbleDirs.h"
0019 #include "MarbleDebug.h"
0020 #include "ScanlineTextureMapperContext.h"
0021 #include "StackedTileLoader.h"
0022 #include "TextureColorizer.h"
0023 #include "ViewParams.h"
0024 #include "ViewportParams.h"
0025 #include "MathHelper.h"
0026 #include "AbstractProjection.h"
0027 
0028 using namespace Marble;
0029 
0030 class GenericScanlineTextureMapper::RenderJob : public QRunnable
0031 {
0032 public:
0033     RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );
0034 
0035     void run() override;
0036 
0037 private:
0038     StackedTileLoader *const m_tileLoader;
0039     const int m_tileLevel;
0040     QImage *const m_canvasImage;
0041     const ViewportParams *const m_viewport;
0042     const MapQuality m_mapQuality;
0043     const int m_yTop;
0044     const int m_yBottom;
0045 };
0046 
0047 GenericScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
0048     : m_tileLoader( tileLoader ),
0049       m_tileLevel( tileLevel ),
0050       m_canvasImage( canvasImage ),
0051       m_viewport( viewport ),
0052       m_mapQuality( mapQuality ),
0053       m_yTop( yTop ),
0054       m_yBottom( yBottom )
0055 {
0056 }
0057 
0058 
0059 GenericScanlineTextureMapper::GenericScanlineTextureMapper( StackedTileLoader *tileLoader )
0060     : TextureMapperInterface()
0061     , m_tileLoader( tileLoader )
0062     , m_radius( 0 )
0063     , m_threadPool()
0064 {
0065 }
0066 
0067 void GenericScanlineTextureMapper::mapTexture( GeoPainter *painter,
0068                                                 const ViewportParams *viewport,
0069                                                 int tileZoomLevel,
0070                                                 const QRect &dirtyRect,
0071                                                 TextureColorizer *texColorizer )
0072 {
0073     if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
0074         const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );
0075 
0076         if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
0077             m_canvasImage = QImage( viewport->size(), optimalFormat );
0078         }
0079 
0080         if ( !viewport->mapCoversViewport() ) {
0081             m_canvasImage.fill( 0 );
0082         }
0083 
0084         m_radius = viewport->radius();
0085         m_repaintNeeded = true;
0086     }
0087 
0088     if ( m_repaintNeeded ) {
0089         mapTexture( viewport, tileZoomLevel, painter->mapQuality() );
0090 
0091         if ( texColorizer ) {
0092             texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
0093         }
0094 
0095         m_repaintNeeded = false;
0096     }
0097 
0098     const int radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
0099 
0100     QRect rect( viewport->width() / 2 - radius, viewport->height() / 2 - radius,
0101                 2 * radius, 2 * radius);
0102     rect = rect.intersected( dirtyRect );
0103     painter->drawImage( rect, m_canvasImage, rect );
0104 }
0105 
0106 void GenericScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
0107 {
0108     // Reset backend
0109     m_tileLoader->resetTilehash();
0110 
0111     const int imageHeight = viewport->height();
0112     const qint64  radius      = viewport->radius() * viewport->currentProjection()->clippingRadius();
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 GenericScanlineTextureMapper::RenderJob::run()
0137 {
0138     const int imageWidth  = m_canvasImage->width();
0139     const int imageHeight  = m_canvasImage->height();
0140     const qint64  radius  = m_viewport->radius();
0141 
0142     const bool interlaced   = ( m_mapQuality == LowQuality );
0143     const bool highQuality  = ( m_mapQuality == HighQuality
0144                              || m_mapQuality == PrintQuality );
0145     const bool printQuality = ( m_mapQuality == PrintQuality );
0146 
0147     // Evaluate the degree of interpolation
0148     const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );
0149 
0150     // Calculate north pole position to decrease pole distortion later on
0151     qreal northPoleX, northPoleY;
0152     bool globeHidesNorthPole;
0153     GeoDataCoordinates northPole(0, m_viewport->currentProjection()->maxLat(), 0);
0154     m_viewport->screenCoordinates(northPole, northPoleX, northPoleY, globeHidesNorthPole );
0155 
0156     // initialize needed variables that are modified during texture mapping:
0157 
0158     ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
0159 
0160     qreal clipRadius = radius * m_viewport->currentProjection()->clippingRadius();
0161 
0162 
0163     // Paint the map.
0164     for ( int y = m_yTop; y < m_yBottom; ++y ) {
0165 
0166         // rx is the radius component in x direction
0167         const int rx = (int)sqrt( (qreal)( clipRadius * clipRadius
0168                                       - ( ( y - imageHeight / 2 )
0169                                           * ( y - imageHeight / 2 ) ) ) );
0170 
0171         // Calculate the actual x-range of the map within the current scanline.
0172         //
0173         // If the circular border of the earth disk is still visible then xLeft
0174         // equals the scanline position of the most left pixel that gets covered
0175         // by the earth disk. In terms of math this equals the half image width minus
0176         // the radius component on the current scanline in x direction ("rx").
0177         //
0178         // If the zoom factor is high enough then the whole screen gets covered
0179         // by the earth and the border of the earth disk isn't visible anymore.
0180         // In that situation xLeft equals zero.
0181         // For xRight the situation is similar.
0182 
0183         const int xLeft  = ( imageWidth / 2 - rx > 0 ) ? imageWidth / 2 - rx
0184                                                        : 0;
0185         const int xRight = ( imageWidth / 2 - rx > 0 ) ? xLeft + rx + rx
0186                                                        : imageWidth;
0187 
0188         QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) ) + xLeft;
0189 
0190         const int xIpLeft  = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xLeft / n + 1 )
0191                                                          : 1;
0192         const int xIpRight = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xRight / n - 1 )
0193                                                          : n * (int)( xRight / n - 1 ) + 1;
0194 
0195         // Decrease pole distortion due to linear approximation ( y-axis )
0196         bool crossingPoleArea = false;
0197         if ( !globeHidesNorthPole
0198              && northPoleY - ( n * 0.75 ) <= y
0199              && northPoleY + ( n * 0.75 ) >= y )
0200         {
0201             crossingPoleArea = true;
0202         }
0203 
0204         int ncount = 0;
0205 
0206 
0207         for ( int x = xLeft; x < xRight; ++x ) {
0208 
0209             // Prepare for interpolation
0210             const int leftInterval = xIpLeft + ncount * n;
0211 
0212             bool interpolate = false;
0213 
0214             if ( x >= xIpLeft && x <= xIpRight ) {
0215 
0216                 // Decrease pole distortion due to linear approximation ( x-axis )
0217                 if ( crossingPoleArea
0218                      && northPoleX >= leftInterval + n
0219                      && northPoleX < leftInterval + 2 * n
0220                      && x < leftInterval + 3 * n )
0221                 {
0222                     interpolate = false;
0223                 }
0224                 else {
0225                     x += n - 1;
0226                     interpolate = !printQuality;
0227                     ++ncount;
0228                 }
0229             }
0230             else
0231                 interpolate = false;
0232 
0233             qreal lon;
0234             qreal lat;
0235             m_viewport->geoCoordinates(x,y, lon, lat, GeoDataCoordinates::Radian);
0236 
0237             if ( interpolate ) {
0238                 if ( highQuality )
0239                     context.pixelValueApproxF( lon, lat, scanLine, n );
0240                 else
0241                     context.pixelValueApprox( lon, lat, scanLine, n );
0242 
0243                 scanLine += ( n - 1 );
0244             }
0245 
0246             if ( x < imageWidth ) {
0247                 if ( highQuality )
0248                     context.pixelValueF( lon, lat, scanLine );
0249                 else
0250                     context.pixelValue( lon, lat, scanLine );
0251             }
0252 
0253             ++scanLine;
0254         }
0255 
0256         // copy scanline to improve performance
0257         if ( interlaced && y + 1 < m_yBottom ) {
0258 
0259             const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
0260 
0261             memcpy( m_canvasImage->scanLine( y + 1 ) + xLeft * pixelByteSize,
0262                     m_canvasImage->scanLine( y     ) + xLeft * pixelByteSize,
0263                     ( xRight - xLeft ) * pixelByteSize );
0264             ++y;
0265         }
0266     }
0267 }