Marble

TextureColorizer.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <[email protected]>
4 // SPDX-FileCopyrightText: 2007 Inge Wallin <[email protected]>
5 // SPDX-FileCopyrightText: 2008 Carlos Licea <[email protected]>
6 // SPDX-FileCopyrightText: 2012 Cezar Mocan <[email protected]>
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 
29 namespace Marble
30 {
31 
32 // 4 uchar long queue
33 class EmbossFifo
34 {
35 public:
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 
53 private:
54  // 4 byte long queue
55  quint32 data;
56 };
57 
58 
59 TextureColorizer::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 {
65  QElapsedTimer t;
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 
139 void TextureColorizer::addSeaDocument( const GeoDataDocument *seaDocument )
140 {
141  m_seaDocuments.append( seaDocument );
142 }
143 
144 void TextureColorizer::addLandDocument( const GeoDataDocument *landDocument )
145 {
146  m_landDocuments.append( landDocument );
147 }
148 
149 void 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 
171 void 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 
194 void 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 
211 void 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 
335 void 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 }
void setPen(const QColor &color)
int height() const const
void drawRect(const QRectF &rectangle)
bool begin(QPaintDevice *device)
bool end()
MapQuality
This enum is used to choose the map quality shown in the view.
Definition: MarbleGlobal.h:74
Binds a QML item to a specific geodetic location in screen coordinates.
qint64 elapsed() const const
void setBrush(const QBrush &brush)
uchar * scanLine(int i)
double toDouble(bool *ok) const const
@ HighQuality
High quality (e.g. antialiasing for lines)
Definition: MarbleGlobal.h:78
QString left(int n) const const
@ PrintQuality
Print quality.
Definition: MarbleGlobal.h:79
QVector::const_iterator constBegin() const const
QString mid(int position, int n) const const
const QList< QKeySequence > & end()
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Dec 11 2023 04:09:41 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.