Marble

GenericScanlineTextureMapper.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Carlos Licea <carlos [email protected]>
4 // SPDX-FileCopyrightText: 2008 Inge Wallin <[email protected]>
5 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <[email protected]>
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 
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 
59 GenericScanlineTextureMapper::GenericScanlineTextureMapper( StackedTileLoader *tileLoader )
60  : TextureMapperInterface()
61  , m_tileLoader( tileLoader )
62  , m_radius( 0 )
63  , m_threadPool()
64 {
65 }
66 
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 
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 
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 }
MapQuality mapQuality() const
Returns the map quality.
Definition: GeoPainter.cpp:211
A 3d point representation.
@ LowQuality
Low resolution (e.g. interlaced)
Definition: MarbleGlobal.h:76
Tile loading from a quad tree.
A public class that controls what is visible in the viewport of a Marble map.
MapQuality
This enum is used to choose the map quality shown in the view.
Definition: MarbleGlobal.h:74
Binds a QML item to a specific geodetic location in screen coordinates.
KIOWIDGETS_EXPORT bool run(const QUrl &_url, bool _is_local)
A painter that allows to draw geometric primitives on the map.
Definition: GeoPainter.h:88
@ HighQuality
High quality (e.g. antialiasing for lines)
Definition: MarbleGlobal.h:78
@ PrintQuality
Print quality.
Definition: MarbleGlobal.h:79
void drawImage(const GeoDataCoordinates &centerPosition, const QImage &image)
Draws an image at the given position. The image is placed with its center located at the given center...
Definition: GeoPainter.cpp:466
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Dec 11 2023 04:09:39 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.