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 }