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