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 
29 namespace Marble
30 {
31 
32 // 4 uchar long queue
33 class EmbossFifo
34 {
35 public:
EmbossFifo()36     EmbossFifo()
37         : data( 0 )
38     {}
39 
head() const40     inline uchar head() const
41     {
42         // return least significant byte as head of queue
43         return data & 0x000000FF;
44     }
45 
enqueue(uchar value)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 
TextureColorizer(const QString & seafile,const QString & landfile)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() << Q_FUNC_INFO << "Time elapsed:" << t.elapsed() << "ms";
137 }
138 
addSeaDocument(const GeoDataDocument * seaDocument)139 void TextureColorizer::addSeaDocument( const GeoDataDocument *seaDocument )
140 {
141     m_seaDocuments.append( seaDocument );
142 }
143 
addLandDocument(const GeoDataDocument * landDocument)144 void TextureColorizer::addLandDocument( const GeoDataDocument *landDocument )
145 {
146     m_landDocuments.append( landDocument );
147 }
148 
setShowRelief(bool show)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 
drawIndividualDocument(GeoPainter * painter,const GeoDataDocument * document)171 void TextureColorizer::drawIndividualDocument( GeoPainter *painter, const GeoDataDocument *document )
172 {
173     QVector<GeoDataFeature*>::ConstIterator i = document->constBegin();
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 
drawTextureMap(GeoPainter * painter)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 
colorize(QImage * origimg,const ViewportParams * viewport,MapQuality mapQuality)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 
setPixel(const QRgb * coastData,QRgb * writeData,int bump,uchar grey)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 }
360