Marble

TextureColorizer.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4// SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
5// SPDX-FileCopyrightText: 2008 Carlos Licea <carlos.licea@kdemail.net>
6// SPDX-FileCopyrightText: 2012 Cezar Mocan <mocancezar@gmail.com>
7//
8
9#include "TextureColorizer.h"
10
11#include <QElapsedTimer>
12#include <QFile>
13#include <QList>
14#include <QPainter>
15#include <QSharedPointer>
16#include <qmath.h>
17
18#include "AbstractProjection.h"
19#include "GeoDataFeature.h"
20#include "GeoDataLinearRing.h"
21#include "GeoDataPlacemark.h"
22#include "GeoDataPolygon.h"
23#include "GeoPainter.h"
24#include "MarbleDebug.h"
25#include "MathHelper.h"
26#include "ViewParams.h"
27#include "ViewportParams.h"
28
29namespace Marble
30{
31
32// 4 uchar long queue
33class EmbossFifo
34{
35public:
36 EmbossFifo()
37 : data(0)
38 {
39 }
40
41 inline uchar head() const
42 {
43 // return least significant byte as head of queue
44 return data & 0x000000FF;
45 }
46
47 inline void enqueue(uchar value)
48 {
49 // drop current head by shifting by one byte
50 // and append new value as most significant byte to queue
51 data = ((data >> 8) & 0x00FFFFFF) | (value << 24);
52 }
53
54private:
55 // 4 byte long queue
56 quint32 data;
57};
58
59TextureColorizer::TextureColorizer(const QString &seafile, const QString &landfile)
60 : m_showRelief(false)
61 , m_landColor(qRgb(255, 0, 0))
62 , m_seaColor(qRgb(0, 255, 0))
63{
65 t.start();
66
67 QImage gradientImage(256, 1, QImage::Format_RGB32);
68 QPainter gradientPainter;
69 gradientPainter.begin(&gradientImage);
70 gradientPainter.setPen(Qt::NoPen);
71
72 int shadingStart = 120;
73 QImage shadingImage(16, 1, QImage::Format_RGB32);
74 QPainter shadingPainter;
75 shadingPainter.begin(&shadingImage);
76 shadingPainter.setPen(Qt::NoPen);
77
78 int offset = 0;
79
80 QStringList filelist;
81 filelist << seafile << landfile;
82
83 for (const QString &filename : std::as_const(filelist)) {
84 QLinearGradient gradient(0, 0, 256, 0);
85
86 QFile file(filename);
87 file.open(QIODevice::ReadOnly);
88 QTextStream stream(&file); // read the data from the file
89
90 QString evalstrg;
91
92 while (!stream.atEnd()) {
93 stream >> evalstrg;
94 if (!evalstrg.isEmpty() && evalstrg.contains(QLatin1Char('='))) {
95 QString colorValue = evalstrg.left(evalstrg.indexOf(QLatin1Char('=')));
96 QString colorPosition = evalstrg.mid(evalstrg.indexOf(QLatin1Char('=')) + 1);
97 gradient.setColorAt(colorPosition.toDouble(), QColor(colorValue));
98 }
99 }
100 gradientPainter.setBrush(gradient);
101 gradientPainter.drawRect(0, 0, 256, 1);
102
103 QLinearGradient shadeGradient(-shadingStart, 0, 256 - shadingStart, 0);
104
105 shadeGradient.setColorAt(0.00, QColor(Qt::white));
106 shadeGradient.setColorAt(0.15, QColor(Qt::white));
107 shadeGradient.setColorAt(0.75, QColor(Qt::black));
108 shadeGradient.setColorAt(1.00, QColor(Qt::black));
109
110 const QRgb *gradientScanLine = (QRgb *)(gradientImage.scanLine(0));
111 const QRgb *shadingScanLine = (QRgb *)(shadingImage.scanLine(0));
112
113 for (int i = 0; i < 256; ++i) {
114 QRgb shadeColor = *(gradientScanLine + i);
115 shadeGradient.setColorAt(0.496, shadeColor);
116 shadeGradient.setColorAt(0.504, shadeColor);
117 shadingPainter.setBrush(shadeGradient);
118 shadingPainter.drawRect(0, 0, 16, 1);
119
120 // populate texturepalette[][]
121 for (int j = 0; j < 16; ++j) {
122 texturepalette[j][offset + i] = *(shadingScanLine + j);
123 }
124 }
125
126 offset += 256;
127 }
128 shadingPainter.end(); // Need to explicitly tell painter lifetime to avoid crash
129 gradientPainter.end(); // on some systems.
130
131 mDebug() << "Time elapsed:" << t.elapsed() << "ms";
132}
133
134void TextureColorizer::addSeaDocument(const GeoDataDocument *seaDocument)
135{
136 m_seaDocuments.append(seaDocument);
137}
138
139void TextureColorizer::addLandDocument(const GeoDataDocument *landDocument)
140{
141 m_landDocuments.append(landDocument);
142}
143
144void TextureColorizer::setShowRelief(bool show)
145{
146 m_showRelief = show;
147}
148
149// This function takes two images, both in viewParams:
150// - The coast image, which has a number of colors where each color
151// represents a sort of terrain (ex: land/sea)
152// - The canvas image, which has a gray scale image, often
153// representing a height field.
154//
155// It then uses the values of the pixels in the coast image to select
156// a color map. The value of the pixel in the canvas image is used as
157// an index into the selected color map and the resulting color is
158// written back to the canvas image. This way we can have different
159// color schemes for land and water.
160//
161// In addition to this, a simple form of bump mapping is performed to
162// increase the illusion of height differences (see the variable
163// showRelief).
164//
165
166void TextureColorizer::drawIndividualDocument(GeoPainter *painter, const GeoDataDocument *document)
167{
168 QList<GeoDataFeature *>::ConstIterator i = document->constBegin();
169 QList<GeoDataFeature *>::ConstIterator end = document->constEnd();
170
171 for (; i != end; ++i) {
172 if (const GeoDataPlacemark *placemark = geodata_cast<GeoDataPlacemark>(*i)) {
173 if (const auto child = geodata_cast<GeoDataLineString>(placemark->geometry())) {
174 const GeoDataLinearRing ring(*child);
175 painter->drawPolygon(ring);
176 }
177
178 if (const auto child = geodata_cast<GeoDataPolygon>(placemark->geometry())) {
179 painter->drawPolygon(*child);
180 }
181
182 if (const auto child = geodata_cast<GeoDataLinearRing>(placemark->geometry())) {
183 painter->drawPolygon(*child);
184 }
185 }
186 }
187}
188
189void TextureColorizer::drawTextureMap(GeoPainter *painter)
190{
191 for (const GeoDataDocument *doc : std::as_const(m_landDocuments)) {
192 painter->setPen(QPen(Qt::NoPen));
193 painter->setBrush(QBrush(m_landColor));
194 drawIndividualDocument(painter, doc);
195 }
196
197 for (const GeoDataDocument *doc : std::as_const(m_seaDocuments)) {
198 if (doc->isVisible()) {
199 painter->setPen(Qt::NoPen);
200 painter->setBrush(QBrush(m_seaColor));
201 drawIndividualDocument(painter, doc);
202 }
203 }
204}
205
206void TextureColorizer::colorize(QImage *origimg, const ViewportParams *viewport, MapQuality mapQuality)
207{
208 if (m_coastImage.size() != viewport->size())
209 m_coastImage = QImage(viewport->size(), QImage::Format_RGB32);
210
211 // update coast image
212 m_coastImage.fill(QColor(0, 0, 255, 0).rgb());
213
214 const bool antialiased = mapQuality == HighQuality || mapQuality == PrintQuality;
215
216 GeoPainter painter(&m_coastImage, viewport, mapQuality);
217 painter.setRenderHint(QPainter::Antialiasing, antialiased);
218
219 drawTextureMap(&painter);
220
221 const qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
222
223 const int imgheight = origimg->height();
224 const int imgwidth = origimg->width();
225 const int imgrx = imgwidth / 2;
226 const int imgry = imgheight / 2;
227 // This variable is not used anywhere..
228 const int imgradius = imgrx * imgrx + imgry * imgry;
229
230 int bump = 8;
231
232 if (radius * radius > imgradius || !viewport->currentProjection()->isClippedToSphere()) {
233 int yTop = 0;
234 int yBottom = imgheight;
235
236 if (!viewport->currentProjection()->isClippedToSphere() && !viewport->currentProjection()->traversablePoles()) {
237 qreal realYTop, realYBottom, dummyX;
238 GeoDataCoordinates yNorth(0, viewport->currentProjection()->maxLat(), 0);
239 GeoDataCoordinates ySouth(0, viewport->currentProjection()->minLat(), 0);
240 viewport->screenCoordinates(yNorth, dummyX, realYTop);
241 viewport->screenCoordinates(ySouth, dummyX, realYBottom);
242 yTop = qBound(qreal(0.0), realYTop, qreal(imgheight));
243 yBottom = qBound(qreal(0.0), realYBottom, qreal(imgheight));
244 }
245
246 const int itEnd = yBottom;
247
248 for (int y = yTop; y < itEnd; ++y) {
249 QRgb *writeData = (QRgb *)(origimg->scanLine(y));
250 const QRgb *coastData = (QRgb *)(m_coastImage.scanLine(y));
251
252 uchar *readDataStart = origimg->scanLine(y);
253 const uchar *readDataEnd = readDataStart + imgwidth * 4;
254
255 EmbossFifo emboss;
256
257 for (uchar *readData = readDataStart; readData < readDataEnd; readData += 4, ++writeData, ++coastData) {
258 // Cheap Emboss / Bumpmapping
259 uchar &grey = *readData; // qBlue(*data);
260
261 if (m_showRelief) {
262 emboss.enqueue(grey);
263 bump = (emboss.head() + 8 - grey);
264 if (bump < 0) {
265 bump = 0;
266 } else if (bump > 15) {
267 bump = 15;
268 }
269 }
270 setPixel(coastData, writeData, bump, grey);
271 }
272 }
273 } else {
274 int yTop = (imgry - radius < 0) ? 0 : imgry - radius;
275 const int yBottom = (yTop == 0) ? imgheight : imgry + radius;
276
277 EmbossFifo emboss;
278
279 for (int y = yTop; y < yBottom; ++y) {
280 const int dy = imgry - y;
281 int rx = (int)sqrt((qreal)(radius * radius - dy * dy));
282 int xLeft = 0;
283 int xRight = imgwidth;
284
285 if (imgrx - rx > 0) {
286 xLeft = imgrx - rx;
287 xRight = imgrx + rx;
288 }
289
290 QRgb *writeData = (QRgb *)(origimg->scanLine(y)) + xLeft;
291 const QRgb *coastData = (QRgb *)(m_coastImage.scanLine(y)) + xLeft;
292
293 uchar *readDataStart = origimg->scanLine(y) + xLeft * 4;
294 const uchar *readDataEnd = origimg->scanLine(y) + xRight * 4;
295
296 for (uchar *readData = readDataStart; readData < readDataEnd; readData += 4, ++writeData, ++coastData) {
297 // Cheap Emboss / Bumpmapping
298
299 uchar &grey = *readData; // qBlue(*data);
300
301 if (m_showRelief) {
302 emboss.enqueue(grey);
303 bump = (emboss.head() + 16 - grey) >> 1;
304 if (bump < 0) {
305 bump = 0;
306 } else if (bump > 15) {
307 bump = 15;
308 }
309 }
310 setPixel(coastData, writeData, bump, grey);
311 }
312 }
313 }
314}
315
316void TextureColorizer::setPixel(const QRgb *coastData, QRgb *writeData, int bump, uchar grey)
317{
318 int alpha = qRed(*coastData);
319 if (alpha == 255)
320 *writeData = texturepalette[bump][grey + 0x100];
321 else if (alpha == 0) {
322 *writeData = texturepalette[bump][grey];
323 } else {
324 qreal c = 1.0 / 255.0;
325
326 QRgb landcolor = (QRgb)(texturepalette[bump][grey + 0x100]);
327 QRgb watercolor = (QRgb)(texturepalette[bump][grey]);
328
329 *writeData = qRgb((int)(c * (alpha * qRed(landcolor) + (255 - alpha) * qRed(watercolor))),
330 (int)(c * (alpha * qGreen(landcolor) + (255 - alpha) * qGreen(watercolor))),
331 (int)(c * (alpha * qBlue(landcolor) + (255 - alpha) * qBlue(watercolor))));
332 }
333}
334}
This file contains the headers for AbstractProjection.
This file contains the headers for ViewParameters.
This file contains the headers for ViewportParams.
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
Binds a QML item to a specific geodetic location in screen coordinates.
@ HighQuality
High quality (e.g. antialiasing for lines)
@ PrintQuality
Print quality.
qint64 elapsed() const const
int height() const const
uchar * scanLine(int i)
int width() const const
bool begin(QPaintDevice *device)
void drawRect(const QRect &rectangle)
bool end()
void setBrush(Qt::BrushStyle style)
void setPen(Qt::PenStyle style)
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
double toDouble(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.