1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Carlos Licea <carlos _licea@hotmail.com>
4 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
5 //
6 
7 
8 // local
9 #include"EquirectScanlineTextureMapper.h"
10 
11 // posix
12 #include <cmath>
13 
14 // Qt
15 #include <QRunnable>
16 
17 // Marble
18 #include "GeoPainter.h"
19 #include "MarbleDebug.h"
20 #include "ScanlineTextureMapperContext.h"
21 #include "StackedTileLoader.h"
22 #include "TextureColorizer.h"
23 #include "ViewportParams.h"
24 
25 using namespace Marble;
26 
27 class EquirectScanlineTextureMapper::RenderJob : public QRunnable
28 {
29 public:
30     RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewportParams, MapQuality mapQuality, int yTop, int yBottom );
31 
32     void run() override;
33 
34 private:
35     StackedTileLoader *const m_tileLoader;
36     const int m_tileLevel;
37     QImage *const m_canvasImage;
38     const ViewportParams *const m_viewport;
39     const MapQuality m_mapQuality;
40     const int m_yPaintedTop;
41     const int m_yPaintedBottom;
42 };
43 
RenderJob(StackedTileLoader * tileLoader,int tileLevel,QImage * canvasImage,const ViewportParams * viewport,MapQuality mapQuality,int yTop,int yBottom)44 EquirectScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
45     : m_tileLoader( tileLoader ),
46       m_tileLevel( tileLevel ),
47       m_canvasImage( canvasImage ),
48       m_viewport( viewport ),
49       m_mapQuality( mapQuality ),
50       m_yPaintedTop( yTop ),
51       m_yPaintedBottom( yBottom )
52 {
53 }
54 
55 
EquirectScanlineTextureMapper(StackedTileLoader * tileLoader)56 EquirectScanlineTextureMapper::EquirectScanlineTextureMapper( StackedTileLoader *tileLoader )
57     : TextureMapperInterface(),
58       m_tileLoader( tileLoader ),
59       m_radius( 0 ),
60       m_oldYPaintedTop( 0 )
61 {
62 }
63 
mapTexture(GeoPainter * painter,const ViewportParams * viewport,int tileZoomLevel,const QRect & dirtyRect,TextureColorizer * texColorizer)64 void EquirectScanlineTextureMapper::mapTexture( GeoPainter *painter,
65                                                 const ViewportParams *viewport,
66                                                 int tileZoomLevel,
67                                                 const QRect &dirtyRect,
68                                                 TextureColorizer *texColorizer )
69 {
70     if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
71         const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );
72 
73         if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
74             m_canvasImage = QImage( viewport->size(), optimalFormat );
75         }
76 
77         if ( !viewport->mapCoversViewport() ) {
78             m_canvasImage.fill( 0 );
79         }
80 
81         m_radius = viewport->radius();
82         m_repaintNeeded = true;
83     }
84 
85     if ( m_repaintNeeded ) {
86         mapTexture( viewport, tileZoomLevel, painter->mapQuality() );
87 
88         if ( texColorizer ) {
89             texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
90         }
91 
92         m_repaintNeeded = false;
93     }
94 
95     painter->drawImage( dirtyRect, m_canvasImage, dirtyRect );
96 }
97 
mapTexture(const ViewportParams * viewport,int tileZoomLevel,MapQuality mapQuality)98 void EquirectScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
99 {
100     // Reset backend
101     m_tileLoader->resetTilehash();
102 
103     // Initialize needed constants:
104 
105     const int imageHeight = m_canvasImage.height();
106     const qint64  radius      = viewport->radius();
107     // Calculate how many degrees are being represented per pixel.
108     const float rad2Pixel = (float)( 2 * radius ) / M_PI;
109 
110     // Calculate translation of center point
111     const qreal centerLat = viewport->centerLatitude();
112 
113     int yCenterOffset = (int)( centerLat * rad2Pixel );
114 
115     // Calculate y-range the represented by the center point, yTop and
116     // what actually can be painted
117     const int yTop     = imageHeight / 2 - radius + yCenterOffset;
118     int yPaintedTop    = imageHeight / 2 - radius + yCenterOffset;
119     int yPaintedBottom = imageHeight / 2 + radius + yCenterOffset;
120 
121     if (yPaintedTop < 0)                yPaintedTop = 0;
122     if (yPaintedTop > imageHeight)    yPaintedTop = imageHeight;
123     if (yPaintedBottom < 0)             yPaintedBottom = 0;
124     if (yPaintedBottom > imageHeight) yPaintedBottom = imageHeight;
125 
126     const int numThreads = m_threadPool.maxThreadCount();
127     const int yStep = ( yPaintedBottom - yPaintedTop ) / numThreads;
128     for ( int i = 0; i < numThreads; ++i ) {
129         const int yStart = yPaintedTop +  i      * yStep;
130         const int yEnd   = yPaintedTop + (i + 1) * yStep;
131         QRunnable *const job = new RenderJob( m_tileLoader, tileZoomLevel, &m_canvasImage, viewport, mapQuality, yStart, yEnd );
132         m_threadPool.start( job );
133     }
134 
135     // Remove unused lines
136     const int clearStart = ( yPaintedTop - m_oldYPaintedTop <= 0 ) ? yPaintedBottom : 0;
137     const int clearStop  = ( yPaintedTop - m_oldYPaintedTop <= 0 ) ? imageHeight  : yTop;
138 
139     QRgb * const itClearBegin = (QRgb*)( m_canvasImage.scanLine( clearStart ) );
140     QRgb * const itClearEnd = (QRgb*)( m_canvasImage.scanLine( clearStop ) );
141 
142     for ( QRgb * it = itClearBegin; it < itClearEnd; ++it ) {
143         *(it) = 0;
144     }
145 
146     m_threadPool.waitForDone();
147 
148     m_oldYPaintedTop = yPaintedTop;
149 
150     m_tileLoader->cleanupTilehash();
151 }
152 
run()153 void EquirectScanlineTextureMapper::RenderJob::run()
154 {
155     // Scanline based algorithm to do texture mapping
156 
157     const int imageHeight = m_canvasImage->height();
158     const int imageWidth  = m_canvasImage->width();
159     const qint64  radius  = m_viewport->radius();
160     // Calculate how many degrees are being represented per pixel.
161     const qreal rad2Pixel = (qreal)( 2 * radius ) / M_PI;
162     const float pixel2Rad = 1.0/rad2Pixel;  // FIXME changing to qreal may crash Marble when the equator is visible
163 
164     const bool interlaced   = ( m_mapQuality == LowQuality );
165     const bool highQuality  = ( m_mapQuality == HighQuality
166                              || m_mapQuality == PrintQuality );
167     const bool printQuality = ( m_mapQuality == PrintQuality );
168 
169     // Evaluate the degree of interpolation
170     const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );
171 
172     // Calculate translation of center point
173     const qreal centerLon = m_viewport->centerLongitude();
174     const qreal centerLat = m_viewport->centerLatitude();
175 
176     const int yCenterOffset = (int)( centerLat * rad2Pixel );
177 
178     const int yTop = imageHeight / 2 - radius + yCenterOffset;
179 
180     qreal leftLon = + centerLon - ( imageWidth / 2 * pixel2Rad );
181     while ( leftLon < -M_PI ) leftLon += 2 * M_PI;
182     while ( leftLon >  M_PI ) leftLon -= 2 * M_PI;
183 
184     const int maxInterpolationPointX = n * (int)( imageWidth / n - 1 ) + 1;
185 
186 
187     // initialize needed variables that are modified during texture mapping:
188 
189     ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
190 
191 
192     // Scanline based algorithm to do texture mapping
193 
194     for ( int y = m_yPaintedTop; y < m_yPaintedBottom; ++y ) {
195 
196         QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) );
197 
198         qreal lon = leftLon;
199         const qreal lat = M_PI/2 - (y - yTop )* pixel2Rad;
200 
201         for ( int x = 0; x < imageWidth; ++x ) {
202 
203             // Prepare for interpolation
204             bool interpolate = false;
205             if ( x > 0 && x <= maxInterpolationPointX ) {
206                 x += n - 1;
207                 lon += (n - 1) * pixel2Rad;
208                 interpolate = !printQuality;
209             }
210             else {
211                 interpolate = false;
212             }
213 
214             if ( lon < -M_PI ) lon += 2 * M_PI;
215             if ( lon >  M_PI ) lon -= 2 * M_PI;
216 
217             if ( interpolate ) {
218                 if (highQuality)
219                     context.pixelValueApproxF( lon, lat, scanLine, n );
220                 else
221                     context.pixelValueApprox( lon, lat, scanLine, n );
222 
223                 scanLine += ( n - 1 );
224             }
225 
226             if ( x < imageWidth ) {
227                 if ( highQuality )
228                     context.pixelValueF( lon, lat, scanLine );
229                 else
230                     context.pixelValue( lon, lat, scanLine );
231             }
232 
233             ++scanLine;
234             lon += pixel2Rad;
235         }
236 
237         // copy scanline to improve performance
238         if ( interlaced && y + 1 < m_yPaintedBottom ) {
239 
240             const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
241 
242             memcpy( m_canvasImage->scanLine( y + 1 ),
243                     m_canvasImage->scanLine( y     ),
244                     imageWidth * pixelByteSize );
245             ++y;
246         }
247     }
248 }
249