Marble

ScanlineTextureMapperContext.cpp
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#include "ScanlineTextureMapperContext.h"
8
9#include "GeoSceneAbstractTileProjection.h"
10#include "MarbleDebug.h"
11#include "StackedTile.h"
12#include "StackedTileLoader.h"
13#include "TileId.h"
14#include "ViewParams.h"
15#include "ViewportParams.h"
16
17using namespace Marble;
18
19ScanlineTextureMapperContext::ScanlineTextureMapperContext(StackedTileLoader *const tileLoader, int tileLevel)
20 : m_tileLoader(tileLoader)
21 , m_textureProjection(tileLoader->tileProjection()->type())
22 , // cache texture projection
23 m_tileSize(tileLoader->tileSize())
24 , // cache tile size
25 m_tileLevel(tileLevel)
26 , m_globalWidth(m_tileSize.width() * m_tileLoader->tileColumnCount(m_tileLevel))
27 , m_globalHeight(m_tileSize.height() * m_tileLoader->tileRowCount(m_tileLevel))
28 , m_normGlobalWidth(m_globalWidth / (2 * M_PI))
29 , m_normGlobalHeight(m_globalHeight / M_PI)
30 , m_tile(nullptr)
31 , m_tilePosX(65535)
32 , m_tilePosY(65535)
33 , m_toTileCoordinatesLon(0.5 * m_globalWidth - m_tilePosX)
34 , m_toTileCoordinatesLat(0.5 * m_globalHeight - m_tilePosY)
35 , m_prevLat(0.0)
36 , m_prevLon(0.0)
37 , m_prevPixelX(0.0)
38 , m_prevPixelY(0.0)
39{
40}
41
42void ScanlineTextureMapperContext::pixelValueF(const qreal lon, const qreal lat, QRgb *const scanLine)
43{
44 // The same method using integers performs about 33% faster.
45 // However we need the qreal version to create the high quality mode.
46
47 // Convert the lon and lat coordinates of the position on the scanline
48 // measured in radian to the pixel position of the requested
49 // coordinate on the current tile.
50
51 m_prevPixelX = rad2PixelX(lon);
52 m_prevPixelY = rad2PixelY(lat);
53
54 qreal posX = m_toTileCoordinatesLon + m_prevPixelX;
55 qreal posY = m_toTileCoordinatesLat + m_prevPixelY;
56
57 // Most of the time while moving along the scanLine we'll stay on the
58 // same tile. However at the tile border we might "fall off". If that
59 // happens we need to find out the next tile that needs to be loaded.
60
61 if (posX >= (qreal)(m_tileSize.width()) || posX < 0.0 || posY >= (qreal)(m_tileSize.height()) || posY < 0.0) {
62 nextTile(posX, posY);
63 }
64 if (m_tile) {
65 *scanLine = m_tile->pixelF(posX, posY);
66 } else {
67 *scanLine = 0;
68 }
69
70 m_prevLon = lon;
71 m_prevLat = lat; // preparing for interpolation
72}
73
74void ScanlineTextureMapperContext::pixelValue(const qreal lon, const qreal lat, QRgb *const scanLine)
75{
76 // The same method using integers performs about 33% faster.
77 // However we need the qreal version to create the high quality mode.
78
79 // Convert the lon and lat coordinates of the position on the scanline
80 // measured in radian to the pixel position of the requested
81 // coordinate on the current tile.
82
83 m_prevPixelX = rad2PixelX(lon);
84 m_prevPixelY = rad2PixelY(lat);
85 int iPosX = (int)(m_toTileCoordinatesLon + m_prevPixelX);
86 int iPosY = (int)(m_toTileCoordinatesLat + m_prevPixelY);
87
88 // Most of the time while moving along the scanLine we'll stay on the
89 // same tile. However at the tile border we might "fall off". If that
90 // happens we need to find out the next tile that needs to be loaded.
91
92 if (iPosX >= m_tileSize.width() || iPosX < 0 || iPosY >= m_tileSize.height() || iPosY < 0) {
93 nextTile(iPosX, iPosY);
94 }
95
96 if (m_tile) {
97 *scanLine = m_tile->pixel(iPosX, iPosY);
98 } else {
99 *scanLine = 0;
100 }
101
102 m_prevLon = lon;
103 m_prevLat = lat; // preparing for interpolation
104}
105
106// This method interpolates color values for skipped pixels in a scanline.
107//
108// While moving along the scanline we don't move from pixel to pixel but
109// leave out n pixels each time and calculate the exact position and
110// color value for the new pixel. The pixel values in between get
111// approximated through linear interpolation across the direct connecting
112// line on the original tiles directly.
113// This method will do by far most of the calculations for the
114// texturemapping, so we move towards integer math to improve speed.
115
116void ScanlineTextureMapperContext::pixelValueApproxF(const qreal lon, const qreal lat, QRgb *scanLine, const int n)
117{
118 // stepLon/Lat: Distance between two subsequent approximated positions
119
120 qreal stepLat = lat - m_prevLat;
121 qreal stepLon = lon - m_prevLon;
122
123 // As long as the distance is smaller than 180 deg we can assume that
124 // we didn't cross the dateline.
125
126 const qreal nInverse = 1.0 / (qreal)(n);
127
128 if (fabs(stepLon) < M_PI) {
129 const qreal itStepLon = (rad2PixelX(lon) - m_prevPixelX) * nInverse;
130 const qreal itStepLat = (rad2PixelY(lat) - m_prevPixelY) * nInverse;
131
132 // To improve speed we unroll
133 // AbstractScanlineTextureMapper::pixelValue(...) here and
134 // calculate the performance critical issues via integers
135
136 qreal itLon = m_prevPixelX + m_toTileCoordinatesLon;
137 qreal itLat = m_prevPixelY + m_toTileCoordinatesLat;
138
139 const int tileWidth = m_tileSize.width();
140 const int tileHeight = m_tileSize.height();
141
142 // int oldR = 0;
143 // int oldG = 0;
144 // int oldB = 0;
145
146 QRgb oldRgb = qRgb(0, 0, 0);
147
148 qreal oldPosX = -1;
149 qreal oldPosY = 0;
150
151 const bool alwaysCheckTileRange = isOutOfTileRangeF(itLon, itLat, itStepLon, itStepLat, n);
152
153 for (int j = 1; j < n; ++j) {
154 qreal posX = itLon + itStepLon * j;
155 qreal posY = itLat + itStepLat * j;
156 if (alwaysCheckTileRange)
157 if (posX >= tileWidth || posX < 0.0 || posY >= tileHeight || posY < 0.0) {
158 nextTile(posX, posY);
159 itLon = m_prevPixelX + m_toTileCoordinatesLon;
160 itLat = m_prevPixelY + m_toTileCoordinatesLat;
161 posX = qBound<qreal>(0.0, (itLon + itStepLon * j), tileWidth - 1.0);
162 posY = qBound<qreal>(0.0, (itLat + itStepLat * j), tileHeight - 1.0);
163 oldPosX = -1;
164 }
165
166 *scanLine = m_tile->pixelF(posX, posY);
167
168 // Just perform bilinear interpolation if there's a color change compared to the
169 // last pixel that was evaluated. This speeds up things greatly for maps like OSM
170 if (*scanLine != oldRgb) {
171 if (oldPosX != -1) {
172 *(scanLine - 1) = m_tile->pixelF(oldPosX, oldPosY, *(scanLine - 1));
173 oldPosX = -1;
174 }
175 oldRgb = m_tile->pixelF(posX, posY, *scanLine);
176 *scanLine = oldRgb;
177 } else {
178 oldPosX = posX;
179 oldPosY = posY;
180 }
181
182 // if ( needsFilter( *scanLine, oldR, oldB, oldG ) ) {
183 // *scanLine = m_tile->pixelF( posX, posY );
184 // }
185
186 ++scanLine;
187 }
188 }
189
190 // For the case where we cross the dateline between (lon, lat) and
191 // (prevlon, prevlat) we need a more sophisticated calculation.
192 // However as this will happen rather rarely, we use
193 // pixelValue(...) directly to make the code more readable.
194
195 else {
196 stepLon = (TWOPI - fabs(stepLon)) * nInverse;
197 stepLat = stepLat * nInverse;
198 // We need to distinguish two cases:
199 // crossing the dateline from east to west ...
200
201 if (m_prevLon < lon) {
202 for (int j = 1; j < n; ++j) {
203 m_prevLat += stepLat;
204 m_prevLon -= stepLon;
205 if (m_prevLon <= -M_PI)
206 m_prevLon += TWOPI;
207 pixelValueF(m_prevLon, m_prevLat, scanLine);
208 ++scanLine;
209 }
210 }
211
212 // ... and vice versa: from west to east.
213
214 else {
215 qreal curStepLon = lon - n * stepLon;
216
217 for (int j = 1; j < n; ++j) {
218 m_prevLat += stepLat;
219 curStepLon += stepLon;
220 qreal evalLon = curStepLon;
221 if (curStepLon <= -M_PI)
222 evalLon += TWOPI;
223 pixelValueF(evalLon, m_prevLat, scanLine);
224 ++scanLine;
225 }
226 }
227 }
228}
229
230bool ScanlineTextureMapperContext::isOutOfTileRangeF(const qreal itLon, const qreal itLat, const qreal itStepLon, const qreal itStepLat, const int n) const
231{
232 const qreal minIPosX = itLon + itStepLon;
233 const qreal minIPosY = itLat + itStepLat;
234 const qreal maxIPosX = itLon + itStepLon * (n - 1);
235 const qreal maxIPosY = itLat + itStepLat * (n - 1);
236 return (maxIPosX >= m_tileSize.width() || maxIPosX < 0 || maxIPosY >= m_tileSize.height() || maxIPosY < 0 || minIPosX >= m_tileSize.width() || minIPosX < 0
237 || minIPosY >= m_tileSize.height() || minIPosY < 0);
238}
239
240void ScanlineTextureMapperContext::pixelValueApprox(const qreal lon, const qreal lat, QRgb *scanLine, const int n)
241{
242 // stepLon/Lat: Distance between two subsequent approximated positions
243
244 qreal stepLat = lat - m_prevLat;
245 qreal stepLon = lon - m_prevLon;
246
247 // As long as the distance is smaller than 180 deg we can assume that
248 // we didn't cross the dateline.
249
250 const qreal nInverse = 1.0 / (qreal)(n);
251
252 if (fabs(stepLon) < M_PI) {
253 const int itStepLon = (int)((rad2PixelX(lon) - m_prevPixelX) * nInverse * 128.0);
254 const int itStepLat = (int)((rad2PixelY(lat) - m_prevPixelY) * nInverse * 128.0);
255
256 // To improve speed we unroll
257 // AbstractScanlineTextureMapper::pixelValue(...) here and
258 // calculate the performance critical issues via integers
259
260 int itLon = (int)((m_prevPixelX + m_toTileCoordinatesLon) * 128.0);
261 int itLat = (int)((m_prevPixelY + m_toTileCoordinatesLat) * 128.0);
262
263 const int tileWidth = m_tileSize.width();
264 const int tileHeight = m_tileSize.height();
265
266 const bool alwaysCheckTileRange = isOutOfTileRange(itLon, itLat, itStepLon, itStepLat, n);
267
268 if (!alwaysCheckTileRange) {
269 int iPosXf = itLon;
270 int iPosYf = itLat;
271 for (int j = 1; j < n; ++j) {
272 iPosXf += itStepLon;
273 iPosYf += itStepLat;
274 *scanLine = m_tile->pixel(iPosXf >> 7, iPosYf >> 7);
275 ++scanLine;
276 }
277 } else {
278 for (int j = 1; j < n; ++j) {
279 int iPosX = (itLon + itStepLon * j) >> 7;
280 int iPosY = (itLat + itStepLat * j) >> 7;
281
282 if (iPosX >= tileWidth || iPosX < 0 || iPosY >= tileHeight || iPosY < 0) {
283 nextTile(iPosX, iPosY);
284 itLon = (int)((m_prevPixelX + m_toTileCoordinatesLon) * 128.0);
285 itLat = (int)((m_prevPixelY + m_toTileCoordinatesLat) * 128.0);
286 iPosX = (itLon + itStepLon * j) >> 7;
287 iPosY = (itLat + itStepLat * j) >> 7;
288 // Bug 453332: Crash on Panning with Equirectangular Projection:
289 // https://bugs.kde.org/show_bug.cgi?id=453332
290 // Apparently at the edge of the latitude range a rounding error can move
291 // iPosY out of tile coords and nextTile will just reload the same tile.
292 // So let's just ensure that we stay in the expected range:
293 if (iPosY >= tileHeight)
294 iPosY = tileHeight - 1;
295 if (iPosY < 0)
296 iPosY = 0;
297 }
298
299 *scanLine = m_tile->pixel(iPosX, iPosY);
300 ++scanLine;
301 }
302 }
303 }
304
305 // For the case where we cross the dateline between (lon, lat) and
306 // (prevlon, prevlat) we need a more sophisticated calculation.
307 // However as this will happen rather rarely, we use
308 // pixelValue(...) directly to make the code more readable.
309
310 else {
311 stepLon = (TWOPI - fabs(stepLon)) * nInverse;
312 stepLat = stepLat * nInverse;
313 // We need to distinguish two cases:
314 // crossing the dateline from east to west ...
315
316 if (m_prevLon < lon) {
317 for (int j = 1; j < n; ++j) {
318 m_prevLat += stepLat;
319 m_prevLon -= stepLon;
320 if (m_prevLon <= -M_PI)
321 m_prevLon += TWOPI;
322 pixelValue(m_prevLon, m_prevLat, scanLine);
323 ++scanLine;
324 }
325 }
326
327 // ... and vice versa: from west to east.
328
329 else {
330 qreal curStepLon = lon - n * stepLon;
331
332 for (int j = 1; j < n; ++j) {
333 m_prevLat += stepLat;
334 curStepLon += stepLon;
335 qreal evalLon = curStepLon;
336 if (curStepLon <= -M_PI)
337 evalLon += TWOPI;
338 pixelValue(evalLon, m_prevLat, scanLine);
339 ++scanLine;
340 }
341 }
342 }
343}
344
345bool ScanlineTextureMapperContext::isOutOfTileRange(const int itLon, const int itLat, const int itStepLon, const int itStepLat, const int n) const
346{
347 const int minIPosX = (itLon + itStepLon) >> 7;
348 const int minIPosY = (itLat + itStepLat) >> 7;
349 const int maxIPosX = (itLon + itStepLon * (n - 1)) >> 7;
350 const int maxIPosY = (itLat + itStepLat * (n - 1)) >> 7;
351 return (maxIPosX >= m_tileSize.width() || maxIPosX < 0 || maxIPosY >= m_tileSize.height() || maxIPosY < 0 || minIPosX >= m_tileSize.width() || minIPosX < 0
352 || minIPosY >= m_tileSize.height() || minIPosY < 0);
353}
354
355int ScanlineTextureMapperContext::interpolationStep(const ViewportParams *viewport, MapQuality mapQuality)
356{
357 if (mapQuality == PrintQuality) {
358 return 1; // Don't interpolate for print quality.
359 }
360
361 if (!viewport->mapCoversViewport()) {
362 return 8;
363 }
364
365 // Find the optimal interpolation interval m_nBest for the
366 // current image canvas width
367 const int width = viewport->width();
368
369 int nBest = 2;
370 int nEvalMin = width - 1;
371 for (int it = 1; it < 48; ++it) {
372 int nEval = (width - 1) / it + (width - 1) % it;
373 if (nEval < nEvalMin) {
374 nEvalMin = nEval;
375 nBest = it;
376 }
377 }
378
379 return nBest;
380}
381
382QImage::Format ScanlineTextureMapperContext::optimalCanvasImageFormat(const ViewportParams *viewport)
383{
384 // If the globe covers fully the screen then we can use the faster
385 // RGB32 as there are no translucent areas involved.
386 QImage::Format imageFormat = (viewport->mapCoversViewport()) ? QImage::Format_RGB32 : QImage::Format_ARGB32_Premultiplied;
387
388 return imageFormat;
389}
390
391void ScanlineTextureMapperContext::nextTile(int &posX, int &posY)
392{
393 // Move from tile coordinates to global texture coordinates
394 // ( with origin in upper left corner, measured in pixel)
395
396 int lon = posX + m_tilePosX;
397 if (lon >= m_globalWidth)
398 lon -= m_globalWidth;
399 else if (lon < 0)
400 lon += m_globalWidth;
401
402 int lat = posY + m_tilePosY;
403 if (lat >= m_globalHeight)
404 lat -= m_globalHeight;
405 else if (lat < 0)
406 lat += m_globalHeight;
407
408 // tileCol counts the tile columns left from the current tile.
409 // tileRow counts the tile rows on the top from the current tile.
410
411 const int tileCol = lon / m_tileSize.width();
412 const int tileRow = lat / m_tileSize.height();
413
414 m_tile = m_tileLoader->loadTile(TileId(0, m_tileLevel, tileCol, tileRow));
415
416 // Update position variables:
417 // m_tilePosX/Y stores the position of the tiles in
418 // global texture coordinates
419 // ( origin upper left, measured in pixels )
420
421 m_tilePosX = tileCol * m_tileSize.width();
422 m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX);
423 posX = lon - m_tilePosX;
424
425 m_tilePosY = tileRow * m_tileSize.height();
426 m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY);
427 posY = lat - m_tilePosY;
428}
429
430void ScanlineTextureMapperContext::nextTile(qreal &posX, qreal &posY)
431{
432 // Move from tile coordinates to global texture coordinates
433 // ( with origin in upper left corner, measured in pixel)
434
435 int lon = (int)(posX + m_tilePosX);
436 if (lon >= m_globalWidth)
437 lon -= m_globalWidth;
438 else if (lon < 0)
439 lon += m_globalWidth;
440
441 int lat = (int)(posY + m_tilePosY);
442 if (lat >= m_globalHeight)
443 lat -= m_globalHeight;
444 else if (lat < 0)
445 lat += m_globalHeight;
446
447 // tileCol counts the tile columns left from the current tile.
448 // tileRow counts the tile rows on the top from the current tile.
449
450 const int tileCol = lon / m_tileSize.width();
451 const int tileRow = lat / m_tileSize.height();
452
453 m_tile = m_tileLoader->loadTile(TileId(0, m_tileLevel, tileCol, tileRow));
454
455 // Update position variables:
456 // m_tilePosX/Y stores the position of the tiles in
457 // global texture coordinates
458 // ( origin upper left, measured in pixels )
459
460 m_tilePosX = tileCol * m_tileSize.width();
461 m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX);
462 posX = lon - m_tilePosX;
463
464 m_tilePosY = tileRow * m_tileSize.height();
465 m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY);
466 posY = lat - m_tilePosY;
467}
This file contains the headers for ViewParameters.
This file contains the headers for ViewportParams.
Tile loading from a quad tree.
const StackedTile * loadTile(TileId const &stackedTileId)
Loads a tile and returns it.
uint pixelF(qreal x, qreal y) const
Returns the color value of the result tile at a given floating point position.
uint pixel(int x, int y) const
Returns the color value of the result tile at the given integer position.
A public class that controls what is visible in the viewport of a Marble map.
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
Binds a QML item to a specific geodetic location in screen coordinates.
MapQuality
This enum is used to choose the map quality shown in the view.
@ PrintQuality
Print quality.
int height() const const
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:37:04 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.