1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Carlos Licea <carlos _licea@hotmail.com>
4 // SPDX-FileCopyrightText: 2008 Inge Wallin <inge@lysator.liu.se>
5 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6 //
7 
8 
9 // local
10 #include "GenericScanlineTextureMapper.h"
11 
12 // Qt
13 #include <qmath.h>
14 #include <QRunnable>
15 
16 // Marble
17 #include "GeoPainter.h"
18 #include "MarbleDirs.h"
19 #include "MarbleDebug.h"
20 #include "ScanlineTextureMapperContext.h"
21 #include "StackedTileLoader.h"
22 #include "TextureColorizer.h"
23 #include "ViewParams.h"
24 #include "ViewportParams.h"
25 #include "MathHelper.h"
26 #include "AbstractProjection.h"
27 
28 using namespace Marble;
29 
30 class GenericScanlineTextureMapper::RenderJob : public QRunnable
31 {
32 public:
33     RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );
34 
35     void run() override;
36 
37 private:
38     StackedTileLoader *const m_tileLoader;
39     const int m_tileLevel;
40     QImage *const m_canvasImage;
41     const ViewportParams *const m_viewport;
42     const MapQuality m_mapQuality;
43     const int m_yTop;
44     const int m_yBottom;
45 };
46 
RenderJob(StackedTileLoader * tileLoader,int tileLevel,QImage * canvasImage,const ViewportParams * viewport,MapQuality mapQuality,int yTop,int yBottom)47 GenericScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
48     : m_tileLoader( tileLoader ),
49       m_tileLevel( tileLevel ),
50       m_canvasImage( canvasImage ),
51       m_viewport( viewport ),
52       m_mapQuality( mapQuality ),
53       m_yTop( yTop ),
54       m_yBottom( yBottom )
55 {
56 }
57 
58 
GenericScanlineTextureMapper(StackedTileLoader * tileLoader)59 GenericScanlineTextureMapper::GenericScanlineTextureMapper( StackedTileLoader *tileLoader )
60     : TextureMapperInterface()
61     , m_tileLoader( tileLoader )
62     , m_radius( 0 )
63     , m_threadPool()
64 {
65 }
66 
mapTexture(GeoPainter * painter,const ViewportParams * viewport,int tileZoomLevel,const QRect & dirtyRect,TextureColorizer * texColorizer)67 void GenericScanlineTextureMapper::mapTexture( GeoPainter *painter,
68                                                 const ViewportParams *viewport,
69                                                 int tileZoomLevel,
70                                                 const QRect &dirtyRect,
71                                                 TextureColorizer *texColorizer )
72 {
73     if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
74         const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );
75 
76         if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
77             m_canvasImage = QImage( viewport->size(), optimalFormat );
78         }
79 
80         if ( !viewport->mapCoversViewport() ) {
81             m_canvasImage.fill( 0 );
82         }
83 
84         m_radius = viewport->radius();
85         m_repaintNeeded = true;
86     }
87 
88     if ( m_repaintNeeded ) {
89         mapTexture( viewport, tileZoomLevel, painter->mapQuality() );
90 
91         if ( texColorizer ) {
92             texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
93         }
94 
95         m_repaintNeeded = false;
96     }
97 
98     const int radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
99 
100     QRect rect( viewport->width() / 2 - radius, viewport->height() / 2 - radius,
101                 2 * radius, 2 * radius);
102     rect = rect.intersected( dirtyRect );
103     painter->drawImage( rect, m_canvasImage, rect );
104 }
105 
mapTexture(const ViewportParams * viewport,int tileZoomLevel,MapQuality mapQuality)106 void GenericScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
107 {
108     // Reset backend
109     m_tileLoader->resetTilehash();
110 
111     const int imageHeight = viewport->height();
112     const qint64  radius      = viewport->radius() * viewport->currentProjection()->clippingRadius();
113 
114     // Calculate the actual y-range of the map on the screen
115     const int skip = ( mapQuality == LowQuality ) ? 1
116                                                   : 0;
117     const int yTop = ( imageHeight / 2 - radius >= 0 ) ? imageHeight / 2 - radius
118                                                        : 0;
119     const int yBottom = ( yTop == 0 ) ? imageHeight - skip
120                                       : yTop + radius + radius - skip;
121 
122     const int numThreads = m_threadPool.maxThreadCount();
123     const int yStep = qCeil(qreal( yBottom - yTop ) / qreal(numThreads));
124     for ( int i = 0; i < numThreads; ++i ) {
125         const int yStart = yTop +  i      * yStep;
126         const int yEnd   = qMin(yBottom, yTop + (i + 1) * yStep);
127         QRunnable *const job = new RenderJob( m_tileLoader, tileZoomLevel, &m_canvasImage, viewport, mapQuality, yStart, yEnd );
128         m_threadPool.start( job );
129     }
130 
131     m_threadPool.waitForDone();
132 
133     m_tileLoader->cleanupTilehash();
134 }
135 
run()136 void GenericScanlineTextureMapper::RenderJob::run()
137 {
138     const int imageWidth  = m_canvasImage->width();
139     const int imageHeight  = m_canvasImage->height();
140     const qint64  radius  = m_viewport->radius();
141 
142     const bool interlaced   = ( m_mapQuality == LowQuality );
143     const bool highQuality  = ( m_mapQuality == HighQuality
144                              || m_mapQuality == PrintQuality );
145     const bool printQuality = ( m_mapQuality == PrintQuality );
146 
147     // Evaluate the degree of interpolation
148     const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );
149 
150     // Calculate north pole position to decrease pole distortion later on
151     qreal northPoleX, northPoleY;
152     bool globeHidesNorthPole;
153     GeoDataCoordinates northPole(0, m_viewport->currentProjection()->maxLat(), 0);
154     m_viewport->screenCoordinates(northPole, northPoleX, northPoleY, globeHidesNorthPole );
155 
156     // initialize needed variables that are modified during texture mapping:
157 
158     ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
159 
160     qreal clipRadius = radius * m_viewport->currentProjection()->clippingRadius();
161 
162 
163     // Paint the map.
164     for ( int y = m_yTop; y < m_yBottom; ++y ) {
165 
166         // rx is the radius component in x direction
167         const int rx = (int)sqrt( (qreal)( clipRadius * clipRadius
168                                       - ( ( y - imageHeight / 2 )
169                                           * ( y - imageHeight / 2 ) ) ) );
170 
171         // Calculate the actual x-range of the map within the current scanline.
172         //
173         // If the circular border of the earth disk is still visible then xLeft
174         // equals the scanline position of the most left pixel that gets covered
175         // by the earth disk. In terms of math this equals the half image width minus
176         // the radius component on the current scanline in x direction ("rx").
177         //
178         // If the zoom factor is high enough then the whole screen gets covered
179         // by the earth and the border of the earth disk isn't visible anymore.
180         // In that situation xLeft equals zero.
181         // For xRight the situation is similar.
182 
183         const int xLeft  = ( imageWidth / 2 - rx > 0 ) ? imageWidth / 2 - rx
184                                                        : 0;
185         const int xRight = ( imageWidth / 2 - rx > 0 ) ? xLeft + rx + rx
186                                                        : imageWidth;
187 
188         QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) ) + xLeft;
189 
190         const int xIpLeft  = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xLeft / n + 1 )
191                                                          : 1;
192         const int xIpRight = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xRight / n - 1 )
193                                                          : n * (int)( xRight / n - 1 ) + 1;
194 
195         // Decrease pole distortion due to linear approximation ( y-axis )
196         bool crossingPoleArea = false;
197         if ( !globeHidesNorthPole
198              && northPoleY - ( n * 0.75 ) <= y
199              && northPoleY + ( n * 0.75 ) >= y )
200         {
201             crossingPoleArea = true;
202         }
203 
204         int ncount = 0;
205 
206 
207         for ( int x = xLeft; x < xRight; ++x ) {
208 
209             // Prepare for interpolation
210             const int leftInterval = xIpLeft + ncount * n;
211 
212             bool interpolate = false;
213 
214             if ( x >= xIpLeft && x <= xIpRight ) {
215 
216                 // Decrease pole distortion due to linear approximation ( x-axis )
217                 if ( crossingPoleArea
218                      && northPoleX >= leftInterval + n
219                      && northPoleX < leftInterval + 2 * n
220                      && x < leftInterval + 3 * n )
221                 {
222                     interpolate = false;
223                 }
224                 else {
225                     x += n - 1;
226                     interpolate = !printQuality;
227                     ++ncount;
228                 }
229             }
230             else
231                 interpolate = false;
232 
233             qreal lon;
234             qreal lat;
235             m_viewport->geoCoordinates(x,y, lon, lat, GeoDataCoordinates::Radian);
236 
237             if ( interpolate ) {
238                 if ( highQuality )
239                     context.pixelValueApproxF( lon, lat, scanLine, n );
240                 else
241                     context.pixelValueApprox( lon, lat, scanLine, n );
242 
243                 scanLine += ( n - 1 );
244             }
245 
246             if ( x < imageWidth ) {
247                 if ( highQuality )
248                     context.pixelValueF( lon, lat, scanLine );
249                 else
250                     context.pixelValue( lon, lat, scanLine );
251             }
252 
253             ++scanLine;
254         }
255 
256         // copy scanline to improve performance
257         if ( interlaced && y + 1 < m_yBottom ) {
258 
259             const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
260 
261             memcpy( m_canvasImage->scanLine( y + 1 ) + xLeft * pixelByteSize,
262                     m_canvasImage->scanLine( y     ) + xLeft * pixelByteSize,
263                     ( xRight - xLeft ) * pixelByteSize );
264             ++y;
265         }
266     }
267 }
268