Marble

GenericScanlineTextureMapper.cpp
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// local
9#include "GenericScanlineTextureMapper.h"
10
11// Qt
12#include <QRunnable>
13#include <qmath.h>
14
15// Marble
16#include "AbstractProjection.h"
17#include "GeoPainter.h"
18#include "MarbleDebug.h"
19#include "MarbleDirs.h"
20#include "MathHelper.h"
21#include "ScanlineTextureMapperContext.h"
22#include "StackedTileLoader.h"
23#include "TextureColorizer.h"
24#include "ViewParams.h"
25#include "ViewportParams.h"
26
27using namespace Marble;
28
29class GenericScanlineTextureMapper::RenderJob : public QRunnable
30{
31public:
32 RenderJob(StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom);
33
34 void run() override;
35
36private:
37 StackedTileLoader *const m_tileLoader;
38 const int m_tileLevel;
39 QImage *const m_canvasImage;
40 const ViewportParams *const m_viewport;
41 const MapQuality m_mapQuality;
42 const int m_yTop;
43 const int m_yBottom;
44};
45
46GenericScanlineTextureMapper::RenderJob::RenderJob(StackedTileLoader *tileLoader,
47 int tileLevel,
48 QImage *canvasImage,
49 const ViewportParams *viewport,
50 MapQuality mapQuality,
51 int yTop,
52 int yBottom)
53 : m_tileLoader(tileLoader)
54 , m_tileLevel(tileLevel)
55 , m_canvasImage(canvasImage)
56 , m_viewport(viewport)
57 , m_mapQuality(mapQuality)
58 , m_yTop(yTop)
59 , m_yBottom(yBottom)
60{
61}
62
63GenericScanlineTextureMapper::GenericScanlineTextureMapper(StackedTileLoader *tileLoader)
64 : TextureMapperInterface()
65 , m_tileLoader(tileLoader)
66 , m_radius(0)
67 , m_threadPool()
68{
69}
70
71void GenericScanlineTextureMapper::mapTexture(GeoPainter *painter,
72 const ViewportParams *viewport,
73 int tileZoomLevel,
74 const QRect &dirtyRect,
75 TextureColorizer *texColorizer)
76{
77 if (m_canvasImage.size() != viewport->size() || m_radius != viewport->radius()) {
78 const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat(viewport);
79
80 if (m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat) {
81 m_canvasImage = QImage(viewport->size(), optimalFormat);
82 }
83
84 if (!viewport->mapCoversViewport()) {
85 m_canvasImage.fill(0);
86 }
87
88 m_radius = viewport->radius();
89 m_repaintNeeded = true;
90 }
91
92 if (m_repaintNeeded) {
93 mapTexture(viewport, tileZoomLevel, painter->mapQuality());
94
95 if (texColorizer) {
96 texColorizer->colorize(&m_canvasImage, viewport, painter->mapQuality());
97 }
98
99 m_repaintNeeded = false;
100 }
101
102 const int radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
103
104 QRect rect(viewport->width() / 2 - radius, viewport->height() / 2 - radius, 2 * radius, 2 * radius);
105 rect = rect.intersected(dirtyRect);
106 painter->drawImage(rect, m_canvasImage, rect);
107}
108
109void GenericScanlineTextureMapper::mapTexture(const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality)
110{
111 // Reset backend
112 m_tileLoader->resetTilehash();
113
114 const int imageHeight = viewport->height();
115 const qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
116
117 // Calculate the actual y-range of the map on the screen
118 const int skip = (mapQuality == LowQuality) ? 1 : 0;
119 const int yTop = (imageHeight / 2 - radius >= 0) ? imageHeight / 2 - radius : 0;
120 const int yBottom = (yTop == 0) ? imageHeight - skip : 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
136void 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 || m_mapQuality == PrintQuality);
144 const bool printQuality = (m_mapQuality == PrintQuality);
145
146 // Evaluate the degree of interpolation
147 const int n = ScanlineTextureMapperContext::interpolationStep(m_viewport, m_mapQuality);
148
149 // Calculate north pole position to decrease pole distortion later on
150 qreal northPoleX, northPoleY;
151 bool globeHidesNorthPole;
152 GeoDataCoordinates northPole(0, m_viewport->currentProjection()->maxLat(), 0);
153 m_viewport->screenCoordinates(northPole, northPoleX, northPoleY, globeHidesNorthPole);
154
155 // initialize needed variables that are modified during texture mapping:
156
157 ScanlineTextureMapperContext context(m_tileLoader, m_tileLevel);
158
159 qreal clipRadius = radius * m_viewport->currentProjection()->clippingRadius();
160
161 // Paint the map.
162 for (int y = m_yTop; y < m_yBottom; ++y) {
163 // rx is the radius component in x direction
164 const int rx = (int)sqrt((qreal)(clipRadius * clipRadius - ((y - imageHeight / 2) * (y - imageHeight / 2))));
165
166 // Calculate the actual x-range of the map within the current scanline.
167 //
168 // If the circular border of the earth disk is still visible then xLeft
169 // equals the scanline position of the most left pixel that gets covered
170 // by the earth disk. In terms of math this equals the half image width minus
171 // the radius component on the current scanline in x direction ("rx").
172 //
173 // If the zoom factor is high enough then the whole screen gets covered
174 // by the earth and the border of the earth disk isn't visible anymore.
175 // In that situation xLeft equals zero.
176 // For xRight the situation is similar.
177
178 const int xLeft = (imageWidth / 2 - rx > 0) ? imageWidth / 2 - rx : 0;
179 const int xRight = (imageWidth / 2 - rx > 0) ? xLeft + rx + rx : imageWidth;
180
181 QRgb *scanLine = (QRgb *)(m_canvasImage->scanLine(y)) + xLeft;
182
183 const int xIpLeft = (imageWidth / 2 - rx > 0) ? n * (int)(xLeft / n + 1) : 1;
184 const int xIpRight = (imageWidth / 2 - rx > 0) ? n * (int)(xRight / n - 1) : n * (int)(xRight / n - 1) + 1;
185
186 // Decrease pole distortion due to linear approximation ( y-axis )
187 bool crossingPoleArea = false;
188 if (!globeHidesNorthPole && northPoleY - (n * 0.75) <= y && northPoleY + (n * 0.75) >= y) {
189 crossingPoleArea = true;
190 }
191
192 int ncount = 0;
193
194 for (int x = xLeft; x < xRight; ++x) {
195 // Prepare for interpolation
196 const int leftInterval = xIpLeft + ncount * n;
197
198 bool interpolate = false;
199
200 if (x >= xIpLeft && x <= xIpRight) {
201 // Decrease pole distortion due to linear approximation ( x-axis )
202 if (crossingPoleArea && northPoleX >= leftInterval + n && northPoleX < leftInterval + 2 * n && x < leftInterval + 3 * n) {
203 interpolate = false;
204 } else {
205 x += n - 1;
206 interpolate = !printQuality;
207 ++ncount;
208 }
209 } else
210 interpolate = false;
211
212 qreal lon;
213 qreal lat;
214 m_viewport->geoCoordinates(x, y, lon, lat, GeoDataCoordinates::Radian);
215
216 if (interpolate) {
217 if (highQuality)
218 context.pixelValueApproxF(lon, lat, scanLine, n);
219 else
220 context.pixelValueApprox(lon, lat, scanLine, n);
221
222 scanLine += (n - 1);
223 }
224
225 if (x < imageWidth) {
226 if (highQuality)
227 context.pixelValueF(lon, lat, scanLine);
228 else
229 context.pixelValue(lon, lat, scanLine);
230 }
231
232 ++scanLine;
233 }
234
235 // copy scanline to improve performance
236 if (interlaced && y + 1 < m_yBottom) {
237 const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;
238
239 memcpy(m_canvasImage->scanLine(y + 1) + xLeft * pixelByteSize,
240 m_canvasImage->scanLine(y) + xLeft * pixelByteSize,
241 (xRight - xLeft) * pixelByteSize);
242 ++y;
243 }
244 }
245}
This file contains the headers for AbstractProjection.
This file contains the headers for ViewParameters.
This file contains the headers for ViewportParams.
A painter that allows to draw geometric primitives on the map.
Definition GeoPainter.h:86
MapQuality mapQuality() const
Returns the map quality.
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...
Tile loading from a quad tree.
A public class that controls what is visible in the viewport of a Marble map.
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.
@ HighQuality
High quality (e.g. antialiasing for lines)
@ PrintQuality
Print quality.
@ LowQuality
Low resolution (e.g. interlaced)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:52:08 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.