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 }