File indexing completed on 2024-04-21 03:49:59
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org> 0004 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org> 0005 // SPDX-FileCopyrightText: 2008 Carlos Licea <carlos.licea@kdemail.net> 0006 // SPDX-FileCopyrightText: 2012 Cezar Mocan <mocancezar@gmail.com> 0007 // 0008 0009 #include "TextureColorizer.h" 0010 0011 #include <qmath.h> 0012 #include <QFile> 0013 #include <QSharedPointer> 0014 #include <QVector> 0015 #include <QElapsedTimer> 0016 #include <QPainter> 0017 0018 #include "GeoPainter.h" 0019 #include "MarbleDebug.h" 0020 #include "ViewParams.h" 0021 #include "ViewportParams.h" 0022 #include "MathHelper.h" 0023 #include "GeoDataLinearRing.h" 0024 #include "GeoDataPolygon.h" 0025 #include "GeoDataFeature.h" 0026 #include "GeoDataPlacemark.h" 0027 #include "AbstractProjection.h" 0028 0029 namespace Marble 0030 { 0031 0032 // 4 uchar long queue 0033 class EmbossFifo 0034 { 0035 public: 0036 EmbossFifo() 0037 : data( 0 ) 0038 {} 0039 0040 inline uchar head() const 0041 { 0042 // return least significant byte as head of queue 0043 return data & 0x000000FF; 0044 } 0045 0046 inline void enqueue(uchar value) 0047 { 0048 // drop current head by shifting by one byte 0049 // and append new value as most significant byte to queue 0050 data = ((data >> 8) & 0x00FFFFFF) | (value << 24); 0051 } 0052 0053 private: 0054 // 4 byte long queue 0055 quint32 data; 0056 }; 0057 0058 0059 TextureColorizer::TextureColorizer( const QString &seafile, 0060 const QString &landfile ) 0061 : m_showRelief( false ), 0062 m_landColor(qRgb( 255, 0, 0 ) ), 0063 m_seaColor( qRgb( 0, 255, 0 ) ) 0064 { 0065 QElapsedTimer t; 0066 t.start(); 0067 0068 QImage gradientImage ( 256, 1, QImage::Format_RGB32 ); 0069 QPainter gradientPainter; 0070 gradientPainter.begin( &gradientImage ); 0071 gradientPainter.setPen( Qt::NoPen ); 0072 0073 0074 int shadingStart = 120; 0075 QImage shadingImage ( 16, 1, QImage::Format_RGB32 ); 0076 QPainter shadingPainter; 0077 shadingPainter.begin( &shadingImage ); 0078 shadingPainter.setPen( Qt::NoPen ); 0079 0080 int offset = 0; 0081 0082 QStringList filelist; 0083 filelist << seafile << landfile; 0084 0085 for ( const QString &filename: filelist ) { 0086 0087 QLinearGradient gradient( 0, 0, 256, 0 ); 0088 0089 QFile file( filename ); 0090 file.open( QIODevice::ReadOnly ); 0091 QTextStream stream( &file ); // read the data from the file 0092 0093 QString evalstrg; 0094 0095 while ( !stream.atEnd() ) { 0096 stream >> evalstrg; 0097 if (!evalstrg.isEmpty() && evalstrg.contains(QLatin1Char('='))) { 0098 QString colorValue = evalstrg.left(evalstrg.indexOf(QLatin1Char('='))); 0099 QString colorPosition = evalstrg.mid(evalstrg.indexOf(QLatin1Char('=')) + 1); 0100 gradient.setColorAt( colorPosition.toDouble(), 0101 QColor( colorValue ) ); 0102 } 0103 } 0104 gradientPainter.setBrush( gradient ); 0105 gradientPainter.drawRect( 0, 0, 256, 1 ); 0106 0107 QLinearGradient shadeGradient( - shadingStart, 0, 256 - shadingStart, 0 ); 0108 0109 shadeGradient.setColorAt(0.00, QColor(Qt::white)); 0110 shadeGradient.setColorAt(0.15, QColor(Qt::white)); 0111 shadeGradient.setColorAt(0.75, QColor(Qt::black)); 0112 shadeGradient.setColorAt(1.00, QColor(Qt::black)); 0113 0114 const QRgb * gradientScanLine = (QRgb*)( gradientImage.scanLine( 0 ) ); 0115 const QRgb * shadingScanLine = (QRgb*)( shadingImage.scanLine( 0 ) ); 0116 0117 for ( int i = 0; i < 256; ++i ) { 0118 0119 QRgb shadeColor = *(gradientScanLine + i ); 0120 shadeGradient.setColorAt(0.496, shadeColor); 0121 shadeGradient.setColorAt(0.504, shadeColor); 0122 shadingPainter.setBrush( shadeGradient ); 0123 shadingPainter.drawRect( 0, 0, 16, 1 ); 0124 0125 // populate texturepalette[][] 0126 for ( int j = 0; j < 16; ++j ) { 0127 texturepalette[j][offset + i] = *(shadingScanLine + j ); 0128 } 0129 } 0130 0131 offset += 256; 0132 } 0133 shadingPainter.end(); // Need to explicitly tell painter lifetime to avoid crash 0134 gradientPainter.end(); // on some systems. 0135 0136 mDebug() << "Time elapsed:" << t.elapsed() << "ms"; 0137 } 0138 0139 void TextureColorizer::addSeaDocument( const GeoDataDocument *seaDocument ) 0140 { 0141 m_seaDocuments.append( seaDocument ); 0142 } 0143 0144 void TextureColorizer::addLandDocument( const GeoDataDocument *landDocument ) 0145 { 0146 m_landDocuments.append( landDocument ); 0147 } 0148 0149 void TextureColorizer::setShowRelief( bool show ) 0150 { 0151 m_showRelief = show; 0152 } 0153 0154 // This function takes two images, both in viewParams: 0155 // - The coast image, which has a number of colors where each color 0156 // represents a sort of terrain (ex: land/sea) 0157 // - The canvas image, which has a gray scale image, often 0158 // representing a height field. 0159 // 0160 // It then uses the values of the pixels in the coast image to select 0161 // a color map. The value of the pixel in the canvas image is used as 0162 // an index into the selected color map and the resulting color is 0163 // written back to the canvas image. This way we can have different 0164 // color schemes for land and water. 0165 // 0166 // In addition to this, a simple form of bump mapping is performed to 0167 // increase the illusion of height differences (see the variable 0168 // showRelief). 0169 // 0170 0171 void TextureColorizer::drawIndividualDocument( GeoPainter *painter, const GeoDataDocument *document ) 0172 { 0173 QVector<GeoDataFeature*>::ConstIterator i = document->constBegin(); 0174 QVector<GeoDataFeature*>::ConstIterator end = document->constEnd(); 0175 0176 for ( ; i != end; ++i ) { 0177 if (const GeoDataPlacemark *placemark = geodata_cast<GeoDataPlacemark>(*i)) { 0178 if (const GeoDataLineString *child = geodata_cast<GeoDataLineString>(placemark->geometry())) { 0179 const GeoDataLinearRing ring( *child ); 0180 painter->drawPolygon( ring ); 0181 } 0182 0183 if (const GeoDataPolygon *child = geodata_cast<GeoDataPolygon>(placemark->geometry())) { 0184 painter->drawPolygon( *child ); 0185 } 0186 0187 if (const GeoDataLinearRing *child = geodata_cast<GeoDataLinearRing>(placemark->geometry())) { 0188 painter->drawPolygon( *child ); 0189 } 0190 } 0191 } 0192 } 0193 0194 void TextureColorizer::drawTextureMap( GeoPainter *painter ) 0195 { 0196 for( const GeoDataDocument *doc: m_landDocuments ) { 0197 painter->setPen( QPen( Qt::NoPen ) ); 0198 painter->setBrush( QBrush( m_landColor ) ); 0199 drawIndividualDocument( painter, doc ); 0200 } 0201 0202 for( const GeoDataDocument *doc: m_seaDocuments ) { 0203 if ( doc->isVisible() ) { 0204 painter->setPen( Qt::NoPen ); 0205 painter->setBrush( QBrush( m_seaColor ) ); 0206 drawIndividualDocument( painter, doc ); 0207 } 0208 } 0209 } 0210 0211 void TextureColorizer::colorize( QImage *origimg, const ViewportParams *viewport, MapQuality mapQuality ) 0212 { 0213 if ( m_coastImage.size() != viewport->size() ) 0214 m_coastImage = QImage( viewport->size(), QImage::Format_RGB32 ); 0215 0216 // update coast image 0217 m_coastImage.fill( QColor( 0, 0, 255, 0).rgb() ); 0218 0219 const bool antialiased = mapQuality == HighQuality 0220 || mapQuality == PrintQuality; 0221 0222 GeoPainter painter( &m_coastImage, viewport, mapQuality ); 0223 painter.setRenderHint( QPainter::Antialiasing, antialiased ); 0224 0225 drawTextureMap( &painter ); 0226 0227 const qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius(); 0228 0229 const int imgheight = origimg->height(); 0230 const int imgwidth = origimg->width(); 0231 const int imgrx = imgwidth / 2; 0232 const int imgry = imgheight / 2; 0233 // This variable is not used anywhere.. 0234 const int imgradius = imgrx * imgrx + imgry * imgry; 0235 0236 int bump = 8; 0237 0238 if ( radius * radius > imgradius 0239 || !viewport->currentProjection()->isClippedToSphere() ) 0240 { 0241 int yTop = 0; 0242 int yBottom = imgheight; 0243 0244 if( !viewport->currentProjection()->isClippedToSphere() && !viewport->currentProjection()->traversablePoles() ) 0245 { 0246 qreal realYTop, realYBottom, dummyX; 0247 GeoDataCoordinates yNorth(0, viewport->currentProjection()->maxLat(), 0); 0248 GeoDataCoordinates ySouth(0, viewport->currentProjection()->minLat(), 0); 0249 viewport->screenCoordinates(yNorth, dummyX, realYTop ); 0250 viewport->screenCoordinates(ySouth, dummyX, realYBottom ); 0251 yTop = qBound(qreal(0.0), realYTop, qreal(imgheight)); 0252 yBottom = qBound(qreal(0.0), realYBottom, qreal(imgheight)); 0253 } 0254 0255 const int itEnd = yBottom; 0256 0257 for (int y = yTop; y < itEnd; ++y) { 0258 0259 QRgb *writeData = (QRgb*)( origimg->scanLine( y ) ); 0260 const QRgb *coastData = (QRgb*)( m_coastImage.scanLine( y ) ); 0261 0262 uchar *readDataStart = origimg->scanLine( y ); 0263 const uchar *readDataEnd = readDataStart + imgwidth*4; 0264 0265 EmbossFifo emboss; 0266 0267 for ( uchar* readData = readDataStart; 0268 readData < readDataEnd; 0269 readData += 4, ++writeData, ++coastData ) 0270 { 0271 0272 // Cheap Emboss / Bumpmapping 0273 uchar& grey = *readData; // qBlue(*data); 0274 0275 if ( m_showRelief ) { 0276 emboss.enqueue(grey); 0277 bump = ( emboss.head() + 8 - grey ); 0278 if (bump < 0) { 0279 bump = 0; 0280 } else if (bump > 15) { 0281 bump = 15; 0282 } 0283 } 0284 setPixel( coastData, writeData, bump, grey ); 0285 } 0286 } 0287 } 0288 else { 0289 int yTop = ( imgry-radius < 0 ) ? 0 : imgry-radius; 0290 const int yBottom = ( yTop == 0 ) ? imgheight : imgry + radius; 0291 0292 EmbossFifo emboss; 0293 0294 for ( int y = yTop; y < yBottom; ++y ) { 0295 const int dy = imgry - y; 0296 int rx = (int)sqrt( (qreal)( radius * radius - dy * dy ) ); 0297 int xLeft = 0; 0298 int xRight = imgwidth; 0299 0300 if ( imgrx-rx > 0 ) { 0301 xLeft = imgrx - rx; 0302 xRight = imgrx + rx; 0303 } 0304 0305 QRgb *writeData = (QRgb*)( origimg->scanLine( y ) ) + xLeft; 0306 const QRgb *coastData = (QRgb*)( m_coastImage.scanLine( y ) ) + xLeft; 0307 0308 uchar *readDataStart = origimg->scanLine( y ) + xLeft * 4; 0309 const uchar *readDataEnd = origimg->scanLine( y ) + xRight * 4; 0310 0311 0312 for ( uchar* readData = readDataStart; 0313 readData < readDataEnd; 0314 readData += 4, ++writeData, ++coastData ) 0315 { 0316 // Cheap Emboss / Bumpmapping 0317 0318 uchar& grey = *readData; // qBlue(*data); 0319 0320 if ( m_showRelief ) { 0321 emboss.enqueue(grey); 0322 bump = ( emboss.head() + 16 - grey ) >> 1; 0323 if (bump < 0) { 0324 bump = 0; 0325 } else if (bump > 15) { 0326 bump = 15; 0327 } 0328 } 0329 setPixel( coastData, writeData, bump, grey ); 0330 } 0331 } 0332 } 0333 } 0334 0335 void TextureColorizer::setPixel( const QRgb *coastData, QRgb *writeData, int bump, uchar grey ) 0336 { 0337 int alpha = qRed( *coastData ); 0338 if ( alpha == 255 ) 0339 *writeData = texturepalette[bump][grey + 0x100]; 0340 else if( alpha == 0 ){ 0341 *writeData = texturepalette[bump][grey]; 0342 } 0343 else { 0344 qreal c = 1.0 / 255.0; 0345 0346 QRgb landcolor = (QRgb)(texturepalette[bump][grey + 0x100]); 0347 QRgb watercolor = (QRgb)(texturepalette[bump][grey]); 0348 0349 *writeData = qRgb( 0350 (int) ( c * ( alpha * qRed( landcolor ) 0351 + ( 255 - alpha ) * qRed( watercolor ) ) ), 0352 (int) ( c * ( alpha * qGreen( landcolor ) 0353 + ( 255 - alpha ) * qGreen( watercolor ) ) ), 0354 (int) ( c * ( alpha * qBlue( landcolor ) 0355 + ( 255 - alpha ) * qBlue( watercolor ) ) ) 0356 ); 0357 } 0358 } 0359 }