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 }