1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3  * Copyright 2019 Pelican Mapping
4  * http://osgearth.org
5  *
6  * osgEarth is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>
18  */
19 #include <osgEarth/ImageLayer>
20 #include <osgEarth/ImageMosaic>
21 #include <osgEarth/Registry>
22 #include <osgEarth/Progress>
23 #include <osgEarth/Capabilities>
24 #include <osgEarth/Metrics>
25 
26 using namespace osgEarth;
27 using namespace OpenThreads;
28 
29 #define LC "[ImageLayer] \"" << getName() << "\" "
30 
31 // TESTING
32 //#undef  OE_DEBUG
33 //#define OE_DEBUG OE_INFO
34 
35 namespace osgEarth {
36     REGISTER_OSGEARTH_LAYER(image, ImageLayer);
37 }
38 
39 //------------------------------------------------------------------------
40 
ImageLayerOptions()41 ImageLayerOptions::ImageLayerOptions() :
42 TerrainLayerOptions()
43 {
44     setDefaults();
45     fromConfig(_conf);
46 }
47 
ImageLayerOptions(const ConfigOptions & options)48 ImageLayerOptions::ImageLayerOptions(const ConfigOptions& options) :
49 TerrainLayerOptions(options)
50 {
51     setDefaults();
52     fromConfig( _conf );
53 }
54 
ImageLayerOptions(const std::string & name)55 ImageLayerOptions::ImageLayerOptions(const std::string& name) :
56 TerrainLayerOptions(name)
57 {
58     setDefaults();
59     fromConfig( _conf );
60 }
61 
ImageLayerOptions(const std::string & name,const TileSourceOptions & driverOpt)62 ImageLayerOptions::ImageLayerOptions(const std::string& name, const TileSourceOptions& driverOpt) :
63 TerrainLayerOptions(name, driverOpt)
64 {
65     setDefaults();
66     fromConfig( _conf );
67 }
68 
69 void
setDefaults()70 ImageLayerOptions::setDefaults()
71 {
72     _transparentColor.init( osg::Vec4ub(0,0,0,0) );
73     _featherPixels.init( false );
74     _minFilter.init( osg::Texture::LINEAR_MIPMAP_LINEAR );
75     _magFilter.init( osg::Texture::LINEAR );
76     _texcomp.init( osg::Texture::USE_IMAGE_DATA_FORMAT ); // none
77     _shared.init( false );
78     _coverage.init( false );
79 }
80 
81 void
mergeConfig(const Config & conf)82 ImageLayerOptions::mergeConfig( const Config& conf )
83 {
84     TerrainLayerOptions::mergeConfig( conf );
85     fromConfig( conf );
86 }
87 
88 void
fromConfig(const Config & conf)89 ImageLayerOptions::fromConfig(const Config& conf)
90 {
91     conf.get( "nodata_image",   _noDataImageFilename );
92     conf.get( "shared",         _shared );
93     conf.get( "coverage",       _coverage );
94     conf.get( "feather_pixels", _featherPixels);
95     conf.get( "altitude",       _altitude );
96 
97     if ( conf.hasValue( "transparent_color" ) )
98         _transparentColor = stringToColor( conf.value( "transparent_color" ), osg::Vec4ub(0,0,0,0));
99 
100     if ( conf.hasChild("color_filters") )
101     {
102         _colorFilters.clear();
103         ColorFilterRegistry::instance()->readChain( conf.child("color_filters"), _colorFilters );
104     }
105 
106     conf.get("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
107     conf.get("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
108     conf.get("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
109     conf.get("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
110     conf.get("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
111     conf.get("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
112     conf.get("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
113     conf.get("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
114     conf.get("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
115     conf.get("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
116     conf.get("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
117     conf.get("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
118 
119     conf.get("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
120     conf.get("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
121     conf.get("texture_compression", "fastdxt", _texcomp, (osg::Texture::InternalFormatMode)(~0 - 1));
122     //TODO add all the enums
123 
124     // uniform names
125     conf.get("shared_sampler", _shareTexUniformName);
126     conf.get("shared_matrix",  _shareTexMatUniformName);
127 }
128 
129 Config
getConfig() const130 ImageLayerOptions::getConfig() const
131 {
132     Config conf = TerrainLayerOptions::getConfig();
133 
134     conf.set( "nodata_image",   _noDataImageFilename );
135     conf.set( "shared",         _shared );
136     conf.set( "coverage",       _coverage );
137     conf.set( "feather_pixels", _featherPixels );
138     conf.set( "altitude",       _altitude );
139 
140     if (_transparentColor.isSet())
141         conf.set("transparent_color", colorToString( _transparentColor.value()));
142 
143     if ( _colorFilters.size() > 0 )
144     {
145         Config filtersConf("color_filters");
146         if ( ColorFilterRegistry::instance()->writeChain( _colorFilters, filtersConf ) )
147         {
148             conf.set( filtersConf );
149         }
150     }
151 
152     conf.set("mag_filter","LINEAR",                _magFilter,osg::Texture::LINEAR);
153     conf.set("mag_filter","LINEAR_MIPMAP_LINEAR",  _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
154     conf.set("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
155     conf.set("mag_filter","NEAREST",               _magFilter,osg::Texture::NEAREST);
156     conf.set("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
157     conf.set("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
158     conf.set("min_filter","LINEAR",                _minFilter,osg::Texture::LINEAR);
159     conf.set("min_filter","LINEAR_MIPMAP_LINEAR",  _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR);
160     conf.set("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST);
161     conf.set("min_filter","NEAREST",               _minFilter,osg::Texture::NEAREST);
162     conf.set("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR);
163     conf.set("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST);
164 
165     conf.set("texture_compression", "none", _texcomp, osg::Texture::USE_IMAGE_DATA_FORMAT);
166     conf.set("texture_compression", "auto", _texcomp, (osg::Texture::InternalFormatMode)~0);
167     conf.set("texture_compression", "on",   _texcomp, (osg::Texture::InternalFormatMode)~0);
168     conf.set("texture_compression", "fastdxt", _texcomp, (osg::Texture::InternalFormatMode)(~0 - 1));
169     //TODO add all the enums
170 
171     // uniform names
172     conf.set("shared_sampler", _shareTexUniformName);
173     conf.set("shared_matrix",  _shareTexMatUniformName);
174 
175     //if (driver().isSet())
176     //    conf.set("driver", driver()->getDriver());
177 
178     return conf;
179 }
180 
181 //------------------------------------------------------------------------
182 
183 namespace
184 {
185     struct ImageLayerPreCacheOperation : public TileSource::ImageOperation
186     {
operator ()__anonb322f4ed0111::ImageLayerPreCacheOperation187         void operator()( osg::ref_ptr<osg::Image>& image )
188         {
189             _processor.process( image );
190         }
191 
192         ImageLayerTileProcessor _processor;
193     };
194 
195     struct ApplyChromaKey
196     {
197         osg::Vec4f _chromaKey;
operator ()__anonb322f4ed0111::ApplyChromaKey198         bool operator()( osg::Vec4f& pixel ) {
199             bool equiv = ImageUtils::areRGBEquivalent( pixel, _chromaKey );
200             if ( equiv ) pixel.a() = 0.0f;
201             return equiv;
202         }
203     };
204 }
205 
206 //------------------------------------------------------------------------
207 
ImageLayerTileProcessor(const ImageLayerOptions & options)208 ImageLayerTileProcessor::ImageLayerTileProcessor(const ImageLayerOptions& options)
209 {
210     init( options, 0L, false );
211 }
212 
213 void
init(const ImageLayerOptions & options,const osgDB::Options * dbOptions,bool layerInTargetProfile)214 ImageLayerTileProcessor::init(const ImageLayerOptions& options,
215                               const osgDB::Options*    dbOptions,
216                               bool                     layerInTargetProfile )
217 {
218     _options = options;
219     _layerInTargetProfile = layerInTargetProfile;
220 
221     //if ( _layerInTargetProfile )
222     //    OE_DEBUG << LC << "Good, the layer and map have the same profile." << std::endl;
223 
224     const osg::Vec4ub& ck= *_options.transparentColor();
225     _chromaKey.set( ck.r() / 255.0f, ck.g() / 255.0f, ck.b() / 255.0f, 1.0 );
226 
227     if ( _options.noDataImageFilename().isSet() && !_options.noDataImageFilename()->empty() )
228     {
229         _noDataImage = _options.noDataImageFilename()->getImage( dbOptions );
230         if ( !_noDataImage.valid() )
231         {
232             OE_WARN << "Failed to read nodata image from \"" << _options.noDataImageFilename()->full() << "\"" << std::endl;
233         }
234     }
235 }
236 
237 void
process(osg::ref_ptr<osg::Image> & image) const238 ImageLayerTileProcessor::process( osg::ref_ptr<osg::Image>& image ) const
239 {
240     if ( !image.valid() )
241         return;
242 
243     // Check to see if the image is the nodata image
244     if ( _noDataImage.valid() )
245     {
246         if (ImageUtils::areEquivalent(image.get(), _noDataImage.get()))
247         {
248             //OE_DEBUG << LC << "Found nodata" << std::endl;
249             image = 0L;
250             return;
251         }
252     }
253 
254     // If this is a compressed image, uncompress it IF the image is not already in the
255     // target profile...because if it's not in the target profile, we will have to do
256     // some mosaicing...and we can't mosaic a compressed image.
257     if (!_layerInTargetProfile &&
258         ImageUtils::isCompressed(image.get()) &&
259         ImageUtils::canConvert(image.get(), GL_RGBA, GL_UNSIGNED_BYTE) )
260     {
261         image = ImageUtils::convertToRGBA8( image.get() );
262     }
263 
264     // Apply a transparent color mask if one is specified
265     if ( _options.transparentColor().isSet() )
266     {
267         if ( !ImageUtils::hasAlphaChannel(image.get()) && ImageUtils::canConvert(image.get(), GL_RGBA, GL_UNSIGNED_BYTE) )
268         {
269             // if the image doesn't have an alpha channel, we must convert it to
270             // a format that does before continuing.
271             image = ImageUtils::convertToRGBA8( image.get() );
272         }
273 
274         ImageUtils::PixelVisitor<ApplyChromaKey> applyChroma;
275         applyChroma._chromaKey = _chromaKey;
276         applyChroma.accept( image.get() );
277     }
278 }
279 
280 //------------------------------------------------------------------------
281 
ImageLayer()282 ImageLayer::ImageLayer() :
283 TerrainLayer(&_optionsConcrete),
284 _options(&_optionsConcrete)
285 {
286     init();
287 }
288 
ImageLayer(const ImageLayerOptions & options)289 ImageLayer::ImageLayer(const ImageLayerOptions& options) :
290 TerrainLayer(&_optionsConcrete),
291 _options(&_optionsConcrete),
292 _optionsConcrete(options)
293 {
294     init();
295 }
296 
ImageLayer(const std::string & name,const TileSourceOptions & tileSourceOptions)297 ImageLayer::ImageLayer(const std::string& name, const TileSourceOptions& tileSourceOptions) :
298 TerrainLayer(&_optionsConcrete),
299 _options(&_optionsConcrete),
300 _optionsConcrete(name, tileSourceOptions)
301 {
302     init();
303 }
304 
ImageLayer(const ImageLayerOptions & options,TileSource * tileSource)305 ImageLayer::ImageLayer(const ImageLayerOptions& options, TileSource* tileSource) :
306 TerrainLayer(&_optionsConcrete, tileSource),
307 _options(&_optionsConcrete),
308 _optionsConcrete(options)
309 {
310     init();
311 }
312 
ImageLayer(ImageLayerOptions * optionsPtr)313 ImageLayer::ImageLayer(ImageLayerOptions* optionsPtr) :
314 TerrainLayer(optionsPtr? optionsPtr : &_optionsConcrete),
315 _options(optionsPtr? optionsPtr : &_optionsConcrete)
316 {
317     //init(); // will be called by subclass.
318 }
319 
320 const Status&
open()321 ImageLayer::open()
322 {
323     if (!_emptyImage.valid())
324         _emptyImage = ImageUtils::createEmptyImage();
325 
326     if ( options().shareTexUniformName().isSet() )
327         _shareTexUniformName = options().shareTexUniformName().get();
328     else
329         _shareTexUniformName.init( Stringify() << "layer_" << getUID() << "_tex" );
330 
331     if ( options().shareTexMatUniformName().isSet() )
332         _shareTexMatUniformName = options().shareTexMatUniformName().get();
333     else
334         _shareTexMatUniformName.init(Stringify() << _shareTexUniformName.get() << "_matrix");
335 
336     // If we are using createTexture to make image tiles,
337     // we don't need to load a tile source plugin.
338     if (useCreateTexture())
339     {
340         setTileSourceExpected(false);
341     }
342 
343     return TerrainLayer::open();
344 }
345 
346 void
init()347 ImageLayer::init()
348 {
349     TerrainLayer::init();
350 
351     _useCreateTexture = false;
352 
353     // image layers render as a terrain texture.
354     setRenderType(RENDERTYPE_TERRAIN_SURFACE);
355 
356     if (options().altitude().isSet())
357     {
358         setAltitude(options().altitude().get());
359     }
360 }
361 
362 void
setAltitude(const Distance & value)363 ImageLayer::setAltitude(const Distance& value)
364 {
365     options().altitude() = value;
366 
367     if (value != 0.0)
368     {
369         osg::StateSet* stateSet = getOrCreateStateSet();
370 
371         stateSet->addUniform(
372             new osg::Uniform("oe_terrain_altitude", (float)options().altitude()->as(Units::METERS)),
373             osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
374 
375         stateSet->setMode(GL_CULL_FACE, 0);
376     }
377     else
378     {
379         osg::StateSet* stateSet = getOrCreateStateSet();
380         getOrCreateStateSet()->removeUniform("oe_terrain_altitude");
381         stateSet->removeMode(GL_CULL_FACE);
382     }
383     fireCallback( &ImageLayerCallback::onAltitudeChanged );
384 }
385 
386 void
fireCallback(ImageLayerCallback::MethodPtr method)387 ImageLayer::fireCallback(ImageLayerCallback::MethodPtr method)
388 {
389     for(CallbackVector::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i)
390     {
391         ImageLayerCallback* cb = dynamic_cast<ImageLayerCallback*>(i->get());
392         if (cb) (cb->*method)( this );
393     }
394 }
395 
396 void
setUseCreateTexture()397 ImageLayer::setUseCreateTexture()
398 {
399     _useCreateTexture = true;
400     setTileSourceExpected(false);
401 }
402 
403 bool
isShared() const404 ImageLayer::isShared() const
405 {
406     return options().shared().get();
407 }
408 
409 bool
isCoverage() const410 ImageLayer::isCoverage() const
411 {
412     return options().coverage().get();
413 }
414 
415 void
addColorFilter(ColorFilter * filter)416 ImageLayer::addColorFilter( ColorFilter* filter )
417 {
418     options().colorFilters().push_back( filter );
419     fireCallback( &ImageLayerCallback::onColorFiltersChanged );
420 }
421 
422 void
removeColorFilter(ColorFilter * filter)423 ImageLayer::removeColorFilter( ColorFilter* filter )
424 {
425     ColorFilterChain& filters = options().colorFilters();
426     ColorFilterChain::iterator i = std::find(filters.begin(), filters.end(), filter);
427     if ( i != filters.end() )
428     {
429         filters.erase( i );
430         fireCallback( &ImageLayerCallback::onColorFiltersChanged );
431     }
432 }
433 
434 const ColorFilterChain&
getColorFilters() const435 ImageLayer::getColorFilters() const
436 {
437     return options().colorFilters();
438 }
439 
440 void
setTargetProfileHint(const Profile * profile)441 ImageLayer::setTargetProfileHint( const Profile* profile )
442 {
443     TerrainLayer::setTargetProfileHint( profile );
444 
445     // if we've already constructed the pre-cache operation, reinitialize it.
446     _preCacheOp = 0L;
447 }
448 
449 TileSource::ImageOperation*
getOrCreatePreCacheOp()450 ImageLayer::getOrCreatePreCacheOp()
451 {
452     if ( !_preCacheOp.valid() )
453     {
454         Threading::ScopedMutexLock lock(_mutex);
455         if ( !_preCacheOp.valid() )
456         {
457             bool layerInTargetProfile =
458                 _targetProfileHint.valid() &&
459                 getProfile()               &&
460                 _targetProfileHint->isEquivalentTo( getProfile() );
461 
462             ImageLayerPreCacheOperation* op = new ImageLayerPreCacheOperation();
463             op->_processor.init( options(), _readOptions.get(), layerInTargetProfile );
464 
465             _preCacheOp = op;
466         }
467     }
468     return _preCacheOp.get();
469 }
470 
471 GeoImage
createImage(const TileKey & key,ProgressCallback * progress)472 ImageLayer::createImage(const TileKey&    key,
473                         ProgressCallback* progress)
474 {
475     ScopedMetric m("ImageLayer::createImage", 2,
476                     "key", key.str().c_str(),
477                     "name", getName().c_str());
478 
479     if (getStatus().isError())
480     {
481         return GeoImage::INVALID;
482     }
483 
484     return createImageInKeyProfile( key, progress );
485 }
486 
487 GeoImage
createImageImplementation(const TileKey & key,ProgressCallback * progress)488 ImageLayer::createImageImplementation(const TileKey& key, ProgressCallback* progress)
489 {
490     // Check here in case a subclass calls this method directly.
491     //if ( !isKeyInLegalRange(key) )
492     //{
493     //    return GeoImage::INVALID;
494     //}
495 
496     return createImageFromTileSource(key, progress);
497 }
498 
499 GeoImage
createImageInNativeProfile(const TileKey & key,ProgressCallback * progress)500 ImageLayer::createImageInNativeProfile(const TileKey&    key,
501                                        ProgressCallback* progress)
502 {
503     if (getStatus().isError())
504     {
505         return GeoImage::INVALID;
506     }
507 
508     const Profile* nativeProfile = getProfile();
509     if ( !nativeProfile )
510     {
511         OE_WARN << LC << "Could not establish the profile" << std::endl;
512         return GeoImage::INVALID;
513     }
514 
515 
516     GeoImage result;
517 
518     if ( key.getProfile()->isHorizEquivalentTo(nativeProfile) )
519     {
520         // requested profile matches native profile, move along.
521         result = createImageInKeyProfile( key, progress );
522     }
523     else
524     {
525         // find the intersection of keys.
526         std::vector<TileKey> nativeKeys;
527         nativeProfile->getIntersectingTiles(key, nativeKeys);
528 
529         // build a mosaic of the images from the native profile keys:
530         bool foundAtLeastOneRealTile = false;
531 
532         ImageMosaic mosaic;
533         for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k )
534         {
535             GeoImage image = createImageInKeyProfile( *k, progress );
536             if ( image.valid() )
537             {
538                 foundAtLeastOneRealTile = true;
539                 mosaic.getImages().push_back( TileImage(image.getImage(), *k) );
540             }
541             else
542             {
543                 // We didn't get an image so pad the mosaic with a transparent image.
544                 mosaic.getImages().push_back( TileImage(ImageUtils::createEmptyImage(getTileSize(), getTileSize()), *k));
545             }
546         }
547 
548         // bail out if we got nothing.
549         if ( foundAtLeastOneRealTile )
550         {
551             // assemble new GeoImage from the mosaic.
552             double rxmin, rymin, rxmax, rymax;
553             mosaic.getExtents( rxmin, rymin, rxmax, rymax );
554 
555             result = GeoImage(
556                 mosaic.createImage(),
557                 GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) );
558         }
559     }
560 
561     return result;
562 }
563 
564 
565 GeoImage
createImageInKeyProfile(const TileKey & key,ProgressCallback * progress)566 ImageLayer::createImageInKeyProfile(const TileKey&    key,
567                                     ProgressCallback* progress)
568 {
569     // If the layer is disabled, bail out.
570     if ( !getEnabled() )
571     {
572         return GeoImage::INVALID;
573     }
574 
575     // Make sure the request is in range.
576     // TODO: perhaps this should be a call to mayHaveData(key) instead.
577     if ( !isKeyInLegalRange(key) )
578     {
579         return GeoImage::INVALID;
580     }
581 
582 
583     GeoImage result;
584 
585     OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= "
586         << key.getExtent().toString() << std::endl;
587 
588     // the cache key combines the Key and the horizontal profile.
589     std::string cacheKey = Cache::makeCacheKey(
590         Stringify() << key.str() << "-" << key.getProfile()->getHorizSignature(),
591         "image");
592 
593     const CachePolicy& policy = getCacheSettings()->cachePolicy().get();
594 
595     // Check the layer L2 cache first
596     if ( _memCache.valid() )
597     {
598         CacheBin* bin = _memCache->getOrCreateDefaultBin();
599         ReadResult result = bin->readObject(cacheKey, 0L);
600         if ( result.succeeded() )
601             return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent());
602     }
603 
604     // locate the cache bin for the target profile for this layer:
605     CacheBin* cacheBin = getCacheBin( key.getProfile() );
606 
607 
608     // Can we continue? Only if either:
609     //  a) there is a valid tile source plugin;
610     //  b) a tile source is not expected, meaning the subclass overrides getHeightField; or
611     //  c) we are in cache-only mode and there is a valid cache bin.
612     bool canContinue =
613         getTileSource() ||
614         !isTileSourceExpected() ||
615         (policy.isCacheOnly() && cacheBin != 0L);
616 
617     if (!canContinue)
618     {
619         disable("Error: layer does not have a valid TileSource, cannot create image");
620         return GeoImage::INVALID;
621     }
622 
623     // validate the existance of a valid layer profile (unless we're in cache-only mode, in which
624     // case there is no layer profile)
625     if ( !policy.isCacheOnly() && !getProfile() )
626     {
627         disable("Could not establish a valid profile");
628         return GeoImage::INVALID;
629     }
630 
631     osg::ref_ptr< osg::Image > cachedImage;
632 
633     // First, attempt to read from the cache. Since the cached data is stored in the
634     // map profile, we can try this first.
635     if ( cacheBin && policy.isCacheReadable() )
636     {
637         ReadResult r = cacheBin->readImage(cacheKey, 0L);
638         if ( r.succeeded() )
639         {
640             cachedImage = r.releaseImage();
641             ImageUtils::fixInternalFormat( cachedImage.get() );
642             bool expired = policy.isExpired(r.lastModifiedTime());
643             if (!expired)
644             {
645                 OE_DEBUG << "Got cached image for " << key.str() << std::endl;
646                 return GeoImage( cachedImage.get(), key.getExtent() );
647             }
648             else
649             {
650                 OE_DEBUG << "Expired image for " << key.str() << std::endl;
651             }
652         }
653     }
654 
655     // The data was not in the cache. If we are cache-only, fail sliently
656     if ( policy.isCacheOnly() )
657     {
658         // If it's cache only and we have an expired but cached image, just return it.
659         if (cachedImage.valid())
660         {
661             return GeoImage( cachedImage.get(), key.getExtent() );
662         }
663         else
664         {
665             return GeoImage::INVALID;
666         }
667     }
668 
669     if (key.getProfile()->isHorizEquivalentTo(getProfile()))
670     {
671         result = createImageImplementation(key, progress);
672     }
673     else
674     {
675         // If the profiles are different, use a compositing method to assemble the tile.
676         result = assembleImage( key, progress );
677     }
678 
679     // Normalize the image if necessary
680     if ( result.valid() )
681     {
682         ImageUtils::fixInternalFormat( result.getImage() );
683     }
684 
685     // Check for cancelation before writing to a cache:
686     if (progress && progress->isCanceled())
687     {
688         return GeoImage::INVALID;
689     }
690 
691     // memory cache first:
692     if ( result.valid() && _memCache.valid() )
693     {
694         CacheBin* bin = _memCache->getOrCreateDefaultBin();
695         bin->write(cacheKey, result.getImage(), 0L);
696     }
697 
698     // If we got a result, the cache is valid and we are caching in the map profile,
699     // write to the map cache.
700     if (result.valid()  &&
701         cacheBin        &&
702         policy.isCacheWriteable())
703     {
704         if ( key.getExtent() != result.getExtent() )
705         {
706             OE_INFO << LC << "WARNING! mismatched extents." << std::endl;
707         }
708 
709         cacheBin->write(cacheKey, result.getImage(), 0L);
710     }
711 
712     if ( result.valid() )
713     {
714         OE_DEBUG << LC << key.str() << " result OK" << std::endl;
715     }
716     else
717     {
718         OE_DEBUG << LC << key.str() << "result INVALID" << std::endl;
719         // We couldn't get an image from the source.  So see if we have an expired cached image
720         if (cachedImage.valid())
721         {
722             OE_DEBUG << LC << "Using cached but expired image for " << key.str() << std::endl;
723             result = GeoImage( cachedImage.get(), key.getExtent());
724         }
725     }
726 
727     return result;
728 }
729 
730 
731 
732 GeoImage
createImageFromTileSource(const TileKey & key,ProgressCallback * progress)733 ImageLayer::createImageFromTileSource(const TileKey&    key,
734                                       ProgressCallback* progress)
735 {
736     TileSource* source = getTileSource();
737     if ( !source )
738         return GeoImage::INVALID;
739 
740     // If the profiles are different, use a compositing method to assemble the tile.
741     if ( !key.getProfile()->isHorizEquivalentTo( getProfile() ) )
742     {
743         return assembleImage( key, progress );
744     }
745 
746     // Good to go, ask the tile source for an image:
747     osg::ref_ptr<TileSource::ImageOperation> op = getOrCreatePreCacheOp();
748 
749     // Fail is the image is blacklisted.
750     if ( source->getBlacklist()->contains(key) )
751     {
752         OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl;
753         return GeoImage::INVALID;
754     }
755 
756     if (!mayHaveData(key))
757     {
758         OE_DEBUG << LC << "createImageFromTileSource: mayHaveData(" << key.str() << ") == false" << std::endl;
759         return GeoImage::INVALID;
760     }
761 
762     //if ( !source->hasData( key ) )
763     //{
764     //    OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl;
765     //    return GeoImage::INVALID;
766     //}
767 
768     // create an image from the tile source.
769     osg::ref_ptr<osg::Image> result = source->createImage( key, op.get(), progress );
770 
771     // Process images with full alpha to properly support MP blending.
772     if (result.valid() &&
773         options().featherPixels() == true)
774     {
775         ImageUtils::featherAlphaRegions( result.get() );
776     }
777 
778     // If image creation failed (but was not intentionally canceled and
779     // didn't time out or end for any other recoverable reason), then
780     // blacklist this tile for future requests.
781     if (result == 0L)
782     {
783         if ( progress == 0L || !progress->isCanceled() )
784         {
785             source->getBlacklist()->add( key );
786         }
787     }
788 
789     if (progress && progress->isCanceled())
790     {
791         return GeoImage::INVALID;
792     }
793 
794     return GeoImage(result.get(), key.getExtent());
795 }
796 
797 
798 GeoImage
assembleImage(const TileKey & key,ProgressCallback * progress)799 ImageLayer::assembleImage(const TileKey& key, ProgressCallback* progress)
800 {
801     // If we got here, asset that there's a non-null layer profile.
802     if (!getProfile())
803     {
804         setStatus(Status::Error(Status::AssertionFailure, "assembleImage with undefined profile"));
805         return GeoImage::INVALID;
806     }
807 
808     GeoImage mosaicedImage, result;
809 
810     // Scale the extent if necessary to apply an "edge buffer"
811     GeoExtent ext = key.getExtent();
812     if ( options().edgeBufferRatio().isSet() )
813     {
814         double ratio = options().edgeBufferRatio().get();
815         ext.scale(ratio, ratio);
816     }
817 
818     // Get a set of layer tiles that intersect the requested extent.
819     std::vector<TileKey> intersectingKeys;
820     getProfile()->getIntersectingTiles( key, intersectingKeys );
821 
822     if ( intersectingKeys.size() > 0 )
823     {
824         double dst_minx, dst_miny, dst_maxx, dst_maxy;
825         key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy);
826 
827         // if we find at least one "real" tile in the mosaic, then the whole result tile is
828         // "real" (i.e. not a fallback tile)
829         bool retry = false;
830         ImageMosaic mosaic;
831 
832         // keep track of failed tiles.
833         std::vector<TileKey> failedKeys;
834 
835         for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k )
836         {
837             GeoImage image = createImageImplementation( *k, progress );
838 
839             if ( image.valid() )
840             {
841                 if ( !isCoverage() )
842                 {
843                     ImageUtils::fixInternalFormat(image.getImage());
844 
845                     // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels.
846                     // This is not the smarter choice (in some case RGB would be sufficient) but
847                     // it ensure consistency between all images / layers.
848                     //
849                     // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images.
850                     // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint
851                     //
852                     if (   (image.getImage()->getDataType() != GL_UNSIGNED_BYTE)
853                         || (image.getImage()->getPixelFormat() != GL_RGBA) )
854                     {
855                         osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
856                         if (convertedImg.valid())
857                         {
858                             image = GeoImage(convertedImg.get(), image.getExtent());
859                         }
860                     }
861                 }
862 
863                 mosaic.getImages().push_back( TileImage(image.getImage(), *k) );
864             }
865             else
866             {
867                 // the tile source did not return a tile, so make a note of it.
868                 failedKeys.push_back( *k );
869 
870                 if (progress && progress->isCanceled())
871                 {
872                     retry = true;
873                     break;
874                 }
875             }
876         }
877 
878         // Fail is: a) we got no data and the LOD is greater than zero; or
879         // b) the operation was canceled mid-stream.
880         if ( (mosaic.getImages().empty() && key.getLOD() > 0) || retry)
881         {
882             // if we didn't get any data at LOD>0, fail.
883             OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl;
884             return GeoImage::INVALID;
885         }
886 
887         // We got at least one good tile, OR we got nothing but since the LOD==0 we have to
888         // fall back on a lower resolution.
889         // So now we go through the failed keys and try to fall back on lower resolution data
890         // to fill in the gaps. The entire mosaic must be populated or this qualifies as a bad tile.
891         for(std::vector<TileKey>::iterator k = failedKeys.begin(); k != failedKeys.end(); ++k)
892         {
893             GeoImage image;
894 
895             for(TileKey parentKey = k->createParentKey();
896                 parentKey.valid() && !image.valid();
897                 parentKey = parentKey.createParentKey())
898             {
899                 image = createImageImplementation( parentKey, progress );
900                 if ( image.valid() )
901                 {
902                     GeoImage cropped;
903 
904                     if ( !isCoverage() )
905                     {
906                         ImageUtils::fixInternalFormat(image.getImage());
907                         if (   (image.getImage()->getDataType() != GL_UNSIGNED_BYTE)
908                             || (image.getImage()->getPixelFormat() != GL_RGBA) )
909                         {
910                             osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage());
911                             if (convertedImg.valid())
912                             {
913                                 image = GeoImage(convertedImg.get(), image.getExtent());
914                             }
915                         }
916 
917                         cropped = image.crop( k->getExtent(), false, image.getImage()->s(), image.getImage()->t() );
918                     }
919 
920                     else
921                     {
922                         // TODO: may not work.... test; tilekey extent will <> cropped extent
923                         cropped = image.crop( k->getExtent(), true, image.getImage()->s(), image.getImage()->t(), false );
924                     }
925 
926                     // and queue it.
927                     mosaic.getImages().push_back( TileImage(cropped.getImage(), *k) );
928 
929                 }
930             }
931 
932             if ( !image.valid() )
933             {
934                 // a tile completely failed, even with fallback. Eject.
935                 OE_DEBUG << LC << "Couldn't fallback on tiles for ImageMosaic" << std::endl;
936                 // let it go. The empty areas will be filled with alpha by ImageMosaic.
937             }
938         }
939 
940         // all set. Mosaic all the images together.
941         double rxmin, rymin, rxmax, rymax;
942         mosaic.getExtents( rxmin, rymin, rxmax, rymax );
943 
944         mosaicedImage = GeoImage(
945             mosaic.createImage(),
946             GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) );
947     }
948     else
949     {
950         OE_DEBUG << LC << "assembleImage: no intersections (" << key.str() << ")" << std::endl;
951     }
952 
953     // Final step: transform the mosaic into the requesting key's extent.
954     if ( mosaicedImage.valid() )
955     {
956         // GeoImage::reproject() will automatically crop the image to the correct extents.
957         // so there is no need to crop after reprojection. Also note that if the SRS's are the
958         // same (even though extents are different), then this operation is technically not a
959         // reprojection but merely a resampling.
960 
961         result = mosaicedImage.reproject(
962             key.getProfile()->getSRS(),
963             &key.getExtent(),
964             getTileSize(), getTileSize(),
965             options().driver()->bilinearReprojection().get());
966     }
967 
968     // Process images with full alpha to properly support MP blending.
969     if (result.valid() &&
970         options().featherPixels() == true &&
971         isCoverage() == false)
972     {
973         ImageUtils::featherAlphaRegions( result.getImage() );
974     }
975 
976     if (progress && progress->isCanceled())
977     {
978         return GeoImage::INVALID;
979     }
980 
981     return result;
982 }
983 
984 
985 void
applyTextureCompressionMode(osg::Texture * tex) const986 ImageLayer::applyTextureCompressionMode(osg::Texture* tex) const
987 {
988     if ( tex == 0L )
989         return;
990 
991     // Coverages are not allowed to use compression since it will corrupt the data
992     if ( isCoverage() )
993     {
994         tex->setInternalFormatMode(osg::Texture::USE_IMAGE_DATA_FORMAT);
995     }
996 
997 
998     else if ( options().textureCompression() == (osg::Texture::InternalFormatMode)~0 )
999     {
1000         // auto mode:
1001         if ( Registry::capabilities().isGLES() )
1002         {
1003             // Many GLES drivers do not support automatic compression, so by
1004             // default, don't set the internal format.
1005             // TODO: later perhaps we can replace this with a CPU-side
1006             // compression step for PV or ETC
1007             tex->setInternalFormatMode(osg::Texture::USE_IMAGE_DATA_FORMAT);
1008         }
1009         else
1010         {
1011             // compute the best available mode.
1012             osg::Texture::InternalFormatMode mode;
1013             if (ImageUtils::computeTextureCompressionMode(tex->getImage(0), mode))
1014             {
1015                 tex->setInternalFormatMode(mode);
1016             }
1017         }
1018     }
1019     else if ( options().textureCompression() == (osg::Texture::InternalFormatMode)(~0 - 1))
1020     {
1021         osg::Timer_t start = osg::Timer::instance()->tick();
1022         osgDB::ImageProcessor* imageProcessor = osgDB::Registry::instance()->getImageProcessorForExtension("fastdxt");
1023         if (imageProcessor)
1024         {
1025             osg::Texture::InternalFormatMode mode;
1026             // RGB uses DXT1
1027             if (tex->getImage(0)->getPixelFormat() == GL_RGB)
1028             {
1029                 mode = osg::Texture::USE_S3TC_DXT1_COMPRESSION;
1030             }
1031             // RGBA uses DXT5
1032             else if (tex->getImage(0)->getPixelFormat() == GL_RGBA)
1033             {
1034                 mode = osg::Texture::USE_S3TC_DXT5_COMPRESSION;
1035             }
1036             else
1037             {
1038                 OE_DEBUG << "FastDXT only works on GL_RGBA or GL_RGB images" << std::endl;
1039                 return;
1040             }
1041 
1042             osg::Image *image = tex->getImage(0);
1043             imageProcessor->compress(*image, mode, false, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::FASTEST);
1044             osg::Timer_t end = osg::Timer::instance()->tick();
1045             image->dirty();
1046             tex->setImage(0, image);
1047             OE_DEBUG << "Compress took " << osg::Timer::instance()->delta_m(start, end) << std::endl;
1048         }
1049         else
1050         {
1051             OE_WARN << "Failed to get ImageProcessor fastdxt" << std::endl;
1052         }
1053 
1054     }
1055     else if ( options().textureCompression().isSet() )
1056     {
1057         // use specifically picked a mode.
1058         tex->setInternalFormatMode(options().textureCompression().get());
1059     }
1060 }
1061 
1062 
1063 void
modifyTileBoundingBox(const TileKey & key,osg::BoundingBox & box) const1064 ImageLayer::modifyTileBoundingBox(const TileKey& key, osg::BoundingBox& box) const
1065 {
1066     if (options().altitude().isSet())
1067     {
1068         if (options().altitude()->as(Units::METERS) > box.zMax())
1069         {
1070             box.zMax() = options().altitude()->as(Units::METERS);
1071         }
1072     }
1073     TerrainLayer::modifyTileBoundingBox(key, box);
1074 }