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.
qreal maxLat() const
Returns the arbitrarily chosen maximum (northern) latitude.
A 3d point representation.
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.
void cleanupTilehash()
Cleans up the internal tile hash.
void resetTilehash()
Resets the internal tile hash.
A public class that controls what is visible in the viewport of a Marble map.
bool screenCoordinates(const qreal lon, const qreal lat, qreal &x, qreal &y) const
Get the screen coordinates corresponding to geographical coordinates in the map.
bool geoCoordinates(const int x, const int y, qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit=GeoDataCoordinates::Degree) const
Get the earth coordinates corresponding to a pixel in the 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)
qsizetype bytesPerLine() const const
void fill(Qt::GlobalColor color)
Format format() const const
int height() const const
uchar * scanLine(int i)
QSize size() const const
int width() const const
void start(Callable &&callableToRun, int priority)
bool waitForDone(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.