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

KDE's Doxygen guidelines are available online.