[Marble-devel] [PATCH 05/13] Implement sun light blending with citylights theme as Blending sub class.
Jens-Michael Hoffmann
jensmh at gmx.de
Tue Mar 30 20:31:09 CEST 2010
Implement sun light blending with citylights theme as Blending sub class.
As a side effect a lot of stuff could be removed from MergedLayerDecorator.
However there are some regressions at the moment:
- the blending is no longer available on celestial bodies other than earth,
- is only possible with citylights theme (no plain darkening),
- is always active (cannot be turned off),
- only support current (and not arbitrary) date and time,
- centering the sun and following time source (with different speeds) does
no longer work.
---
marble/data/maps/earth/bluemarble/bluemarble.dgml | 6 +
marble/src/lib/BlendingFactory.cpp | 2 +
marble/src/lib/CMakeLists.txt | 1 +
marble/src/lib/MergedLayerDecorator.cpp | 241 +--------------------
marble/src/lib/MergedLayerDecorator.h | 19 +--
marble/src/lib/blendings/SunLightBlending.cpp | 207 ++++++++++++++++++
marble/src/lib/blendings/SunLightBlending.h | 45 ++++
7 files changed, 266 insertions(+), 255 deletions(-)
create mode 100644 marble/src/lib/blendings/SunLightBlending.cpp
create mode 100644 marble/src/lib/blendings/SunLightBlending.h
diff --git a/marble/data/maps/earth/bluemarble/bluemarble.dgml b/marble/data/maps/earth/bluemarble/bluemarble.dgml
index d4c5d5e..6924820 100644
--- a/marble/data/maps/earth/bluemarble/bluemarble.dgml
+++ b/marble/data/maps/earth/bluemarble/bluemarble.dgml
@@ -44,6 +44,12 @@
<storageLayout maximumTileLevel="1"/>
<blending name="CloudsBlending" />
</texture>
+ <texture name="citylights_data">
+ <sourcedir format="JPG"> earth/citylights </sourcedir>
+ <installmap> citylights.jpg </installmap>
+ <storageLayout maximumTileLevel="3"/>
+ <blending name="SunLightBlending" />
+ </texture>
</layer>
<layer name="mwdbii" backend="vector" role="polyline">
diff --git a/marble/src/lib/BlendingFactory.cpp b/marble/src/lib/BlendingFactory.cpp
index c32f11b..c9247a3 100644
--- a/marble/src/lib/BlendingFactory.cpp
+++ b/marble/src/lib/BlendingFactory.cpp
@@ -15,6 +15,7 @@
#include "BlendingFactory.h"
+#include "blendings/SunLightBlending.h"
#include "BlendingAlgorithms.h"
#include "MarbleDebug.h"
@@ -81,6 +82,7 @@ BlendingFactory::BlendingFactory()
// Special purpose blendings
m_blendings.insert( "CloudsBlending", new CloudsBlending );
+ m_blendings.insert( "SunLightBlending", new SunLightBlending );
}
}
diff --git a/marble/src/lib/CMakeLists.txt b/marble/src/lib/CMakeLists.txt
index c9a6f31..59e4505 100644
--- a/marble/src/lib/CMakeLists.txt
+++ b/marble/src/lib/CMakeLists.txt
@@ -59,6 +59,7 @@ set(marblewidget_SRCS
${geodata_SRCS}
${graphicsview_SRCS}
${screengraphicsitem_SRCS}
+ blendings/SunLightBlending.cpp
Blending.cpp
BlendingAlgorithms.cpp
BlendingFactory.cpp
diff --git a/marble/src/lib/MergedLayerDecorator.cpp b/marble/src/lib/MergedLayerDecorator.cpp
index daca26c..a846d15 100644
--- a/marble/src/lib/MergedLayerDecorator.cpp
+++ b/marble/src/lib/MergedLayerDecorator.cpp
@@ -17,71 +17,24 @@
#include "MergedLayerDecorator.h"
+#include <QtCore/QString>
+#include <QtGui/QImage>
#include <QtGui/QPainter>
-#include "SunLocator.h"
-#include "StackedTileLoader.h"
#include "global.h"
-#include "MarbleDebug.h"
-#include "GeoSceneDocument.h"
-#include "GeoSceneHead.h"
-#include "GeoSceneMap.h"
-#include "GeoSceneSettings.h"
-#include "GeoSceneTexture.h"
-#include "MapThemeManager.h"
-#include "StackedTile.h"
-#include "TileLoaderHelper.h"
-#include "Planet.h"
using namespace Marble;
MergedLayerDecorator::MergedLayerDecorator( StackedTileLoader * const tileLoader,
SunLocator* sunLocator )
- : m_tileLoader( tileLoader ),
- m_tile( 0 ),
+ : m_tile( 0 ),
m_id(),
- m_sunLocator( sunLocator ),
- m_showTileId( false ),
- m_cityLightsTheme( 0 ),
- m_cityLightsTextureLayer( 0 )
+ m_showTileId( false )
{
}
-void MergedLayerDecorator::initCityLights()
-{
- // look for the texture layers inside the themes
- // As long as we don't have an Layer Management Class we just lookup
- // the name of the layer that has the same name as the theme ID
-
- mDebug() << Q_FUNC_INFO;
- m_cityLightsTheme = MapThemeManager::loadMapTheme( "earth/citylights/citylights.dgml" );
- if (m_cityLightsTheme) {
- QString cityLightsId = m_cityLightsTheme->head()->theme();
- m_cityLightsTextureLayer = static_cast<GeoSceneTexture*>(
- m_cityLightsTheme->map()->layer( cityLightsId )->datasets().first() );
- }
-}
-
-MergedLayerDecorator::~MergedLayerDecorator()
-{
- delete m_cityLightsTheme;
-}
-
void MergedLayerDecorator::paint( const QString& themeId, GeoSceneDocument *mapTheme )
{
-// QTime time;
-// time.start();
-
- if ( m_sunLocator->getShow() && mapTheme ) {
-
- // Initialize citylights layer if it hasn't happened already
- if ( !m_cityLightsTheme ) {
- initCityLights();
- }
-// QTime time2;
-// time2.start();
- paintSunShading();
- }
if ( m_showTileId ) {
paintTileId( themeId );
}
@@ -97,170 +50,6 @@ bool MergedLayerDecorator::showTileId() const
return m_showTileId;
}
-StackedTile * MergedLayerDecorator::loadDataset( GeoSceneTexture *textureLayer )
-{
- const TileId decorationTileId( textureLayer->sourceDir(), m_id.zoomLevel(), m_id.x(), m_id.y());
- StackedTile * const tile = m_tileLoader->loadTile( decorationTileId, true );
- tile->setUsed( true );
- return tile;
-}
-
-void MergedLayerDecorator::paintSunShading()
-{
- if ( m_tile->depth() != 32 )
- return;
-
- // TODO add support for 8-bit maps?
- // add sun shading
- const qreal global_width = m_tile->width()
- * TileLoaderHelper::levelToColumn( m_cityLightsTextureLayer->levelZeroColumns(),
- m_id.zoomLevel() );
- const qreal global_height = m_tile->height()
- * TileLoaderHelper::levelToRow( m_cityLightsTextureLayer->levelZeroRows(),
- m_id.zoomLevel() );
- const qreal lon_scale = 2*M_PI / global_width;
- const qreal lat_scale = -M_PI / global_height;
- const int tileHeight = m_tile->height();
- const int tileWidth = m_tile->width();
-
- // First we determine the supporting point interval for the interpolation.
- const int n = maxDivisor( 30, tileWidth );
- const int ipRight = n * (int)( tileWidth / n );
-
- //Don't use city lights on non-earth planets!
- if ( m_sunLocator->getCitylights() && m_sunLocator->planet()->id() == "earth" ) {
-
- StackedTile * tile = loadDataset( m_cityLightsTextureLayer );
- if ( tile->state() == StackedTile::TileEmpty )
- return;
-
- QImage * nighttile = tile->resultTile();
-
- for ( int cur_y = 0; cur_y < tileHeight; ++cur_y ) {
- qreal lat = lat_scale * ( m_id.y() * tileHeight + cur_y ) - 0.5*M_PI;
- qreal a = sin( ( lat+DEG2RAD * m_sunLocator->getLat() )/2.0 );
- qreal c = cos(lat)*cos( -DEG2RAD * m_sunLocator->getLat() );
-
- QRgb* scanline = (QRgb*)m_tile->scanLine( cur_y );
- QRgb* nscanline = (QRgb*)nighttile->scanLine( cur_y );
-
- qreal shade = 0;
- qreal lastShade = -10.0;
-
- int cur_x = 0;
-
- while ( cur_x < tileWidth ) {
-
- bool interpolate = ( cur_x != 0 && cur_x < ipRight && cur_x + n < tileWidth );
-
- if ( interpolate ) {
- int check = cur_x + n;
- qreal checklon = lon_scale * ( m_id.x() * tileWidth + check );
- shade = m_sunLocator->shading( checklon, a, c );
-
- // if the shading didn't change across the interpolation
- // interval move on and don't change anything.
- if ( shade == lastShade && shade == 1.0 ) {
- scanline += n;
- nscanline += n;
- cur_x += n;
- continue;
- }
- if ( shade == lastShade && shade == 0.0 ) {
- for ( int t = 0; t < n; ++t ) {
- m_sunLocator->shadePixelComposite( *scanline, *nscanline, shade );
- ++scanline;
- ++nscanline;
- }
- cur_x += n;
- continue;
- }
- for ( int t = 0; t < n ; ++t ) {
- qreal lon = lon_scale * ( m_id.x() * tileWidth + cur_x );
- shade = m_sunLocator->shading( lon, a, c );
- m_sunLocator->shadePixelComposite( *scanline, *nscanline, shade );
- ++scanline;
- ++nscanline;
- ++cur_x;
- }
- }
-
- else {
- // Make sure we don't exceed the image memory
- if ( cur_x < tileWidth ) {
- qreal lon = lon_scale * ( m_id.x() * tileWidth + cur_x );
- shade = m_sunLocator->shading( lon, a, c );
- m_sunLocator->shadePixelComposite( *scanline, *nscanline, shade );
- ++scanline;
- ++nscanline;
- ++cur_x;
- }
- }
- lastShade = shade;
- }
- }
- } else {
- for ( int cur_y = 0; cur_y < tileHeight; ++cur_y ) {
- qreal lat = lat_scale * ( m_id.y() * tileHeight + cur_y ) - 0.5*M_PI;
- qreal a = sin( (lat+DEG2RAD * m_sunLocator->getLat() )/2.0 );
- qreal c = cos(lat)*cos( -DEG2RAD * m_sunLocator->getLat() );
-
- QRgb* scanline = (QRgb*)m_tile->scanLine( cur_y );
-
- qreal shade = 0;
- qreal lastShade = -10.0;
-
- int cur_x = 0;
-
- while ( cur_x < tileWidth ) {
-
- bool interpolate = ( cur_x != 0 && cur_x < ipRight && cur_x + n < tileWidth );
-
- if ( interpolate ) {
- int check = cur_x + n;
- qreal checklon = lon_scale * ( m_id.x() * tileWidth + check );
- shade = m_sunLocator->shading( checklon, a, c );
-
- // if the shading didn't change across the interpolation
- // interval move on and don't change anything.
- if ( shade == lastShade && shade == 1.0 ) {
- scanline += n;
- cur_x += n;
- continue;
- }
- if ( shade == lastShade && shade == 0.0 ) {
- for ( int t = 0; t < n; ++t ) {
- m_sunLocator->shadePixel( *scanline, shade );
- ++scanline;
- }
- cur_x += n;
- continue;
- }
- for ( int t = 0; t < n ; ++t ) {
- qreal lon = lon_scale * ( m_id.x() * tileWidth + cur_x );
- shade = m_sunLocator->shading( lon, a, c );
- m_sunLocator->shadePixel( *scanline, shade );
- ++scanline;
- ++cur_x;
- }
- }
-
- else {
- // Make sure we don't exceed the image memory
- if ( cur_x < tileWidth ) {
- qreal lon = lon_scale * ( m_id.x() * tileWidth + cur_x );
- shade = m_sunLocator->shading( lon, a, c );
- m_sunLocator->shadePixel( *scanline, shade );
- ++scanline;
- ++cur_x;
- }
- }
- lastShade = shade;
- }
- }
- }
-}
-
void MergedLayerDecorator::paintTileId( const QString& themeId )
{
QString filename = QString( "%1_%2.jpg" )
@@ -333,26 +122,4 @@ void MergedLayerDecorator::setInfo( TileId const& id )
m_id = id;
}
-// TODO: This should likely go into a math class in the future ...
-
-int MergedLayerDecorator::maxDivisor( int maximum, int fullLength )
-{
- // Find the optimal interpolation interval n for the
- // current image canvas width
- int best = 2;
-
- int nEvalMin = fullLength;
- for ( int it = 1; it <= maximum; ++it ) {
- // The optimum is the interval which results in the least amount
- // supporting points taking into account the rest which can't
- // get used for interpolation.
- int nEval = fullLength / it + fullLength % it;
- if ( nEval < nEvalMin ) {
- nEvalMin = nEval;
- best = it;
- }
- }
- return best;
-}
-
#include "MergedLayerDecorator.moc"
diff --git a/marble/src/lib/MergedLayerDecorator.h b/marble/src/lib/MergedLayerDecorator.h
index d3f45f3..af046b3 100644
--- a/marble/src/lib/MergedLayerDecorator.h
+++ b/marble/src/lib/MergedLayerDecorator.h
@@ -16,21 +16,18 @@
#ifndef MERGED_LAYER_DECORATOR_H
#define MERGED_LAYER_DECORATOR_H
-#include <QtGui/QImage>
#include <QtCore/QObject>
#include "TileId.h"
-#include "global.h"
+class QImage;
class QString;
class QUrl;
namespace Marble
{
class GeoSceneDocument;
-class GeoSceneTexture;
class SunLocator;
-class StackedTile;
class StackedTileLoader;
class MergedLayerDecorator : public QObject
@@ -39,7 +36,6 @@ class MergedLayerDecorator : public QObject
public:
MergedLayerDecorator( StackedTileLoader * const tileLoader, SunLocator* sunLocator );
- virtual ~MergedLayerDecorator();
// The Parameter themeId is only used for displaying the TileId,
// which is a debugging feature, therefore at this point QString remains.
@@ -55,24 +51,11 @@ class MergedLayerDecorator : public QObject
Q_SIGNALS:
void repaintMap();
- private:
- StackedTile * loadDataset( GeoSceneTexture *textureLayer );
- int maxDivisor( int maximum, int fullLength );
-
- void initCityLights();
-
- void paintSunShading();
- void paintClouds();
-
protected:
Q_DISABLE_COPY( MergedLayerDecorator )
- StackedTileLoader * const m_tileLoader;
QImage* m_tile;
TileId m_id;
- SunLocator* m_sunLocator;
bool m_showTileId;
- GeoSceneDocument *m_cityLightsTheme;
- GeoSceneTexture *m_cityLightsTextureLayer;
};
}
diff --git a/marble/src/lib/blendings/SunLightBlending.cpp b/marble/src/lib/blendings/SunLightBlending.cpp
new file mode 100644
index 0000000..8debf33
--- /dev/null
+++ b/marble/src/lib/blendings/SunLightBlending.cpp
@@ -0,0 +1,207 @@
+// Copyright 2010 Jens-Michael Hoffmann <jmho at c-xx.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+#include "SunLightBlending.h"
+
+#include "ExtDateTime.h"
+#include "MarbleDebug.h"
+#include "Planet.h"
+#include "SunLocator.h"
+#include "TextureTile.h"
+#include "global.h"
+
+#include <QtGui/QImage>
+
+#include <cmath>
+
+namespace Marble
+{
+
+SunLightBlending::SunLightBlending()
+ : Blending(),
+ m_sunLocator( new SunLocator( new ExtDateTime, new Planet( "earth" )))
+{
+}
+
+SunLightBlending::~SunLightBlending()
+{
+ delete m_sunLocator;
+}
+
+void SunLightBlending::blend( QImage * const bottom, QSharedPointer<TextureTile> const & top ) const
+{
+ QImage const * const topImage = top->image();
+ Q_ASSERT( topImage );
+ Q_ASSERT( bottom->size() == topImage->size() );
+ int const tileWidth = bottom->width();
+ int const tileHeight = bottom->height();
+ mDebug() << "SunLightBlending::blend, tile width/height:" << tileWidth << tileHeight;
+
+ // number of pixels in current zoom level
+ // TODO: fix calculation, take levelZero(Columns|Rows) into account
+ int const globalWidth = tileWidth << top->id().zoomLevel();
+ int const globalHeight = tileHeight << top->id().zoomLevel();
+ mDebug() << "SunLightBlending::blend, global width/height:" << globalWidth << globalHeight;
+
+ qreal const lonScale = 2.0 * M_PI / globalWidth;
+ qreal const latScale = -M_PI / globalHeight;
+
+ m_sunLocator->update();
+ qreal const sunZenithLon = m_sunLocator->getLon() * DEG2RAD;
+ qreal const sunZenithLat = m_sunLocator->getLat() * DEG2RAD;
+ mDebug() << "SunLightBlending::blend, sun zenith lon/lat:" << sunZenithLon << sunZenithLat;
+
+ // First we determine the supporting point interval for the interpolation.
+ int const n = maxDivisor( 30, tileWidth );
+ int const ipRight = n * (int)( tileWidth / n );
+
+ for ( int y = 0; y < tileHeight; ++y ) {
+
+ qreal const lat = latScale * ( top->id().y() * tileHeight + y ) - 0.5 * M_PI;
+ qreal const a = sin(( lat + sunZenithLat ) / 2.0 );
+ qreal const c = cos( lat ) * cos( -sunZenithLat );
+
+ QRgb * bottomScanline = (QRgb*) bottom->scanLine( y );
+ QRgb * topScanline = (QRgb*) topImage->scanLine( y );
+
+ qreal shade = 0.0;
+ qreal lastShade = -10.0;
+
+ int x = 0;
+ while ( x < tileWidth ) {
+ bool const interpolate = ( x != 0 && x < ipRight && x + n < tileWidth );
+ if ( interpolate ) {
+ int const check = x + n;
+ qreal const checkLon = lonScale * ( top->id().x() * tileWidth + check );
+ shade = shading( checkLon - sunZenithLon, a, c );
+
+ // if the shading didn't change across the interpolation
+ // interval move on and don't change anything.
+ if ( shade == lastShade && shade == 1.0 ) {
+ bottomScanline += n;
+ topScanline += n;
+ x += n;
+ continue;
+ }
+ if ( shade == lastShade && shade == 0.0 ) {
+ for ( int t = 0; t < n; ++t ) {
+ shadePixelComposite( *bottomScanline, *topScanline, shade );
+ ++bottomScanline;
+ ++topScanline;
+ }
+ x += n;
+ continue;
+ }
+ for ( int t = 0; t < n ; ++t ) {
+ qreal const lon = lonScale * ( top->id().x() * tileWidth + x );
+ shade = shading( lon - sunZenithLon, a, c );
+ shadePixelComposite( *bottomScanline, *topScanline, shade );
+ ++bottomScanline;
+ ++topScanline;
+ ++x;
+ }
+ }
+ else {
+ // Make sure we don't exceed the image memory
+ if ( x < tileWidth ) {
+ qreal const lon = lonScale * ( top->id().x() * tileWidth + x );
+ shade = shading( lon - sunZenithLon, a, c );
+ shadePixelComposite( *bottomScanline, *topScanline, shade );
+ ++bottomScanline;
+ ++topScanline;
+ ++x;
+ }
+ }
+ lastShade = shade;
+ }
+ }
+}
+
+void SunLightBlending::shadePixelComposite( QRgb & bottom, QRgb const top, qreal const brightness ) const
+{
+ if ( brightness > 0.99999 )
+ // daylight - no change
+ return;
+
+ if ( brightness < 0.00001 ) {
+ // night
+ bottom = top;
+ }
+ else {
+ // gradual shadowing
+ int const bottomRed = qRed( bottom );
+ int const bootomGreen = qGreen( bottom );
+ int const bottomBlue = qBlue( bottom );
+
+ int const topRed = qRed( top );
+ int const topGreen = qGreen( top );
+ int const topBlue = qBlue( top );
+
+ bottom = qRgb( (int)( brightness * bottomRed + ( 1.0 - brightness ) * topRed ),
+ (int)( brightness * bootomGreen + ( 1.0 - brightness ) * topGreen ),
+ (int)( brightness * bottomBlue + ( 1.0 - brightness ) * topBlue ));
+ }
+}
+
+// deltaLon = lon - sunLon
+qreal SunLightBlending::shading( qreal const deltaLon, qreal const a, qreal const c ) const
+{
+ // haversine formula
+ qreal const b = sin( deltaLon / 2.0 );
+ qreal const h = ( a * a ) + c * ( b * b );
+
+ /*
+ h = 0.0 // directly beneath sun
+ h = 0.5 // sunrise/sunset line
+ h = 1.0 // opposite side of earth to the sun
+ theta = 2*asin(sqrt(h))
+ */
+
+ // this equals 18 deg astronomical twilight and is correct for earth or venus
+ qreal const twilightZone = 0.1;
+
+ qreal brightness;
+ if ( h <= 0.5 - twilightZone / 2.0 )
+ brightness = 1.0;
+ else if ( h >= 0.5 + twilightZone / 2.0 )
+ brightness = 0.0;
+ else
+ brightness = ( 0.5 + twilightZone / 2.0 - h ) / twilightZone;
+
+ return brightness;
+}
+
+// TODO: This should likely go into a math class in the future ...
+int SunLightBlending::maxDivisor( int const maximum, int const fullLength ) const
+{
+ // Find the optimal interpolation interval n for the
+ // current image canvas width
+ int best = 2;
+
+ int nEvalMin = fullLength;
+ for ( int it = 1; it <= maximum; ++it ) {
+ // The optimum is the interval which results in the least amount
+ // supporting points taking into account the rest which can't
+ // get used for interpolation.
+ int nEval = fullLength / it + fullLength % it;
+ if ( nEval < nEvalMin ) {
+ nEvalMin = nEval;
+ best = it;
+ }
+ }
+ return best;
+}
+
+}
diff --git a/marble/src/lib/blendings/SunLightBlending.h b/marble/src/lib/blendings/SunLightBlending.h
new file mode 100644
index 0000000..47ec8c4
--- /dev/null
+++ b/marble/src/lib/blendings/SunLightBlending.h
@@ -0,0 +1,45 @@
+// Copyright 2010 Jens-Michael Hoffmann <jmho at c-xx.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef MARBLE_SUN_LIGHT_BLENDING_H
+#define MARBLE_SUN_LIGHT_BLENDING_H
+
+#include <QtCore/QtGlobal>
+#include <QtGui/QColor>
+
+#include "Blending.h"
+
+namespace Marble
+{
+
+class SunLocator;
+
+class SunLightBlending: public Blending
+{
+ public:
+ SunLightBlending();
+ virtual ~SunLightBlending();
+ virtual void blend( QImage * const bottom, QSharedPointer<TextureTile> const & top ) const;
+
+ private:
+ void shadePixelComposite( QRgb & bottom, QRgb const top, qreal const brightness ) const;
+ qreal shading( qreal const deltaLon, qreal const a, qreal const c ) const;
+ int maxDivisor( int const maximum, int const fullLength ) const;
+ SunLocator * const m_sunLocator;
+};
+
+}
+
+#endif
--
1.7.0.3
More information about the Marble-devel
mailing list