diff --git a/simgear/scene/material/mipmap.cxx b/simgear/scene/material/mipmap.cxx index 59a800da..f2487ef0 100644 --- a/simgear/scene/material/mipmap.cxx +++ b/simgear/scene/material/mipmap.cxx @@ -392,6 +392,8 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs ) image->getInternalTextureFormat(), image->getPixelFormat(), image->getDataType(), data, osg::Image::USE_NEW_DELETE, image->getPacking() ); mipmaps->setMipmapLevels( mipmapOffsets ); + mipmaps->setName(image->getName()); + mipmaps->setFileName(image->getFileName()); return mipmaps.release(); } diff --git a/simgear/scene/model/ModelRegistry.cxx b/simgear/scene/model/ModelRegistry.cxx index f788162a..2aea1bc8 100644 --- a/simgear/scene/model/ModelRegistry.cxx +++ b/simgear/scene/model/ModelRegistry.cxx @@ -22,6 +22,8 @@ #endif #include "ModelRegistry.hxx" +#include +#include "../material/mipmap.hxx" #include #include @@ -44,6 +46,7 @@ #include #include #include +#include #include @@ -61,6 +64,10 @@ #include "BoundingVolumeBuildVisitor.hxx" #include "model.hxx" +#include +#include +#include + using namespace std; using namespace osg; using namespace osgUtil; @@ -173,110 +180,278 @@ public: } // namespace -Node* DefaultProcessPolicy::process(Node* node, const string& filename, - const Options* opt) +static bool isPowerOfTwo(int width, int height) +{ + return (((width & (width - 1)) == 0) && ((height & (height - 1))) == 0); +} +osg::Node* DefaultProcessPolicy::process(osg::Node* node, const std::string& filename, + const Options* opt) { TextureNameVisitor nameVisitor; node->accept(nameVisitor); return node; } +//#define LOCAL_IMAGE_CACHE +#ifdef LOCAL_IMAGE_CACHE +typedef std::map> ImageMap; +ImageMap _imageMap; +//typedef std::map > ImageMap; +//ImageMap _imageMap; +osg::Image* getImageByName(const std::string& filename) +{ + ImageMap::iterator itr = _imageMap.find(filename); + if (itr != _imageMap.end()) return itr->second.get(); + return nullptr; +} +#endif + ReaderWriter::ReadResult ModelRegistry::readImage(const string& fileName, - const Options* opt) + const Options* opt) { - CallbackMap::iterator iter - = imageCallbackMap.find(getFileExtension(fileName)); - { - if (iter != imageCallbackMap.end() && iter->second.valid()) - return iter->second->readImage(fileName, opt); - string absFileName = SGModelLib::findDataFile(fileName, opt); - if (!fileExists(absFileName)) { - SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \"" - << fileName << "\""); - return ReaderWriter::ReadResult::FILE_NOT_FOUND; + /* + * processor is the interface to the osg_nvtt plugin + */ + static bool init = false; + static osgDB::ImageProcessor *processor = 0; + int max_texture_size = SGSceneFeatures::instance()->getMaxTextureSize(); + if (!init) { + processor = osgDB::Registry::instance()->getImageProcessor(); + init = true; + } + + bool persist = true; + bool cache_active = SGSceneFeatures::instance()->getTextureCacheActive(); + bool compress_solid = SGSceneFeatures::instance()->getTextureCacheCompressionActive(); + bool compress_transparent = SGSceneFeatures::instance()->getTextureCacheCompressionActiveTransparent(); + + // + // heuristically less than 2048 is more likely to be a badly reported size rather than + // something that is valid so we'll have a minimum size of 2048. + if (max_texture_size < 2048) + max_texture_size = 2048; + + std::string fileExtension = getFileExtension(fileName); + CallbackMap::iterator iter = imageCallbackMap.find(fileExtension); + + if (iter != imageCallbackMap.end() && iter->second.valid()) + return iter->second->readImage(fileName, opt); + string absFileName = SGModelLib::findDataFile(fileName, opt); + + if (!fileExists(absFileName)) { + SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \"" + << fileName << "\""); + return ReaderWriter::ReadResult::FILE_NOT_FOUND; + } + Registry* registry = Registry::instance(); + ReaderWriter::ReadResult res; + + if (cache_active) { + if (fileExtension != "dds" && fileExtension != "gz") { + std::string root = getPathRoot(absFileName); + std::string prr = getPathRelative(root, absFileName); + std::string cache_root = SGSceneFeatures::instance()->getTextureCompressionPath().c_str(); + std::string newName = cache_root + "/" + prr; + + SGPath file(absFileName); + std::stringstream tstream; + tstream << std::hex << file.modTime(); + newName += "." + tstream.str(); + + newName += ".cache.dds"; + if (!fileExists(newName)) { + res = registry->readImageImplementation(absFileName, opt); + + osg::ref_ptr srcImage = res.getImage(); + int width = srcImage->s(); + bool transparent = srcImage->isImageTranslucent(); + int height = srcImage->t(); + + if (height >= max_texture_size) + { + SG_LOG(SG_IO, SG_WARN, "Image texture too high " << width << "," << height << absFileName); + osg::ref_ptr resizedImage; + int factor = height / max_texture_size; + if (ImageUtils::resizeImage(srcImage, width / factor, height / factor, resizedImage)) + srcImage = resizedImage; + width = srcImage->s(); + height = srcImage->t(); + } + if (width >= max_texture_size) + { + SG_LOG(SG_IO, SG_WARN, "Image texture too wide " << width << "," << height << absFileName); + osg::ref_ptr resizedImage; + int factor = width / max_texture_size; + if (ImageUtils::resizeImage(srcImage, width / factor, height / factor, resizedImage)) + srcImage = resizedImage; + width = srcImage->s(); + height = srcImage->t(); + } + + // + // only cache power of two textures that are of a reasonable size + if (width >= 64 && height >= 64 && isPowerOfTwo(width, height)) { + simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE); + + SGPath filePath(newName); + filePath.create_dir(); + + // setup the options string for saving the texture as we don't want OSG to auto flip the texture + // as this complicates loading as it requires a flag to flip it back which will preclude the + // image from being cached because we will have to clone the options to set the flag and thus lose + // the link to the cache in the options from the caller. + osg::ref_ptr nopt; + nopt = opt->cloneOptions(); + std::string optionstring = nopt->getOptionString(); + + if (!optionstring.empty()) + optionstring += " "; + + nopt->setOptionString(optionstring + "ddsNoAutoFlipWrite"); + + /* + * decide if we need to compress this. + */ + bool compress = (transparent && compress_transparent) || (!transparent && compress_solid); + + if (compress) { + if (processor) + { + if (transparent) + processor->compress(*srcImage, osg::Texture::USE_S3TC_DXT5_COMPRESSION, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION); + else + processor->compress(*srcImage, osg::Texture::USE_S3TC_DXT1_COMPRESSION, true, true, osgDB::ImageProcessor::USE_CPU, osgDB::ImageProcessor::PRODUCTION); + //processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU); + } + else { + simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE); + SG_LOG(SG_IO, SG_WARN, "Texture compression plugin (osg_nvtt) not available; storing uncompressed image: " << newName); + srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions); + } + } + else { + if (processor) { + processor->generateMipMap(*srcImage, true, osgDB::ImageProcessor::USE_CPU); + } + else { + simgear::effect::MipMapTuple mipmapFunctions(simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE, simgear::effect::AVERAGE); + srcImage = simgear::effect::computeMipmap(srcImage, mipmapFunctions); + } + } + if (persist) { + registry->writeImage(*srcImage, newName, nopt); + //printf(" ->> written to %s\n", newName.c_str()); + + } + else { + return srcImage; + } + absFileName = newName; + } + } + else + absFileName = newName; } + } + res = registry->readImageImplementation(absFileName, opt); - Registry* registry = Registry::instance(); - ReaderWriter::ReadResult res; - res = registry->readImageImplementation(absFileName, opt); - if (!res.success()) { - SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message()); - return res; - } + if (!res.success()) { + SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message()); + return res; + } - if (res.loadedFromCache()) - SG_LOG(SG_IO, SG_BULK, "Returning cached image \"" - << res.getImage()->getFileName() << "\""); - else - SG_LOG(SG_IO, SG_BULK, "Reading image \"" - << res.getImage()->getFileName() << "\""); + osg::ref_ptr srcImage1 = res.getImage(); + //printf(" --> finished loading %s [%s] (%s) %d\n", absFileName.c_str(), srcImage1->getFileName().c_str(), res.loadedFromCache() ? "from cache" : "from disk", res.getImage()->getOrigin()); + /* + * Fixup the filename - as when loading from eg. dds.gz the originating filename is lost in the conversion due to the way the OSG loader works + */ + if (srcImage1->getFileName().empty()) { + srcImage1->setFileName(absFileName); + } + + if (srcImage1->getName().empty()) { + srcImage1->setName(absFileName); + } + + if (res.loadedFromCache()) + SG_LOG(SG_IO, SG_BULK, "Returning cached image \"" + << res.getImage()->getFileName() << "\""); + else + SG_LOG(SG_IO, SG_BULK, "Reading image \"" + << res.getImage()->getFileName() << "\""); + + // as of March 2018 all patents have expired, https://en.wikipedia.org/wiki/S3_Texture_Compression#Patent + // there is support for S3TC DXT1..5 in MESA https://www.phoronix.com/scan.php?page=news_item&px=S3TC-Lands-In-Mesa + // so it seems that there isn't a valid reason to warn any longer; and beside this is one of those cases where it should + // really only be a developer message +#ifdef WARN_DDS_TEXTURES // Check for precompressed textures that depend on an extension - switch (res.getImage()->getPixelFormat()) { + switch (res.getImage()->getPixelFormat()) { - // GL_EXT_texture_compression_s3tc - // patented, no way to decompress these + // GL_EXT_texture_compression_s3tc + // patented, no way to decompress these #ifndef GL_EXT_texture_compression_s3tc #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 #endif - case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - // GL_EXT_texture_sRGB - // patented, no way to decompress these + // GL_EXT_texture_sRGB + // patented, no way to decompress these #ifndef GL_EXT_texture_sRGB #define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F #endif - case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: - case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: - case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: - case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: - // GL_TDFX_texture_compression_FXT1 - // can decompress these in software but - // no code present in simgear. + // GL_TDFX_texture_compression_FXT1 + // can decompress these in software but + // no code present in simgear. #ifndef GL_3DFX_texture_compression_FXT1 #define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 #define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 #endif - case GL_COMPRESSED_RGB_FXT1_3DFX: - case GL_COMPRESSED_RGBA_FXT1_3DFX: + case GL_COMPRESSED_RGB_FXT1_3DFX: + case GL_COMPRESSED_RGBA_FXT1_3DFX: - // GL_EXT_texture_compression_rgtc - // can decompress these in software but - // no code present in simgear. + // GL_EXT_texture_compression_rgtc + // can decompress these in software but + // no code present in simgear. #ifndef GL_EXT_texture_compression_rgtc #define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB #define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC #define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE #endif - case GL_COMPRESSED_RED_RGTC1_EXT: - case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: - case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: - case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_RED_RGTC1_EXT: + case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: + case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: - SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n" - "uses compressed textures which cannot be supported on " - "some systems.\n" - "Please decompress this texture for improved portability."); - break; + SG_LOG(SG_IO, SG_WARN, "Image \"" << fileName << "\"\n" + "uses compressed textures which cannot be supported on " + "some systems.\n" + "Please decompress this texture for improved portability."); + break; - default: - break; - } - - return res; + default: + break; } +#endif + + return res; } @@ -430,7 +605,7 @@ public: setObjectCacheHint((Options::CacheHintOptions)cacheOptions); registry->setOptions(options); registry->getOrCreateSharedStateManager()-> - setShareMode(SharedStateManager::SHARE_STATESETS); + setShareMode(SharedStateManager::SHARE_ALL); registry->setReadFileCallback(ModelRegistry::instance()); } }; @@ -520,9 +695,79 @@ typedef ModelRegistryCallback OBJCallback; + +// we get optimal geometry from the loader (Hah!). +struct IVEOptimizePolicy : public OptimizeModelPolicy { + IVEOptimizePolicy(const string& extension) : + OptimizeModelPolicy(extension) + { + _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY; + } + Node* optimize(Node* node, const string& fileName, + const Options* opt) + { + ref_ptr optimized + = OptimizeModelPolicy::optimize(node, fileName, opt); + Group* group = dynamic_cast(optimized.get()); + MatrixTransform* transform + = dynamic_cast(optimized.get()); + if (((transform && transform->getMatrix().isIdentity()) || group) + && group->getName().empty() + && group->getNumChildren() == 1) { + optimized = static_cast(group->getChild(0)); + group = dynamic_cast(optimized.get()); + if (group && group->getName().empty() + && group->getNumChildren() == 1) + optimized = static_cast(group->getChild(0)); + } + const SGReaderWriterOptions* sgopt + = dynamic_cast(opt); + + if (sgopt && sgopt->getInstantiateMaterialEffects()) { + optimized = instantiateMaterialEffects(optimized.get(), sgopt); + } + else if (sgopt && sgopt->getInstantiateEffects()) { + optimized = instantiateEffects(optimized.get(), sgopt); + } + + return optimized.release(); + } +}; + +struct IVEProcessPolicy { + IVEProcessPolicy(const string& extension) {} + Node* process(Node* node, const string& filename, + const Options* opt) + { + Matrix m(1, 0, 0, 0, + 0, 0, 1, 0, + 0, -1, 0, 0, + 0, 0, 0, 1); + // XXX Does there need to be a Group node here to trick the + // optimizer into optimizing the static transform? + osg::Group* root = new Group; + MatrixTransform* transform = new MatrixTransform; + root->addChild(transform); + + transform->setDataVariance(Object::STATIC); + transform->setMatrix(m); + transform->addChild(node); + + return root; + } +}; + +typedef ModelRegistryCallback + IVECallback; + namespace { ModelRegistryCallbackProxy g_acRegister("ac"); ModelRegistryCallbackProxy g_objRegister("obj"); +ModelRegistryCallbackProxy g_iveRegister("ive"); +ModelRegistryCallbackProxy g_osgtRegister("osgt"); +ModelRegistryCallbackProxy g_osgbRegister("osgb"); } diff --git a/simgear/scene/util/CMakeLists.txt b/simgear/scene/util/CMakeLists.txt index dd135077..7177f87e 100644 --- a/simgear/scene/util/CMakeLists.txt +++ b/simgear/scene/util/CMakeLists.txt @@ -16,6 +16,7 @@ set(HEADERS RenderConstants.hxx SGDebugDrawCallback.hxx SGEnlargeBoundingBox.hxx + SGImageUtils.hxx SGNodeMasks.hxx SGPickCallback.hxx SGReaderWriterOptions.hxx @@ -44,6 +45,7 @@ set(SOURCES PrimitiveUtils.cxx QuadTreeBuilder.cxx SGEnlargeBoundingBox.cxx + SGImageUtils.cxx SGReaderWriterOptions.cxx SGSceneFeatures.cxx SGSceneUserData.cxx diff --git a/simgear/scene/util/SGImageUtils.cxx b/simgear/scene/util/SGImageUtils.cxx new file mode 100644 index 00000000..7f79cd7b --- /dev/null +++ b/simgear/scene/util/SGImageUtils.cxx @@ -0,0 +1,2167 @@ +/* -*-c++-*- */ +/* + * SimGear ImageUtils; taken from osgEarth - Geospatial SDK for OpenSceneGraph +* Copyright 2018 Pelican Mapping +* http://osgearth.org +* +* osgEarth 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 of the License, or +* (at your option) any later version. +* +* This program 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 program. If not, see +*/ + +#include "SGImageUtils.hxx" +#include +#include +#include +#include +#include +#include +#include + +#define LC "[ImageUtils] " + + +#if defined(OSG_GLES1_AVAILABLE) || defined(OSG_GLES2_AVAILABLE) || defined(OSG_GLES3_AVAILABLE) +# define GL_RGB8_INTERNAL GL_RGB8_OES +# define GL_RGB8A_INTERNAL GL_RGBA8_OES +#else +# define GL_RGB8_INTERNAL GL_RGB8 +# define GL_RGB8A_INTERNAL GL_RGBA8 +#endif + + +namespace simgear +{ + +osg::Image* +ImageUtils::cloneImage(const osg::Image* input) +{ + // Why not just call image->clone()? Because, the osg::Image copy constructor does not + // clear out the underlying BufferData/BufferObject's GL handles. This can cause + // exepected results if you are cloning an image that has already been used in GL. + // Calling clone->dirty() might work, but we are not sure. + + if (!input) return 0L; + + osg::Image* clone = osg::clone(input, osg::CopyOp::DEEP_COPY_ALL); + clone->dirty(); + if (isNormalized(input) != isNormalized(clone)) { + ////OE_WARN << LC << "Fail in clone.\n"; + } + return clone; +} + +void +ImageUtils::fixInternalFormat(osg::Image* image) +{ + // OpenGL is lax about internal texture formats, and e.g. allows GL_RGBA to be used + // instead of the proper GL_RGBA8, etc. Correct that here, since some of our compositors + // rely on having a proper internal texture format. + if (image->getDataType() == GL_UNSIGNED_BYTE) + { + if (image->getPixelFormat() == GL_RGB) + image->setInternalTextureFormat(GL_RGB8_INTERNAL); + else if (image->getPixelFormat() == GL_RGBA) + image->setInternalTextureFormat(GL_RGB8A_INTERNAL); + } +} + +void +ImageUtils::markAsUnNormalized(osg::Image* image, bool value) +{ + if (image) + { + image->setUserValue("osgEarth.unnormalized", value); + } +} + +bool +ImageUtils::isUnNormalized(const osg::Image* image) +{ + if (!image) return false; + bool result; + return image->getUserValue("osgEarth.unnormalized", result) && (result == true); +} + +bool +ImageUtils::copyAsSubImage(const osg::Image* src, osg::Image* dst, int dst_start_col, int dst_start_row) +{ + if (!src || !dst || + dst_start_col + src->s() > dst->s() || + dst_start_row + src->t() > dst->t() || + src->r() != dst->r()) + { + return false; + } + + // check for fast bytewise copy: + if (src->getPacking() == dst->getPacking() && + src->getDataType() == dst->getDataType() && + src->getPixelFormat() == dst->getPixelFormat()) + { + for (int r = 0; rr(); ++r) // each layer + { + for (int src_row = 0, dst_row = dst_start_row; src_row < src->t(); src_row++, dst_row++) + { + const void* src_data = src->data(0, src_row, r); + void* dst_data = dst->data(dst_start_col, dst_row, r); + memcpy(dst_data, src_data, src->getRowSizeInBytes()); + } + } + } + + // otherwise loop through an convert pixel-by-pixel. + else + { + if (!PixelReader::supports(src) || !PixelWriter::supports(dst)) + return false; + + PixelReader read(src); + PixelWriter write(dst); + + for (int r = 0; rr(); ++r) + { + for (int src_t = 0, dst_t = dst_start_row; src_t < src->t(); src_t++, dst_t++) + { + for (int src_s = 0, dst_s = dst_start_col; src_s < src->s(); src_s++, dst_s++) + { + write(read(src_s, src_t, r), dst_s, dst_t, r); + } + } + } + } + + return true; +} + +osg::Image* +ImageUtils::createBumpMap(const osg::Image* input) +{ + if (!PixelReader::supports(input) || !PixelWriter::supports(input)) + return 0L; + + osg::Image* output = osg::clone(input, osg::CopyOp::DEEP_COPY_ALL); + + static const float kernel[] = { + -1.0, -1.0, 0.0, + -1.0, 0.0, 1.0, + 0.0, 1.0, 1.0 + }; + + PixelReader read(input); + PixelWriter write(output); + + osg::Vec4f mid(0.5f, 0.5f, 0.5f, 0.5f); + + for (int t = 0; tt(); ++t) + { + for (int s = 0; ss(); ++s) + { + if (t == 0 || t == input->t() - 1 || s == 0 || s == input->s() - 1) + { + write(mid, s, t); + } + else + { + osg::Vec4f sum; + + // run the emboss kernel: + for (int tt = 0; tt <= 2; ++tt) + for (int ss = 0; ss <= 2; ++ss) + sum += read(s + ss - 1, t + tt - 1) * kernel[tt * 3 + ss]; + sum /= 9.0f; + + // bias for bumpmapping: + sum += osg::Vec4f(0.5f, 0.5f, 0.5f, 0.5f); + + // convert to greyscale: + sum.r() *= 0.2989f; + sum.g() *= 0.5870f; + sum.b() *= 0.1140f; + + sum.a() = read(s, t).a(); + write(sum, s, t); + } + } + } + return output; +} + +bool +ImageUtils::resizeImage(const osg::Image* input, + unsigned int out_s, unsigned int out_t, + osg::ref_ptr& output, + unsigned int mipmapLevel, + bool bilinear) +{ + if (!input && out_s == 0 && out_t == 0) + return false; + + if (!PixelReader::supports(input)) + { + //OE_WARN << LC << "resizeImage: unsupported format" << std::endl; + return false; + } + + if (output.valid() && !PixelWriter::supports(output.get())) + { + //OE_WARN << LC << "resizeImage: pre-allocated output image is in an unsupported format" << std::endl; + return false; + } + + unsigned int in_s = input->s(); + unsigned int in_t = input->t(); + + if (!output.valid()) + { + output = new osg::Image(); + + if (PixelWriter::supports(input)) + { + output->allocateImage(out_s, out_t, input->r(), input->getPixelFormat(), input->getDataType(), input->getPacking()); + output->setInternalTextureFormat(input->getInternalTextureFormat()); + markAsNormalized(output.get(), isNormalized(input)); + } + else + { + // for unsupported write formats, convert to normalized RGBA8 automatically. + output->allocateImage(out_s, out_t, input->r(), GL_RGBA, GL_UNSIGNED_BYTE); + output->setInternalTextureFormat(GL_RGB8A_INTERNAL); + } + } + else + { + // make sure they match up + output->setInternalTextureFormat(input->getInternalTextureFormat()); + } + + if (in_s == out_s && in_t == out_t && mipmapLevel == 0 && input->getInternalTextureFormat() == output->getInternalTextureFormat()) + { + memcpy(output->data(), input->data(), input->getTotalSizeInBytes()); + } + else + { + PixelReader read(input); + PixelWriter write(output.get()); + + for (unsigned int output_row = 0; output_row < out_t; output_row++) + { + // get an appropriate input row + float output_row_ratio = (float)output_row / (float)out_t; + float input_row = output_row_ratio * (float)in_t; + if (input_row >= input->t()) input_row = in_t - 1; + else if (input_row < 0) input_row = 0; + + for (unsigned int output_col = 0; output_col < out_s; output_col++) + { + float output_col_ratio = (float)output_col / (float)out_s; + float input_col = output_col_ratio * (float)in_s; + if (input_col >= (int)in_s) input_col = in_s - 1; + else if (input_col < 0) input_col = 0.0f; + + osg::Vec4 color; + + for (int layer = 0; layerr(); ++layer) + { + if (bilinear) + { + // Do a billinear interpolation for the image + int rowMin = osg::maximum((int)floor(input_row), 0); + int rowMax = osg::maximum(osg::minimum((int)ceil(input_row), (int)(input->t() - 1)), 0); + int colMin = osg::maximum((int)floor(input_col), 0); + int colMax = osg::maximum(osg::minimum((int)ceil(input_col), (int)(input->s() - 1)), 0); + + if (rowMin > rowMax) rowMin = rowMax; + if (colMin > colMax) colMin = colMax; + + osg::Vec4 urColor = read(colMax, rowMax, layer); + osg::Vec4 llColor = read(colMin, rowMin, layer); + osg::Vec4 ulColor = read(colMin, rowMax, layer); + osg::Vec4 lrColor = read(colMax, rowMin, layer); + + if ((colMax == colMin) && (rowMax == rowMin)) + { + // Exact value + color = urColor; + } + else if (colMax == colMin) + { + // Linear interpolate vertically + color = llColor * ((double)rowMax - input_row) + ulColor * (input_row - (double)rowMin); + } + else if (rowMax == rowMin) + { + // Linear interpolate horizontally + color = llColor * ((double)colMax - input_col) + lrColor * (input_col - (double)colMin); + } + else + { + // Bilinear interpolate + osg::Vec4 r1 = llColor * ((double)colMax - input_col) + lrColor * (input_col - (double)colMin); + osg::Vec4 r2 = ulColor * ((double)colMax - input_col) + urColor * (input_col - (double)colMin); + color = r1 * ((double)rowMax - input_row) + r2 * (input_row - (double)rowMin); + } + } + else + { + // nearest neighbor: + int col = (input_col - (int)input_col) <= (ceil(input_col) - input_col) ? + (int)input_col : + osg::minimum(1 + (int)input_col, (int)in_s - 1); + + int row = (input_row - (int)input_row) <= (ceil(input_row) - input_row) ? + (int)input_row : + osg::minimum(1 + (int)input_row, (int)in_t - 1); + + color = read(col, row, layer); // read pixel from mip level 0. + + // old code + //color = read( (int)input_col, (int)input_row, layer ); // read pixel from mip level 0 + } + + write(color, output_col, output_row, layer, mipmapLevel); // write to target mip level + } + } + } + } + + return true; +} + +bool +ImageUtils::flattenImage(osg::Image* input, + std::vector >& output) +{ + if (input == 0L) + return false; + + if (input->r() == 1) + { + output.push_back(input); + return true; + } + + for (int r = 0; rr(); ++r) + { + osg::Image* layer = new osg::Image(); + layer->allocateImage(input->s(), input->t(), 1, input->getPixelFormat(), input->getDataType(), input->getPacking()); + layer->setPixelAspectRatio(input->getPixelAspectRatio()); + markAsNormalized(layer, isNormalized(input)); + + layer->setRowLength(input->getRowLength()); + layer->setOrigin(input->getOrigin()); + layer->setFileName(input->getFileName()); + layer->setWriteHint(input->getWriteHint()); + layer->setInternalTextureFormat(input->getInternalTextureFormat()); + ::memcpy(layer->data(), input->data(0, 0, r), layer->getTotalSizeInBytes()); + output.push_back(layer); + } + + return true; +} + +bool +ImageUtils::bicubicUpsample(const osg::Image* source, + osg::Image* target, + unsigned quadrant, + unsigned stride) +{ + const int border = 1; // don't change this. + + int width = ((source->s() - 2 * border) / 2) + 1 + 2 * border; + int height = ((source->t() - 2 * border) / 2) + 1 + 2 * border; + + int s_off = quadrant == 0 || quadrant == 2 ? 0 : source->s() - width; + int t_off = quadrant == 2 || quadrant == 3 ? 0 : source->t() - height; + + ImageUtils::PixelReader readSource(source); + ImageUtils::PixelWriter writeTarget(target); + ImageUtils::PixelReader readTarget(target); + + // copy the main box, which is all odd-numbered cells when there is a border size = 1. + for (int t = 1; ts() - 1, 0); + writeTarget(readSource(s_off, t_off + height - 1), 0, target->t() - 1); + writeTarget(readSource(s_off + width - 1, t_off + height - 1), target->s() - 1, target->t() - 1); + + // copy the border intermediate cells. + for (int s = 1; st() - 1); + } + for (int t = 1; t < height - 1; ++t) // left/right: + { + writeTarget(readSource(s_off, t_off + t), 0, (t - 1) * 2 + 1); + writeTarget(readSource(s_off + width - 1, t_off + t), target->s() - 1, (t - 1) * 2 + 1); + } + + // now interpolate the missing columns, including the border cells. + for (int s = 2; ss() - 2; s += 2) + { + for (int t = 0; t < target->t(); ) + { + int offset = (s - 1) % stride; // the minus1 accounts for the border + int s0 = osg::maximum(s - offset, 0); + int s1 = osg::minimum(s0 + (int)stride, target->s() - 1); + double mu = (double)offset / (double)(s1 - s0); + osg::Vec4 p1 = readTarget(s0, t); + osg::Vec4 p2 = readTarget(s1, t); + double mu2 = (1.0 - cos(mu*osg::PI))*0.5; + osg::Vec4 v = (p1*(1.0 - mu2)) + (p2*mu2); + writeTarget(v, s, t); + + if (t == 0 || t == target->t() - 2) t += 1; else t += 2; + } + } + + // next interpolate the odd numbered rows + for (int s = 0; s < target->s();) + { + for (int t = 2; tt() - 2; t += 2) + { + int offset = (t - 1) % stride; // the minus1 accounts for the border + int t0 = osg::maximum(t - offset, 0); + int t1 = osg::minimum(t0 + (int)stride, target->t() - 1); + double mu = (double)offset / double(t1 - t0); + + osg::Vec4 p1 = readTarget(s, t0); + osg::Vec4 p2 = readTarget(s, t1); + double mu2 = (1.0 - cos(mu*osg::PI))*0.5; + osg::Vec4 v = (p1*(1.0 - mu2)) + (p2*mu2); + writeTarget(v, s, t); + } + + if (s == 0 || s == target->s() - 2) s += 1; else s += 2; + } + + // then interpolate the centers + for (int s = 2; ss() - 2; s += 2) + { + for (int t = 2; tt() - 2; t += 2) + { + int s_offset = (s - 1) % stride; + int s0 = osg::maximum(s - s_offset, 0); + int s1 = osg::minimum(s0 + (int)stride, target->s() - 1); + + int t_offset = (t - 1) % stride; + int t0 = osg::maximum(t - t_offset, 0); + int t1 = osg::minimum(t0 + (int)stride, target->t() - 1); + + double mu, mu2; + + osg::Vec4 p1 = readTarget(s0, t); + osg::Vec4 p2 = readTarget(s1, t); + mu = (double)s_offset / (double)(s1 - s0); + mu2 = (1.0 - cos(mu*osg::PI))*0.5; + osg::Vec4 v1 = (p1*(1.0 - mu2)) + (p2*mu2); + + osg::Vec4 p3 = readTarget(s, t0); + osg::Vec4 p4 = readTarget(s, t1); + mu = (double)t_offset / (double)(t1 - t0); + mu2 = (1.0 - cos(mu*osg::PI))*0.5; + osg::Vec4 v2 = (p3*(1.0 - mu2)) + (p4*mu2); + + osg::Vec4 v = (v1 + v2)*0.5; + + writeTarget(v, s, t); + } + } + + return true; +} + +osg::Image* +ImageUtils::buildNearestNeighborMipmaps(const osg::Image* input) +{ + // first, build the image that will hold all the mipmap levels. + int numMipmapLevels = osg::Image::computeNumberOfMipmapLevels(input->s(), input->t()); + int pixelSizeBytes = osg::Image::computeRowWidthInBytes(input->s(), input->getPixelFormat(), input->getDataType(), input->getPacking()) / input->s(); + int totalSizeBytes = 0; + std::vector< unsigned int > mipmapDataOffsets; + + mipmapDataOffsets.reserve(numMipmapLevels - 1); + + for (int i = 0; i 0) + mipmapDataOffsets.push_back(totalSizeBytes); + + int level_s = input->s() >> i; + int level_t = input->t() >> i; + int levelSizeBytes = level_s * level_t * pixelSizeBytes; + + totalSizeBytes += levelSizeBytes; + } + + unsigned char* data = new unsigned char[totalSizeBytes]; + + osg::ref_ptr result = new osg::Image(); + result->setImage( + input->s(), input->t(), 1, + input->getInternalTextureFormat(), + input->getPixelFormat(), + input->getDataType(), + data, osg::Image::USE_NEW_DELETE); + + result->setMipmapLevels(mipmapDataOffsets); + + // now, populate the image levels. + int level_s = input->s(); + int level_t = input->t(); + + osg::ref_ptr input2 = input; + for (int level = 0; level temp; + ImageUtils::resizeImage(input2.get(), level_s, level_t, result, level, false); + ImageUtils::resizeImage(input2.get(), level_s, level_t, temp, 0, false); + level_s >>= 1; + level_t >>= 1; + input2 = temp.get(); + } + + return result.release(); +} + +osg::Image* +ImageUtils::createMipmapBlendedImage(const osg::Image* primary, const osg::Image* secondary) +{ + // ASSUMPTION: primary and secondary are the same size, same format. + + // first, build the image that will hold all the mipmap levels. + int numMipmapLevels = osg::Image::computeNumberOfMipmapLevels(primary->s(), primary->t()); + int pixelSizeBytes = osg::Image::computeRowWidthInBytes(primary->s(), primary->getPixelFormat(), primary->getDataType(), primary->getPacking()) / primary->s(); + int totalSizeBytes = 0; + std::vector< unsigned int > mipmapDataOffsets; + + mipmapDataOffsets.reserve(numMipmapLevels - 1); + + for (int i = 0; i 0) + mipmapDataOffsets.push_back(totalSizeBytes); + + int level_s = primary->s() >> i; + int level_t = primary->t() >> i; + int levelSizeBytes = level_s * level_t * pixelSizeBytes; + + totalSizeBytes += levelSizeBytes; + } + + unsigned char* data = new unsigned char[totalSizeBytes]; + + osg::ref_ptr result = new osg::Image(); + result->setImage( + primary->s(), primary->t(), 1, + primary->getInternalTextureFormat(), + primary->getPixelFormat(), + primary->getDataType(), + data, osg::Image::USE_NEW_DELETE); + + result->setMipmapLevels(mipmapDataOffsets); + + // now, populate the image levels. + int level_s = primary->s(); + int level_t = primary->t(); + + for (int level = 0; level 0) + ImageUtils::resizeImage(secondary, level_s, level_t, result, level); + else + ImageUtils::resizeImage(primary, level_s, level_t, result, level); + + level_s >>= 1; + level_t >>= 1; + } + + return result.release(); +} + +osgDB::ReaderWriter* +ImageUtils::getReaderWriterForStream(std::istream& stream) { + // Modified from https://oroboro.com/image-format-magic-bytes/ + + // Get the length of the stream + stream.seekg(0, std::ios::end); + unsigned int len = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (len < 16) return 0; + + //const char* data = input.c_str(); + // Read a 16 byte header + char data[16]; + stream.read(data, 16); + // Reset reading + stream.seekg(0, std::ios::beg); + + // .jpg: FF D8 FF + // .png: 89 50 4E 47 0D 0A 1A 0A + // .gif: GIF87a + // GIF89a + // .tiff: 49 49 2A 00 + // 4D 4D 00 2A + // .bmp: BM + // .webp: RIFF ???? WEBP + // .ico 00 00 01 00 + // 00 00 02 00 ( cursor files ) + switch (data[0]) + { + case '\xFF': + return (!strncmp((const char*)data, "\xFF\xD8\xFF", 3)) ? + osgDB::Registry::instance()->getReaderWriterForExtension("jpg") : 0; + + case '\x89': + return (!strncmp((const char*)data, + "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) ? + osgDB::Registry::instance()->getReaderWriterForExtension("png") : 0; + + case 'G': + return (!strncmp((const char*)data, "GIF87a", 6) || + !strncmp((const char*)data, "GIF89a", 6)) ? + osgDB::Registry::instance()->getReaderWriterForExtension("gif") : 0; + + case 'I': + return (!strncmp((const char*)data, "\x49\x49\x2A\x00", 4)) ? + osgDB::Registry::instance()->getReaderWriterForExtension("tif") : 0; + + case 'M': + return (!strncmp((const char*)data, "\x4D\x4D\x00\x2A", 4)) ? + osgDB::Registry::instance()->getReaderWriterForExtension("tif") : 0; + + case 'B': + return ((data[1] == 'M')) ? + osgDB::Registry::instance()->getReaderWriterForExtension("bmp") : 0; + + default: + return 0; + } +} + +osg::Image* +ImageUtils::readStream(std::istream& stream, const osgDB::Options* options) { + + osgDB::ReaderWriter* rw = getReaderWriterForStream(stream); + if (!rw) { + return 0; + } + + osgDB::ReaderWriter::ReadResult rr = rw->readImage(stream, options); + if (rr.validImage()) { + return rr.takeImage(); + } + return 0; +} + +namespace +{ + struct MixImage + { + float _a; + bool _srcHasAlpha, _destHasAlpha; + + bool operator()(const osg::Vec4f& src, osg::Vec4f& dest) + { + float sa = _srcHasAlpha ? _a * src.a() : _a; + float da = _destHasAlpha ? dest.a() : 1.0f; + dest.set( + dest.r()*(1.0f - sa) + src.r()*sa, + dest.g()*(1.0f - sa) + src.g()*sa, + dest.b()*(1.0f - sa) + src.b()*sa, + osg::maximum(sa, da)); + return true; + } + }; +} + +bool +ImageUtils::mix(osg::Image* dest, const osg::Image* src, float a) +{ + if (!dest || !src || dest->s() != src->s() || dest->t() != src->t() || src->r() != dest->r() || + !PixelReader::supports(src) || + !PixelWriter::supports(dest)) + { + return false; + } + + PixelVisitor mixer; + mixer._a = osg::clampBetween(a, 0.0f, 1.0f); + mixer._srcHasAlpha = hasAlphaChannel(src); //src->getPixelSizeInBits() == 32; + mixer._destHasAlpha = hasAlphaChannel(dest); //dest->getPixelSizeInBits() == 32; + + mixer.accept(src, dest); + + return true; +} + +osg::Image* +ImageUtils::cropImage(const osg::Image* image, + double src_minx, double src_miny, double src_maxx, double src_maxy, + double &dst_minx, double &dst_miny, double &dst_maxx, double &dst_maxy) +{ + if (image == 0L) + return 0L; + + //Compute the desired cropping rectangle + int windowX = osg::clampBetween((int)floor((dst_minx - src_minx) / (src_maxx - src_minx) * (double)image->s()), 0, image->s() - 1); + int windowY = osg::clampBetween((int)floor((dst_miny - src_miny) / (src_maxy - src_miny) * (double)image->t()), 0, image->t() - 1); + int windowWidth = osg::clampBetween((int)ceil((dst_maxx - src_minx) / (src_maxx - src_minx) * (double)image->s()) - windowX, 0, image->s()); + int windowHeight = osg::clampBetween((int)ceil((dst_maxy - src_miny) / (src_maxy - src_miny) * (double)image->t()) - windowY, 0, image->t()); + + if (windowX + windowWidth > image->s()) + { + windowWidth = image->s() - windowX; + } + + if (windowY + windowHeight > image->t()) + { + windowHeight = image->t() - windowY; + } + + if ((windowWidth * windowHeight) == 0) + { + return NULL; + } + + //Compute the actual bounds of the area we are computing + double res_s = (src_maxx - src_minx) / (double)image->s(); + double res_t = (src_maxy - src_miny) / (double)image->t(); + + dst_minx = src_minx + (double)windowX * res_s; + dst_miny = src_miny + (double)windowY * res_t; + dst_maxx = dst_minx + (double)windowWidth * res_s; + dst_maxy = dst_miny + (double)windowHeight * res_t; + + //OE_NOTICE << "Copying from " << windowX << ", " << windowY << ", " << windowWidth << ", " << windowHeight << std::endl; + + //Allocate the croppped image + osg::Image* cropped = new osg::Image; + cropped->allocateImage(windowWidth, windowHeight, image->r(), image->getPixelFormat(), image->getDataType()); + cropped->setInternalTextureFormat(image->getInternalTextureFormat()); + ImageUtils::markAsNormalized(cropped, ImageUtils::isNormalized(image)); + + for (int layer = 0; layerr(); ++layer) + { + for (int src_row = windowY, dst_row = 0; dst_row < windowHeight; src_row++, dst_row++) + { +// if (src_row > image->t() - 1) OE_NOTICE << "HeightBroke" << std::endl; + const void* src_data = image->data(windowX, src_row, layer); + void* dst_data = cropped->data(0, dst_row, layer); + memcpy(dst_data, src_data, cropped->getRowSizeInBytes()); + } + } + return cropped; +} + +bool +ImageUtils::isPowerOfTwo(const osg::Image* image) +{ + return (((image->s() & (image->s() - 1)) == 0) && + ((image->t() & (image->t() - 1)) == 0)); +} + + +osg::Image* +ImageUtils::createSharpenedImage(const osg::Image* input) +{ + int filter[9] = { 0, -1, 0, -1, 5, -1, 0, -1, 0 }; + osg::Image* output = ImageUtils::cloneImage(input); + for (int r = 0; rr(); ++r) + { + for (int t = 1; tt() - 1; t++) + { + for (int s = 1; ss() - 1; s++) + { + int pixels[9] = { + *(int*)input->data(s - 1,t - 1,r), *(int*)input->data(s,t - 1,r), *(int*)input->data(s + 1,t - 1,r), + *(int*)input->data(s - 1,t ,r), *(int*)input->data(s,t ,r), *(int*)input->data(s + 1,t ,r), + *(int*)input->data(s - 1,t + 1,r), *(int*)input->data(s,t + 1,r), *(int*)input->data(s + 1,t + 1,r) }; + + int shifts[4] = { 0, 8, 16, 32 }; + + for (int c = 0; c<4; c++) // components + { + int mask = 0xff << shifts[c]; + int sum = 0; + for (int i = 0; i<9; i++) + { + sum += ((pixels[i] & mask) >> shifts[c]) * filter[i]; + } + sum = sum > 255 ? 255 : sum < 0 ? 0 : sum; + output->data(s, t, r)[c] = sum; + } + } + } + } + return output; +} + +namespace +{ + //static Threading::Mutex s_emptyImageMutex; + static osg::ref_ptr s_emptyImage; +} + +osg::Image* +ImageUtils::createEmptyImage() +{ + if (!s_emptyImage.valid()) + { + // Threading::ScopedMutexLock exclusive(s_emptyImageMutex); + if (!s_emptyImage.valid()) + { + s_emptyImage = createEmptyImage(1, 1); + } + } + return s_emptyImage.get(); +} + +osg::Image* +ImageUtils::createEmptyImage(unsigned int s, unsigned int t) +{ + osg::Image* empty = new osg::Image; + empty->allocateImage(s, t, 1, GL_RGBA, GL_UNSIGNED_BYTE); + empty->setInternalTextureFormat(GL_RGB8A_INTERNAL); + unsigned char *data = empty->data(0, 0); + memset(data, 0, 4 * s * t); + return empty; +} + +bool +ImageUtils::isEmptyImage(const osg::Image* image, float alphaThreshold) +{ + if (!hasAlphaChannel(image) || !PixelReader::supports(image)) + return false; + + PixelReader read(image); + for (unsigned r = 0; r<(unsigned)image->r(); ++r) + { + for (unsigned t = 0; t<(unsigned)image->t(); ++t) + { + for (unsigned s = 0; s<(unsigned)image->s(); ++s) + { + osg::Vec4 color = read(s, t, r); + if (color.a() > alphaThreshold) + return false; + } + } + } + return true; +} + + +osg::Image* +ImageUtils::createOnePixelImage(const osg::Vec4& color) +{ + osg::Image* image = new osg::Image; + image->allocateImage(1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE); + image->setInternalTextureFormat(GL_RGB8A_INTERNAL); + PixelWriter write(image); + write(color, 0, 0); + return image; +} + +osg::Image* +ImageUtils::upSampleNN(const osg::Image* src, int quadrant) +{ + throw "Not Supported"; + return nullptr; + //int soff = quadrant == 0 || quadrant == 2 ? 0 : src->s() / 2; + //int toff = quadrant == 2 || quadrant == 3 ? 0 : src->t() / 2; + //osg::Image* dst = new osg::Image(); + //dst->allocateImage(src->s(), src->t(), 1, src->getPixelFormat(), src->getDataType(), src->getPacking()); + + //PixelReader readSrc(src); + //PixelWriter writeDst(dst); + + //// first, copy the quadrant into the new image at every other pixel (s and t). + //for (int s = 0; ss() / 2; ++s) + //{ + // for (int t = 0; tt() / 2; ++t) + // { + // writeDst(readSrc(soff + s, toff + t), 2 * s, 2 * t); + // } + //} + + //// next fill in the rows - simply copy the pixel from the left. + //PixelReader readDst(dst); + //int seed = *(int*)dst->data(0, 0); + + //Random rng(seed + quadrant); + + //for (int t = 0; tt(); t += 2) + //{ + // for (int s = 1; ss(); s += 2) + // { + // int ss = rng.next(2) % 2 && ss() - 1 ? s + 1 : s - 1; + // writeDst(readDst(ss, t), s, t); + // } + //} + + //// fill in the columns - copy the pixel above. + //for (int t = 1; tt(); t += 2) + //{ + // for (int s = 0; ss(); s += 2) + // { + // int tt = rng.next(2) % 2 && tt() - 1 ? t + 1 : t - 1; + // writeDst(readDst(s, tt), s, t); + // } + //} + + //// fill in the LRs. + //for (int t = 1; tt(); t += 2) + //{ + // bool last_t = t + 2 >= dst->t(); + // for (int s = 1; ss(); s += 2) + // { + // bool last_s = s + 2 >= dst->s(); + + // if (!last_s && !last_t) + // { + // bool d1 = readDst(s - 1, t - 1) == readDst(s + 1, t + 1); + // bool d2 = readDst(s - 1, t + 1) == readDst(s + 1, t - 1); + + // if (d1 && !d2) + // { + // writeDst(readDst(s - 1, t - 1), s, t); + // } + // else if (!d1 && d2) + // { + // writeDst(readDst(s + 1, t - 1), s, t); + // } + // else if (d1 && d2) + // { + // writeDst(readDst(s - 1, t - 1), s, t); + // } + // else + // { + // int ss = rng.next(2) % 2 ? s + 1 : s - 1, tt = rng.next(2) % 2 ? t + 1 : t - 1; + // //int ss = (c++)%2? s+1, s-1, tt = (c++)%2? t+1 : t-1; + // writeDst(readDst(ss, tt), s, t); + // } + + // } + // else if (last_s && !last_t) + // { + // writeDst(readDst(s, t - 1), s, t); + // //if ( readDst(s, t-1) == readDst(s, t+1) ) + // //{ + // // writeDst( readDst(s, t-1), s, t ); + // //} + // //else + // //{ + // // writeDst( readDst(s-1, t-1), s, t ); + // //} + // } + // else if (!last_s && last_t) + // { + // writeDst(readDst(s - 1, t), s, t); + // //if ( readDst(s-1, t) == readDst(s+1, t) ) + // //{ + // // writeDst( readDst(s-1,t), s, t ); + // //} + // //else + // //{ + // // writeDst( readDst(s-1,t-1), s, t ); + // //} + // } + // else + // { + // writeDst(readDst(s - 1, t - 1), s, t); + // } + // } + //} + + //return dst; +} + +bool +ImageUtils::isSingleColorImage(const osg::Image* image, float threshold) +{ + if (!PixelReader::supports(image)) + return false; + + PixelReader read(image); + + osg::Vec4 referenceColor = read(0, 0, 0); + float refR = referenceColor.r(); + float refG = referenceColor.g(); + float refB = referenceColor.b(); + float refA = referenceColor.a(); + + for (unsigned r = 0; r<(unsigned)image->r(); ++r) + { + for (unsigned t = 0; t<(unsigned)image->t(); ++t) + { + for (unsigned s = 0; s<(unsigned)image->s(); ++s) + { + osg::Vec4 color = read(s, t, r); + if ((fabs(color.r() - refR) > threshold) + || (fabs(color.g() - refG) > threshold) + || (fabs(color.b() - refB) > threshold) + || (fabs(color.a() - refA) > threshold)) + { + return false; + } + } + } + } + return true; +} + +bool +ImageUtils::computeTextureCompressionMode(const osg::Image* image, + osg::Texture::InternalFormatMode& out_mode) +{ + if (!image) + return false; + +// const Capabilities& caps = Registry::capabilities(); + +#if !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) + + if (image->getPixelFormat() == GL_RGBA && image->getPixelSizeInBits() == 32) + { + //if (caps.supportsTextureCompression(osg::Texture::USE_S3TC_DXT5_COMPRESSION)) + //{ + out_mode = osg::Texture::USE_S3TC_DXT5_COMPRESSION; + return true; + //} + ////todo: add ETC2 + //else if (caps.supportsTextureCompression(osg::Texture::USE_ARB_COMPRESSION)) + //{ + // out_mode = osg::Texture::USE_ARB_COMPRESSION; + // return true; + //} + } + else if (image->getPixelFormat() == GL_RGB && image->getPixelSizeInBits() == 24) + { + //if (caps.supportsTextureCompression(osg::Texture::USE_S3TC_DXT1_COMPRESSION)) + //{ + out_mode = osg::Texture::USE_S3TC_DXT1_COMPRESSION; + return true; + //} + //else if (caps.supportsTextureCompression(osg::Texture::USE_ETC_COMPRESSION)) + //{ + // // ETC1 is RGB only + // out_mode = osg::Texture::USE_ETC_COMPRESSION; + // return true; + //} + //else if (caps.supportsTextureCompression(osg::Texture::USE_ARB_COMPRESSION)) + //{ + // out_mode = osg::Texture::USE_ARB_COMPRESSION; + // return true; + //} + } + +#else // OSG_GLES2_AVAILABLE + + if (caps.supportsTextureCompression(osg::Texture::USE_PVRTC_4BPP_COMPRESSION)) + { + out_mode = osg::Texture::USE_PVRTC_4BPP_COMPRESSION; + return true; + } + else if (caps.supportsTextureCompression(osg::Texture::USE_PVRTC_2BPP_COMPRESSION)) + { + out_mode = osg::Texture::USE_PVRTC_2BPP_COMPRESSION; + return true; + } + else if (caps.supportsTextureCompression(osg::Texture::USE_ETC_COMPRESSION)) + { + out_mode = osg::Texture::USE_ETC_COMPRESSION; + return true; + } + +#endif + + return false; +} + +//bool +//ImageUtils::replaceNoDataValues(osg::Image* target, +// const Bounds& targetBounds, +// const osg::Image* reference, +// const Bounds& referenceBounds) +//{ +// if (target == 0L || +// reference == 0L || +// !targetBounds.intersects(referenceBounds)) +// { +// return false; +// } +// +// float +// xscale = targetBounds.width() / referenceBounds.width(), +// yscale = targetBounds.height() / referenceBounds.height(); +// +// float +// xbias = targetBounds.xMin() - referenceBounds.xMin(), +// ybias = targetBounds.yMin() - referenceBounds.yMin(); +// +// PixelReader readTarget(target); +// PixelWriter writeTarget(target); +// PixelReader readReference(reference); +// +// for (int s = 0; ss(); ++s) +// { +// for (int t = 0; tt(); ++t) +// { +// osg::Vec4f pixel = readTarget(s, t); +// if (pixel.r() == NO_DATA_VALUE) +// { +// float nx = (float)s / (float)(target->s() - 1); +// float ny = (float)t / (float)(target->t() - 1); +// osg::Vec4f refValue = readReference(xscale*nx + xbias, yscale*ny + ybias); +// writeTarget(refValue, s, t); +// } +// } +// } +// +// return true; +//} + +bool +ImageUtils::canConvert(const osg::Image* image, GLenum pixelFormat, GLenum dataType) +{ + if (!image) return false; + return PixelReader::supports(image) && PixelWriter::supports(pixelFormat, dataType); +} + +osg::Image* +ImageUtils::convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType) +{ + if (!image) + return 0L; + + // Very fast conversion if possible : clone image + if (image->getPixelFormat() == pixelFormat && image->getDataType() == dataType) + { + GLenum texFormat = image->getInternalTextureFormat(); + if (dataType != GL_UNSIGNED_BYTE + || (pixelFormat == GL_RGB && texFormat == GL_RGB8_INTERNAL) + || (pixelFormat == GL_RGBA && texFormat == GL_RGB8A_INTERNAL)) + return cloneImage(image); + } + + // Fast conversion if possible : RGB8 to RGBA8 + if (dataType == GL_UNSIGNED_BYTE && pixelFormat == GL_RGBA && image->getDataType() == GL_UNSIGNED_BYTE && image->getPixelFormat() == GL_RGB) + { + // Do fast conversion + osg::Image* result = new osg::Image(); + result->allocateImage(image->s(), image->t(), image->r(), GL_RGBA, GL_UNSIGNED_BYTE); + result->setInternalTextureFormat(GL_RGBA8); + + const unsigned char* pSrcData = image->data(); + unsigned char* pDstData = result->data(); + int srcIndex = 0; + int dstIndex = 0; + + // Convert all pixels except last one by reading 32bits chunks + for (int i = 0; it()*image->s()*image->r() - 1; i++) + { + unsigned int srcValue = *((const unsigned int*)(pSrcData + srcIndex)) | 0xFF000000; + *((unsigned int*)(pDstData + dstIndex)) = srcValue; + + srcIndex += 3; + dstIndex += 4; + } + + // Convert last pixel + pDstData[dstIndex + 0] = pSrcData[srcIndex + 0]; + pDstData[dstIndex + 1] = pSrcData[srcIndex + 1]; + pDstData[dstIndex + 2] = pSrcData[srcIndex + 2]; + pDstData[dstIndex + 3] = 0xFF; + + return result; + } + + // Test if generic conversion is possible + if (!canConvert(image, pixelFormat, dataType)) + return 0L; + + // Generic conversion : use PixelVisitor + osg::Image* result = new osg::Image(); + result->allocateImage(image->s(), image->t(), image->r(), pixelFormat, dataType); + memset(result->data(), 0, result->getTotalSizeInBytes()); + markAsNormalized(result, isNormalized(image)); + + if (pixelFormat == GL_RGB && dataType == GL_UNSIGNED_BYTE) + result->setInternalTextureFormat(GL_RGB8_INTERNAL); + else if (pixelFormat == GL_RGBA && dataType == GL_UNSIGNED_BYTE) + result->setInternalTextureFormat(GL_RGB8A_INTERNAL); + else + result->setInternalTextureFormat(pixelFormat); + + PixelVisitor().accept(image, result); + + return result; +} + +osg::Image* +ImageUtils::convertToRGB8(const osg::Image *image) +{ + return convert(image, GL_RGB, GL_UNSIGNED_BYTE); +} + +osg::Image* +ImageUtils::convertToRGBA8(const osg::Image* image) +{ + return convert(image, GL_RGBA, GL_UNSIGNED_BYTE); +} + +bool +ImageUtils::areEquivalent(const osg::Image *lhs, const osg::Image *rhs) +{ + if (lhs == rhs) return true; + + if ((lhs->s() == rhs->s()) && + (lhs->t() == rhs->t()) && + (lhs->r() == rhs->r()) && + (lhs->getInternalTextureFormat() == rhs->getInternalTextureFormat()) && + (lhs->getPixelFormat() == rhs->getPixelFormat()) && + (lhs->getDataType() == rhs->getDataType()) && + (lhs->getPacking() == rhs->getPacking()) && + (lhs->getImageSizeInBytes() == rhs->getImageSizeInBytes())) + { + unsigned int size = lhs->getImageSizeInBytes(); + const unsigned char* ptr1 = lhs->data(); + const unsigned char* ptr2 = rhs->data(); + for (unsigned int i = 0; i < size; ++i) + { + if (*ptr1++ != *ptr2++) + return false; + } + + return true; + } + + return false; +} + +bool +ImageUtils::hasAlphaChannel(const osg::Image* image) +{ + return image && ( + image->getPixelFormat() == GL_RGBA || + image->getPixelFormat() == GL_BGRA || + image->getPixelFormat() == GL_LUMINANCE_ALPHA || + image->getPixelFormat() == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || + image->getPixelFormat() == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT || + image->getPixelFormat() == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT || + image->getPixelFormat() == GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG || + image->getPixelFormat() == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG); +} + + +bool +ImageUtils::hasTransparency(const osg::Image* image, float threshold) +{ + if (!image || !hasAlphaChannel(image) || !PixelReader::supports(image)) + return false; + + PixelReader read(image); + for (int r = 0; rr(); ++r) + for (int t = 0; tt(); ++t) + for (int s = 0; ss(); ++s) + if (read(s, t, r).a() < threshold) + return true; + + return false; +} + + +void +ImageUtils::activateMipMaps(osg::Texture* tex) +{ +#ifdef OSGEARTH_ENABLE_NVTT_CPU_MIPMAPS + // Verify that this texture requests mipmaps: + osg::Texture::FilterMode minFilter = tex->getFilter(tex->MIN_FILTER); + + bool needsMipmaps = + minFilter == tex->LINEAR_MIPMAP_LINEAR || + minFilter == tex->LINEAR_MIPMAP_NEAREST || + minFilter == tex->NEAREST_MIPMAP_LINEAR || + minFilter == tex->NEAREST_MIPMAP_NEAREST; + + if (needsMipmaps && tex->getNumImages() > 0) + { + // See if we have a CPU mipmap generator: + osgDB::ImageProcessor* ip = osgDB::Registry::instance()->getImageProcessor(); + if (ip) + { + for (unsigned i = 0; i < tex->getNumImages(); ++i) + { + if (tex->getImage(i)->getNumMipmapLevels() <= 1) + { + ip->generateMipMap(*tex->getImage(i), true, ip->USE_CPU); + } + } + } + } +#endif +} + + +bool +ImageUtils::featherAlphaRegions(osg::Image* image, float maxAlpha) +{ + if (!PixelReader::supports(image) || !PixelWriter::supports(image)) + return false; + + PixelReader read(image); + PixelWriter write(image); + + int ns = image->s(); + int nt = image->t(); + int nr = image->r(); + + osg::Vec4 n; + + for (int r = 0; r < nr; ++r) + { + for (int t = 0; t < nt; ++t) + { + bool rowdone = false; + for (int s = 0; s < ns && !rowdone; ++s) + { + osg::Vec4 pixel = read(s, t, r); + if (pixel.a() <= maxAlpha) + { + bool wrote = false; + if (s < ns - 1) { + n = read(s + 1, t, r); + if (n.a() > maxAlpha) { + write(n, s, t, r); + wrote = true; + } + } + if (!wrote && s > 0) { + n = read(s - 1, t, r); + if (n.a() > maxAlpha) { + write(n, s, t, r); + rowdone = true; + } + } + } + } + } + + for (int s = 0; s < ns; ++s) + { + bool coldone = false; + for (int t = 0; t < nt && !coldone; ++t) + { + osg::Vec4 pixel = read(s, t, r); + if (pixel.a() <= maxAlpha) + { + bool wrote = false; + if (t < nt - 1) { + n = read(s, t + 1, r); + if (n.a() > maxAlpha) { + write(n, s, t, r); + wrote = true; + } + } + if (!wrote && t > 0) { + n = read(s, t - 1, r); + if (n.a() > maxAlpha) { + write(n, s, t, r); + coldone = true; + } + } + } + } + } + } + + return true; +} + + +bool +ImageUtils::convertToPremultipliedAlpha(osg::Image* image) +{ + if (!PixelReader::supports(image) || !PixelWriter::supports(image)) + return false; + + PixelReader read(image); + PixelWriter write(image); + for (int r = 0; r < image->r(); ++r) { + for (int s = 0; s < image->s(); ++s) { + for (int t = 0; t < image->t(); ++t) { + osg::Vec4f c = read(s, t, r); + write(osg::Vec4f(c.r()*c.a(), c.g()*c.a(), c.b()*c.a(), c.a()), s, t, r); + } + } + } + return true; +} + + +bool +ImageUtils::isCompressed(const osg::Image *image) +{ + //Later versions of OSG have an Image::isCompressed function but earlier versions like 2.8.3 do not. This is a workaround so that + //we can tell if an image is compressed on all versions of OSG. + switch (image->getPixelFormat()) + { + case(GL_COMPRESSED_ALPHA_ARB): + case(GL_COMPRESSED_INTENSITY_ARB): + case(GL_COMPRESSED_LUMINANCE_ALPHA_ARB): + case(GL_COMPRESSED_LUMINANCE_ARB): + case(GL_COMPRESSED_RGBA_ARB): + case(GL_COMPRESSED_RGB_ARB): + case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): + case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): + case(GL_COMPRESSED_SIGNED_RED_RGTC1_EXT): + case(GL_COMPRESSED_RED_RGTC1_EXT): + case(GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): + case(GL_COMPRESSED_RED_GREEN_RGTC2_EXT): + case(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG): + case(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG): + case(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG): + case(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG): + return true; + default: + return false; + } +} + + +bool +ImageUtils::isFloatingPointInternalFormat(GLint i) +{ + return + (i >= 0x8C10 && i <= 0x8C17) || // GL_TEXTURE_RED_TYPE_ARB, et al + (i >= 0x8814 && i <= 0x881F); // GL_RGBA32F_ARB, et al +} + +bool +ImageUtils::sameFormat(const osg::Image* lhs, const osg::Image* rhs) +{ + return + lhs != 0L && + rhs != 0L && + lhs->getPixelFormat() == rhs->getPixelFormat() && + lhs->getDataType() == rhs->getDataType(); +} + +bool +ImageUtils::textureArrayCompatible(const osg::Image* lhs, const osg::Image* rhs) +{ + return + sameFormat(lhs, rhs) && + lhs->s() == rhs->s() && + lhs->t() == rhs->t() && + lhs->r() == rhs->r(); +} + +//------------------------------------------------------------------------ + +namespace +{ + //static const double r10= 1.0/1023.0; + //static const double r8 = 1.0/255.0; + //static const double r6 = 1.0/63.0; + static const double r5 = 1.0 / 31.0; + //static const double r4 = 1.0/15.0; + static const double r3 = 1.0 / 7.0; + static const double r2 = 1.0 / 3.0; + + // The scale factors to convert from an image data type to a + // float. This is copied from OSG; I think the factors for the signed + // types are wrong, but need to investigate further. + + template struct GLTypeTraits; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return norm ? 1.0 / 128.0 : 1.0; } // XXX + }; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return norm ? 1.0 / 255.0 : 1.0; } + }; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return norm ? 1.0 / 32768.0 : 1.0; } // XXX + }; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return norm ? 1.0 / 65535.0 : 1.0; } + }; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return norm ? 1.0 / 2147483648.0 : 1.0; } // XXX + }; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return norm ? 1.0 / 4294967295.0 : 1.0; } + }; + + template<> struct GLTypeTraits + { + static double scale(bool norm) { return 1.0; } + }; + + // The Reader function that performs the read. + template struct ColorReader; + template struct ColorWriter; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float l = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(l, l, l, 1.0f); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + (*ptr) = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float l = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(l, l, l, 1.0f); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + (*ptr) = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float l = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(l, l, l, 1.0f); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + (*ptr) = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float a = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(1.0f, 1.0f, 1.0f, a); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + (*ptr) = (T)(c.a() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float l = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float a = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(l, l, l, a); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + *ptr++ = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + *ptr = (T)(c.a() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float d = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float g = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float b = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(d, g, b, 1.0f); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + *ptr++ = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.g() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.b() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float d = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float g = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float b = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float a = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(d, g, b, a); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + *ptr++ = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.g() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.b() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.a() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float b = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + float g = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float d = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(d, g, b, 1.0f); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + *ptr++ = (T)(c.b() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.g() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + const T* ptr = (const T*)ia->data(s, t, r, m); + float b = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float g = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float d = float(*ptr++) * GLTypeTraits::scale(ia->_normalized); + float a = float(*ptr) * GLTypeTraits::scale(ia->_normalized); + return osg::Vec4(d, g, b, a); + } + }; + + template + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + T* ptr = (T*)iw->data(s, t, r, m); + *ptr++ = (T)(c.b() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.g() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.r() / GLTypeTraits::scale(iw->_normalized)); + *ptr++ = (T)(c.a() / GLTypeTraits::scale(iw->_normalized)); + } + }; + + template + struct ColorReader<0, T> + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + return osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f); + } + }; + + template + struct ColorWriter<0, T> + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + //nop + } + }; + + template<> + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + GLushort p = *(const GLushort*)ia->data(s, t, r, m); + //internal format GL_RGB5_A1 is implied + return osg::Vec4( + r5*(float)(p >> 11), + r5*(float)((p & 0x7c0) >> 6), + r5*(float)((p & 0x3e) >> 1), + (float)(p & 0x1)); + } + }; + + template<> + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + GLushort + red = (unsigned short)(c.r() * 255), + g = (unsigned short)(c.g() * 255), + b = (unsigned short)(c.b() * 255), + a = c.a() < 0.15 ? 0 : 1; + + GLushort* ptr = (GLushort*)iw->data(s, t, r, m); + *ptr = (((red) & (0xf8)) << 8) | (((g) & (0xf8)) << 3) | (((b) & (0xF8)) >> 2) | a; + } + }; + + template<> + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* ia, int s, int t, int r, int m) + { + GLubyte p = *(const GLubyte*)ia->data(s, t, r, m); + // internal format GL_R3_G3_B2 is implied + return osg::Vec4(r3*(float)(p >> 5), r3*(float)((p & 0x28) >> 2), r2*(float)(p & 0x3), 1.0f); + } + }; + + template<> + struct ColorWriter + { + static void write(const ImageUtils::PixelWriter* iw, const osg::Vec4f& c, int s, int t, int r, int m) + { + GLubyte* ptr = (GLubyte*)iw->data(s, t, r, m); + //OE_WARN << LC << "Target GL_UNSIGNED_BYTE_3_3_2 not yet implemented" << std::endl; + } + }; + + template<> + struct ColorReader + { + static osg::Vec4 read(const ImageUtils::PixelReader* pr, int s, int t, int r, int m) + { + static const int BLOCK_BYTES = 8; + + unsigned int blocksPerRow = pr->_image->s() / 4; + unsigned int bs = s / 4, bt = t / 4; + unsigned int blockStart = (bt*blocksPerRow + bs) * BLOCK_BYTES; + + const GLushort* p = (const GLushort*)(pr->data() + blockStart); + + GLushort c0p = *p++; + osg::Vec4f c0( + (float)(c0p >> 11) / 31.0f, + (float)((c0p & 0x07E0) >> 5) / 63.0f, + (float)((c0p & 0x001F)) / 31.0f, + 1.0f); + + GLushort c1p = *p++; + osg::Vec4f c1( + (float)(c1p >> 11) / 31.0f, + (float)((c1p & 0x07E0) >> 5) / 63.0f, + (float)((c1p & 0x001F)) / 31.0f, + 1.0f); + + static const float one_third = 1.0f / 3.0f; + static const float two_thirds = 2.0f / 3.0f; + + osg::Vec4f c2, c3; + if (c0p > c1p) + { + c2 = c0*two_thirds + c1*one_third; + c3 = c0*one_third + c1*two_thirds; + } + else + { + c2 = c0*0.5 + c1*0.5; + c3.set(0, 0, 0, 1); + } + + unsigned int table = *(unsigned int*)p; + int ls = s - 4 * bs, lt = t - 4 * bt; //int ls = s % 4, lt = t % 4; + int x = ls + (4 * lt); + + unsigned int index = (table >> (2 * x)) & 0x00000003; + + return index == 0 ? c0 : index == 1 ? c1 : index == 2 ? c2 : c3; + } + }; + + template + inline ImageUtils::PixelReader::ReaderFunc + chooseReader(GLenum dataType) + { + switch (dataType) + { + case GL_BYTE: + return &ColorReader::read; + case GL_UNSIGNED_BYTE: + return &ColorReader::read; + case GL_SHORT: + return &ColorReader::read; + case GL_UNSIGNED_SHORT: + return &ColorReader::read; + case GL_INT: + return &ColorReader::read; + case GL_UNSIGNED_INT: + return &ColorReader::read; + case GL_FLOAT: + return &ColorReader::read; + case GL_UNSIGNED_SHORT_5_5_5_1: + return &ColorReader::read; + case GL_UNSIGNED_BYTE_3_3_2: + return &ColorReader::read; + case GL_UNSIGNED_INT_8_8_8_8_REV: + return &ColorReader::read; + default: + return &ColorReader<0, GLbyte>::read; + } + } + + inline ImageUtils::PixelReader::ReaderFunc + getReader(GLenum pixelFormat, GLenum dataType) + { + switch (pixelFormat) + { + case GL_DEPTH_COMPONENT: + return chooseReader(dataType); + break; + case GL_LUMINANCE: + return chooseReader(dataType); + break; + case GL_RED: + return chooseReader(dataType); + break; + case GL_ALPHA: + return chooseReader(dataType); + break; + case GL_LUMINANCE_ALPHA: + return chooseReader(dataType); + break; + case GL_RGB: + return chooseReader(dataType); + break; + case GL_RGBA: + return chooseReader(dataType); + break; + case GL_BGR: + return chooseReader(dataType); + break; + case GL_BGRA: + return chooseReader(dataType); + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return &ColorReader::read; + break; + default: + return 0L; + break; + } + } +} + +ImageUtils::PixelReader::PixelReader(const osg::Image* image) : + _bilinear(false) +{ + setImage(image); +} + +void +ImageUtils::PixelReader::setImage(const osg::Image* image) +{ + _image = image; + if (image) + { + _normalized = ImageUtils::isNormalized(image); + _colMult = _image->getPixelSizeInBits() / 8; + _rowMult = _image->getRowSizeInBytes(); + _imageSize = _image->getImageSizeInBytes(); + GLenum dataType = _image->getDataType(); + _reader = getReader(_image->getPixelFormat(), dataType); + if (!_reader) + { + //OE_WARN << "[PixelReader] No reader found for pixel format " << std::hex << _image->getPixelFormat() << std::endl; + _reader = &ColorReader<0, GLbyte>::read; + } + } +} + +osg::Vec4 +ImageUtils::PixelReader::operator()(float u, float v, int r, int m) const +{ + return operator()((double)u, (double)v, r, m); +} + +osg::Vec4 +ImageUtils::PixelReader::operator()(double u, double v, int r, int m) const +{ + if (_bilinear) + { + double sizeS = (double)(_image->s() - 1); + double sizeT = (double)(_image->t() - 1); + + // u, v => [0..1] + double s = u * sizeS; + double t = v * sizeT; + + double s0 = osg::maximum(floorf(s), 0.0f); + double s1 = osg::minimum(s0 + 1.0f, sizeS); + double smix = s0 < s1 ? (s - s0) / (s1 - s0) : 0.0f; + + double t0 = osg::maximum(floorf(t), 0.0f); + double t1 = osg::minimum(t0 + 1.0f, sizeT); + double tmix = t0 < t1 ? (t - t0) / (t1 - t0) : 0.0f; + + osg::Vec4 UL = (*_reader)(this, (int)s0, (int)t0, r, m); // upper left + osg::Vec4 UR = (*_reader)(this, (int)s1, (int)t0, r, m); // upper right + osg::Vec4 LL = (*_reader)(this, (int)s0, (int)t1, r, m); // lower left + osg::Vec4 LR = (*_reader)(this, (int)s1, (int)t1, r, m); // lower right + + osg::Vec4 TOP = UL*(1.0f - smix) + UR*smix; + osg::Vec4 BOT = LL*(1.0f - smix) + LR*smix; + + return TOP*(1.0f - tmix) + BOT*tmix; + } + else + { + return (*_reader)(this, + (int)(u * (double)(_image->s() - 1)), + (int)(v * (double)(_image->t() - 1)), + r, m); + } +} + +bool +ImageUtils::PixelReader::supports(GLenum pixelFormat, GLenum dataType) +{ + return getReader(pixelFormat, dataType) != 0L; +} + +//------------------------------------------------------------------------ + +namespace +{ + template + inline ImageUtils::PixelWriter::WriterFunc chooseWriter(GLenum dataType) + { + switch (dataType) + { + case GL_BYTE: + return &ColorWriter::write; + case GL_UNSIGNED_BYTE: + return &ColorWriter::write; + case GL_SHORT: + return &ColorWriter::write; + case GL_UNSIGNED_SHORT: + return &ColorWriter::write; + case GL_INT: + return &ColorWriter::write; + case GL_UNSIGNED_INT: + return &ColorWriter::write; + case GL_FLOAT: + return &ColorWriter::write; + case GL_UNSIGNED_SHORT_5_5_5_1: + return &ColorWriter::write; + case GL_UNSIGNED_BYTE_3_3_2: + return &ColorWriter::write; + default: + return 0L; + } + } + + inline ImageUtils::PixelWriter::WriterFunc getWriter(GLenum pixelFormat, GLenum dataType) + { + switch (pixelFormat) + { + case GL_DEPTH_COMPONENT: + return chooseWriter(dataType); + break; + case GL_LUMINANCE: + return chooseWriter(dataType); + break; + case GL_RED: + return chooseWriter(dataType); + break; + case GL_ALPHA: + return chooseWriter(dataType); + break; + case GL_LUMINANCE_ALPHA: + return chooseWriter(dataType); + break; + case GL_RGB: + return chooseWriter(dataType); + break; + case GL_RGBA: + return chooseWriter(dataType); + break; + case GL_BGR: + return chooseWriter(dataType); + break; + case GL_BGRA: + return chooseWriter(dataType); + break; + default: + return 0L; + break; + } + } +} + +ImageUtils::PixelWriter::PixelWriter(osg::Image* image) : + _image(image) +{ + if (image) + { + _normalized = ImageUtils::isNormalized(image); + _colMult = _image->getPixelSizeInBits() / 8; + _rowMult = _image->getRowSizeInBytes(); + _imageSize = _image->getImageSizeInBytes(); + GLenum dataType = _image->getDataType(); + _writer = getWriter(_image->getPixelFormat(), dataType); + if (!_writer) + { + //OE_WARN << "[PixelWriter] No writer found for pixel format " << std::hex << _image->getPixelFormat() << std::endl; + _writer = &ColorWriter<0, GLbyte>::write; + } + } +} + +bool +ImageUtils::PixelWriter::supports(GLenum pixelFormat, GLenum dataType) +{ + return getWriter(pixelFormat, dataType) != 0L; +} + +TextureAndImageVisitor::TextureAndImageVisitor() : + osg::NodeVisitor() +{ + setNodeMaskOverride(~0L); + setTraversalMode(TRAVERSE_ALL_CHILDREN); +} + +void +TextureAndImageVisitor::apply(osg::Texture& texture) +{ + for (unsigned k = 0; k < texture.getNumImages(); ++k) + { + osg::Image* image = texture.getImage(k); + if (image) + { + apply(*image); + } + } +} + +void +TextureAndImageVisitor::apply(osg::Node& node) +{ + if (node.getStateSet()) + apply(*node.getStateSet()); + + traverse(node); +} + +void +TextureAndImageVisitor::apply(osg::StateSet& stateSet) +{ + osg::StateSet::TextureAttributeList& a = stateSet.getTextureAttributeList(); + for (osg::StateSet::TextureAttributeList::iterator i = a.begin(); i != a.end(); ++i) + { + osg::StateSet::AttributeList& b = *i; + for (osg::StateSet::AttributeList::iterator j = b.begin(); j != b.end(); ++j) + { + osg::StateAttribute* sa = j->second.first.get(); + if (sa) + { + osg::Texture* tex = dynamic_cast(sa); + if (tex) + { + apply(*tex); + } + } + } + } +} +} diff --git a/simgear/scene/util/SGImageUtils.hxx b/simgear/scene/util/SGImageUtils.hxx new file mode 100644 index 00000000..24ef239a --- /dev/null +++ b/simgear/scene/util/SGImageUtils.hxx @@ -0,0 +1,542 @@ + /* -*-c++-*- */ + /* ImageUtils: copied from osgEarth - Geospatial SDK for OpenSceneGraph + * Copyright 2018 Pelican Mapping + * http://osgearth.org + * + * osgEarth 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 of the License, or + * (at your option) any later version. + * + * This program 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 program. If not, see + */ + +#ifndef SIMGEAR_IMAGEUTILS_H +#define SIMGEAR_IMAGEUTILS_H + +#include +#include +#include +#include +#include +#include + + //These formats were not added to OSG until after 2.8.3 so we need to define them to use them. +#ifndef GL_EXT_texture_compression_rgtc +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif + +#ifndef GL_IMG_texture_compression_pvrtc +#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#endif + +namespace simgear +{ + class ImageUtils + { + public: + /** + * Clones an image. + * + * Use this instead of the osg::Image copy construtor, which keeps the referenced to + * its underlying BufferObject around. Calling dirty() on the new clone appears to + * help, but just call this method instead to be sure. + */ + static osg::Image* cloneImage(const osg::Image* image); + + /** + * Tweaks an image for consistency. OpenGL allows enums like "GL_RGBA" et.al. to be + * used in the internal texture format, when really "GL_RGBA8" is the proper things + * to use. This method accounts for that. Some parts of osgEarth (like the texture- + * array compositor) rely on the internal texture format being correct. + * (http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf) + */ + static void fixInternalFormat(osg::Image* image); + + /** + * Marks an image as containing un-normalized data values. + * + * Normally the values in an image are "normalized", i.e. scaled so they are in the + * range [0..1]. This is normal for color values. But when the image is being used + * for coverage data (a value lookup table) it is desireable to store the raw + * values instead. + */ + static void markAsUnNormalized(osg::Image* image, bool value); + + /** Inverse of above. */ + static void markAsNormalized(osg::Image* image, bool value) { markAsUnNormalized(image, !value); } + + /** + * Whether the image has been marked as containing un-normalized values. + */ + static bool isUnNormalized(const osg::Image* image); + + /** + * Whether the image has been marked as containing normalized values. + */ + static bool isNormalized(const osg::Image* image) { return !isUnNormalized(image); } + + /** + * Copys a portion of one image into another. + */ + static bool copyAsSubImage( + const osg::Image* src, + osg::Image* dst, + int dst_start_col, int dst_start_row); + + /** + * Resizes an image. Returns a new image, leaving the input image unaltered. + * + * Note. If the output parameter is NULL, this method will allocate a new image and + * resize into that new image. If the output parameter is non-NULL, this method will + * assume that the output image is already allocated to the proper size, and will + * do a resize+copy into that image. In the latter case, it is your responsibility + * to make sure the output image is allocated to the proper size. + * + * If the output parameter is non-NULL, then the mipmapLevel is also considered. + * This lets you resize directly into a particular mipmap level of the output image. + */ + static bool resizeImage( + const osg::Image* input, + unsigned int new_s, unsigned int new_t, + osg::ref_ptr& output, + unsigned int mipmapLevel = 0, bool bilinear = true); + + /** + * Crops the input image to the dimensions provided and returns a + * new image. Returns a new image, leaving the input image unaltered. + * Note: The input destination bounds are modified to reflect the bounds of the + * actual output image. Due to the fact that you cannot crop in the middle of a pixel + * The specified destination extents and the output extents may vary slightly. + *@param src_minx + * The minimum x coordinate of the input image. + *@param src_miny + * The minimum y coordinate of the input image. + *@param src_maxx + * The maximum x coordinate of the input image. + *@param src_maxy + * The maximum y coordinate of the input image. + *@param dst_minx + * The desired minimum x coordinate of the cropped image. + *@param dst_miny + * The desired minimum y coordinate of the cropped image. + *@param dst_maxx + * The desired maximum x coordinate of the cropped image. + *@param dst_maxy + * The desired maximum y coordinate of the cropped image. + */ + static osg::Image* cropImage( + const osg::Image* image, + double src_minx, double src_miny, double src_maxx, double src_maxy, + double &dst_minx, double &dst_miny, double &dst_maxx, double &dst_maxy); + + /** + * Creates an Image that "blends" two images into a new image in which "primary" + * occupies mipmap level 0, and "secondary" occupies all the other mipmap levels. + * + * WARNING: this method assumes that primary and seconday are the same exact size + * and the same exact format. + */ + static osg::Image* createMipmapBlendedImage( + const osg::Image* primary, + const osg::Image* secondary); + + /** + * Creates a new image containing mipmaps built with nearest-neighbor + * sampling. + */ + static osg::Image* buildNearestNeighborMipmaps( + const osg::Image* image); + + /** + * Blends the "src" image into the "dest" image, based on the "a" value. + * The two images must be the same. + */ + static bool mix(osg::Image* dest, const osg::Image* src, float a); + + /** + * Creates and returns a copy of the input image after applying a + * sharpening filter. Returns a new image, leaving the input image unaltered. + */ + static osg::Image* createSharpenedImage(const osg::Image* image); + + /** + * For each "layer" in the input image (each bitmap in the "r" dimension), + * create a new, separate image with r=1. If the input image is r=1, it is + * simply placed onto the output vector (no copy). + * Returns true upon sucess, false upon failure + */ + static bool flattenImage(osg::Image* image, std::vector >& output); + + /** + * Gets whether the input image's dimensions are powers of 2. + */ + static bool isPowerOfTwo(const osg::Image* image); + + /** + * Gets a transparent, single pixel image used for a placeholder + */ + static osg::Image* createEmptyImage(); + + /** + * Gets a transparent image used for a placeholder with the specified dimensions + */ + static osg::Image* createEmptyImage(unsigned int s, unsigned int t); + + /** + * Creates a one-pixel image. + */ + static osg::Image* createOnePixelImage(const osg::Vec4& color); + + /** + * Tests an image to see whether it's "empty", i.e. completely transparent, + * within an alpha threshold. + */ + static bool isEmptyImage(const osg::Image* image, float alphaThreshold = 0.01); + + /** + * Tests an image to see whether it's "single color", i.e. completely filled with a single color, + * within an threshold (threshold is tested on each channel). + */ + static bool isSingleColorImage(const osg::Image* image, float threshold = 0.01); + + /** + * Returns true if it is possible to convert the image to the specified + * format/datatype specification. + */ + static bool canConvert(const osg::Image* image, GLenum pixelFormat, GLenum dataType); + + /** + * Converts an image to the specified format. + */ + static osg::Image* convert(const osg::Image* image, GLenum pixelFormat, GLenum dataType); + + /** + *Converts the given image to RGB8 + */ + static osg::Image* convertToRGB8(const osg::Image* image); + + /** + *Converts the given image to RGBA8 + */ + static osg::Image* convertToRGBA8(const osg::Image* image); + + /** + * True if the two images are of the same format (pixel format, data type, etc.) + * though not necessarily the same size, depth, etc. + */ + static bool sameFormat(const osg::Image* lhs, const osg::Image* rhs); + + /** + * True if the two images have the same format AND size, and can therefore + * be used together in a texture array. + */ + static bool textureArrayCompatible(const osg::Image* lhs, const osg::Image* rhs); + + /** + *Compares the image data of two images and determines if they are equivalent + */ + static bool areEquivalent(const osg::Image *lhs, const osg::Image *rhs); + + /** + * Whether two colors are roughly equivalent. + */ + static bool areRGBEquivalent(const osg::Vec4& lhs, const osg::Vec4& rhs, float epsilon = 0.01f) { + return + fabs(lhs.r() - rhs.r()) < epsilon && + fabs(lhs.g() - rhs.g()) < epsilon && + fabs(lhs.b() - rhs.b()) < epsilon; + } + + /** + * Checks whether the image has an alpha component + */ + static bool hasAlphaChannel(const osg::Image* image); + + /** + * Checks whether an image has transparency; i.e. whether + * there are any pixels with an alpha component whole value + * falls below the specified threshold. + */ + static bool hasTransparency(const osg::Image* image, float alphaThreshold = 1.0f); + + /** + * Finds pixels with alpha less than [maxAlpha] and sets their color + * to match that or neighboring non-alpha pixels. This facilitates multipass + * blending or abutting tiles by overlapping them slightly. Specify "maxAlpha" + * as the maximum value to consider when searching for fully-transparent pixels. + * + * Returns false if there is no reader or writer for the image's format. + */ + static bool featherAlphaRegions(osg::Image* image, float maxAlpha = 0.0f); + + /** + * Converts an image (in place) to premultiplied-alpha format. + * Returns False is the conversion fails, e.g., if there is no reader + * or writer for the image format. + */ + static bool convertToPremultipliedAlpha(osg::Image* image); + + /** + * Checks whether the given image is compressed + */ + static bool isCompressed(const osg::Image* image); + + /** + * Generated a bump map image for the input image + */ + static osg::Image* createBumpMap(const osg::Image* input); + + /** + * Is it a floating-point texture format? + */ + static bool isFloatingPointInternalFormat(GLint internalFormat); + + /** + * Compute a texture compression format suitable for the image. + */ + static bool computeTextureCompressionMode( + const osg::Image* image, + osg::Texture::InternalFormatMode& out_mode); + + + /** + * Bicubic upsampling in a quadrant. Target image is already allocated. + */ + static bool bicubicUpsample( + const osg::Image* source, + osg::Image* target, + unsigned quadrant, + unsigned stride); + + /** + * + */ + static osg::Image* upSampleNN(const osg::Image* src, int quadrant); + + /** + * Activates mipmapping for a texture image if the correct filters exist. + * + * If OSG has an ImageProcessor service installed, this method will use that + * to generate mipmaps. If not, the method will be a NOP and the GPU wil + * generate mipmaps (if necessary) upon GPU transfer. + */ + static void activateMipMaps(osg::Texture* texture); + + /** + * Gets an osgDB::ReaderWriter for the given input stream. + * Returns NULL if no ReaderWriter can be found. + */ + static osgDB::ReaderWriter* getReaderWriterForStream(std::istream& stream); + + /** + * Reads an osg::Image from the given input stream. + * Returns NULL if the image could not be read. + */ + static osg::Image* readStream(std::istream& stream, const osgDB::Options* options); + + /** + * Reads color data out of an image, regardles of its internal pixel format. + */ + class PixelReader + { + public: + /** + * Constructs a pixel reader. "Normalized" means that the values in the source + * image have been scaled to [0..1] and should be denormalized upon reading. + */ + PixelReader(const osg::Image* image); + + /** Sets an image to read. */ + void setImage(const osg::Image* image); + + /** Whether to use bilinear interpolation when reading with u,v coords (default=true) */ + void setBilinear(bool value) { _bilinear = value; } + + /** Whether PixelReader supports a given format/datatype combiniation. */ + static bool supports(GLenum pixelFormat, GLenum dataType); + + /** Whether PixelReader can read from the specified image. */ + static bool supports(const osg::Image* image) { + return image && supports(image->getPixelFormat(), image->getDataType()); + } + + /** Reads a color from the image */ + osg::Vec4 operator()(int s, int t, int r = 0, int m = 0) const { + return (*_reader)(this, s, t, r, m); + } + + /** Reads a color from the image */ + osg::Vec4 operator()(unsigned s, unsigned t, unsigned r = 0, int m = 0) const { + return (*_reader)(this, s, t, r, m); + } + + /** Reads a color from the image by unit coords [0..1] */ + osg::Vec4 operator()(float u, float v, int r = 0, int m = 0) const; + osg::Vec4 operator()(double u, double v, int r = 0, int m = 0) const; + + // internals: + const unsigned char* data(int s = 0, int t = 0, int r = 0, int m = 0) const { + return m == 0 ? + _image->data() + s*_colMult + t*_rowMult + r*_imageSize : + _image->getMipmapData(m) + s*_colMult + t*(_rowMult >> m) + r*(_imageSize >> m); + } + + typedef osg::Vec4(*ReaderFunc)(const PixelReader* ia, int s, int t, int r, int m); + ReaderFunc _reader; + const osg::Image* _image; + unsigned _colMult; + unsigned _rowMult; + unsigned _imageSize; + bool _normalized; + bool _bilinear; + }; + + /** + * Writes color data to an image, regardles of its internal pixel format. + */ + class PixelWriter + { + public: + /** + * Constructs a pixel writer. "Normalized" means the values are scaled to [0..1] + * before writing. + */ + PixelWriter(osg::Image* image); + + /** Whether PixelWriter can write to an image with the given format/datatype combo. */ + static bool supports(GLenum pixelFormat, GLenum dataType); + + /** Whether PixelWriter can write to non-const version of an image. */ + static bool supports(const osg::Image* image) { + return image && supports(image->getPixelFormat(), image->getDataType()); + } + + /** Writes a color to a pixel. */ + void operator()(const osg::Vec4& c, int s, int t, int r = 0, int m = 0) { + (*_writer)(this, c, s, t, r, m); + } + + void f(const osg::Vec4& c, float s, float t, int r = 0, int m = 0) { + this->operator()(c, + (int)(s * (float)(_image->s() - 1)), + (int)(t * (float)(_image->t() - 1)), + r, m); + } + + // internals: + osg::Image* _image; + unsigned _colMult; + unsigned _rowMult; + unsigned _imageSize; + bool _normalized; + + unsigned char* data(int s = 0, int t = 0, int r = 0, int m = 0) const { + return m == 0 ? + _image->data() + s*_colMult + t*_rowMult + r*_imageSize : + _image->getMipmapData(m) + s*_colMult + t*(_rowMult >> m) + r*(_imageSize >> m); + } + + typedef void(*WriterFunc)(const PixelWriter* iw, const osg::Vec4& c, int s, int t, int r, int m); + WriterFunc _writer; + }; + + /** + * Functor that visits every pixel in an image + */ + template + struct PixelVisitor : public T + { + /** + * Traverse an image, and call this method on the superclass: + * + * bool operator(osg::Vec4& pixel); + * + * If that method returns true, write the value back at the same location. + */ + void accept(osg::Image* image) { + PixelReader _reader(image); + PixelWriter _writer(image); + for (int r = 0; rr(); ++r) { + for (int t = 0; tt(); ++t) { + for (int s = 0; ss(); ++s) { + osg::Vec4f pixel = _reader(s, t, r); + if ((*this)(pixel)) + _writer(pixel, s, t, r); + } + } + } + } + + /** + * Traverse an image, and call this method on the superclass: + * + * bool operator(const osg::Vec4& srcPixel, osg::Vec4& destPixel); + * + * If that method returns true, write destPixel back at the same location + * in the destination image. + */ + void accept(const osg::Image* src, osg::Image* dest) { + PixelReader _readerSrc(src); + PixelReader _readerDest(dest); + PixelWriter _writerDest(dest); + for (int r = 0; rr(); ++r) { + for (int t = 0; tt(); ++t) { + for (int s = 0; ss(); ++s) { + const osg::Vec4f pixelSrc = _readerSrc(s, t, r); + osg::Vec4f pixelDest = _readerDest(s, t, r); + if ((*this)(pixelSrc, pixelDest)) + _writerDest(pixelDest, s, t, r); + } + } + } + } + }; + + /** + * Simple functor to copy pixels from one image to another. + * + * Usage: + * PixelVisitor().accept( fromImage, toImage ); + */ + struct CopyImage { + bool operator()(const osg::Vec4f& src, osg::Vec4f& dest) { + dest = src; + return true; + } + }; + }; + + /** Visitor that finds and operates on textures and images */ + class TextureAndImageVisitor : public osg::NodeVisitor + { + public: + TextureAndImageVisitor(); + virtual ~TextureAndImageVisitor() { } + + public: + /** Visits a texture and, by default, all its components images */ + virtual void apply(osg::Texture& texture); + + /** Visits an image inside a texture */ + virtual void apply(osg::Image& image) { } + + public: // osg::NodeVisitor + virtual void apply(osg::Node& node); + virtual void apply(osg::StateSet& stateSet); + }; +} + +#endif //SIMGEAR_IMAGEUTILS_H diff --git a/simgear/scene/util/SGSceneFeatures.cxx b/simgear/scene/util/SGSceneFeatures.cxx index 07b8dc48..a3549a80 100644 --- a/simgear/scene/util/SGSceneFeatures.cxx +++ b/simgear/scene/util/SGSceneFeatures.cxx @@ -43,7 +43,11 @@ SGSceneFeatures::SGSceneFeatures() : _shaderLights(true), _pointSpriteLights(true), _distanceAttenuationLights(true), - _textureFilter(1) + _textureFilter(1), + _MaxTextureSize(4096), + _TextureCacheCompressionActive(true), + _TextureCacheCompressionActiveTransparent(true), + _TextureCacheActive(false) { } diff --git a/simgear/scene/util/SGSceneFeatures.hxx b/simgear/scene/util/SGSceneFeatures.hxx index 51261286..5b9f7f7c 100644 --- a/simgear/scene/util/SGSceneFeatures.hxx +++ b/simgear/scene/util/SGSceneFeatures.hxx @@ -1,102 +1,133 @@ /* -*-c++-*- - * - * Copyright (C) 2006-2007 Mathias Froehlich - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * - */ +* +* Copyright (C) 2006-2007 Mathias Froehlich +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of the +* License, or (at your option) any later version. +* +* This program 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 +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +* MA 02110-1301, USA. +* +*/ #ifndef SG_SCENE_FEATURES_HXX #define SG_SCENE_FEATURES_HXX #include +#include +#include namespace osg { class Texture; } class SGSceneFeatures : public SGReferenced { public: - static SGSceneFeatures* instance(); + static SGSceneFeatures* instance(); - enum TextureCompression { - DoNotUseCompression, - UseARBCompression, - UseDXT1Compression, - UseDXT3Compression, - UseDXT5Compression - }; + enum TextureCompression { + DoNotUseCompression, + UseARBCompression, + UseDXT1Compression, + UseDXT3Compression, + UseDXT5Compression + }; + int getMaxTextureSize() const { return _MaxTextureSize; } + void setMaxTextureSize(const int maxTextureSize) { _MaxTextureSize = maxTextureSize; } - void setTextureCompression(TextureCompression textureCompression) - { _textureCompression = textureCompression; } - TextureCompression getTextureCompression() const - { return _textureCompression; } - void setTextureCompression(osg::Texture* texture) const; + SGPath getTextureCompressionPath() const { return _TextureCompressionPath; } + void setTextureCompressionPath(const SGPath path) { _TextureCompressionPath = path; } - void setEnablePointSpriteLights(bool enable) - { _pointSpriteLights = enable; } - bool getEnablePointSpriteLights() const - { - return _pointSpriteLights; - } - bool getEnablePointSpriteLights(unsigned contextId) const - { - if (!_pointSpriteLights) - return false; - return getHavePointSprites(contextId); - } + bool getTextureCacheActive() const { return _TextureCacheActive; } + void setTextureCacheActive(const bool val) { _TextureCacheActive = val; } - void setEnableDistanceAttenuationLights(bool enable) - { _distanceAttenuationLights = enable; } - bool getEnableDistanceAttenuationLights(unsigned contextId) const - { - if (!_distanceAttenuationLights) - return false; - return getHavePointParameters(contextId); - } + bool getTextureCacheCompressionActive() const { return _TextureCacheCompressionActive; } + void setTextureCacheCompressionActive(const bool val) { _TextureCacheCompressionActive = val; } - void setEnableShaderLights(bool enable) - { _shaderLights = enable; } - bool getEnableShaderLights(unsigned contextId) const - { - if (!_shaderLights) - return false; - return getHaveShaderPrograms(contextId); - } - - void setTextureFilter(int max) - { _textureFilter = max; } - int getTextureFilter() const - { return _textureFilter; } + bool getTextureCacheCompressionActiveTransparent() const { return _TextureCacheCompressionActiveTransparent; } + void setTextureCacheCompressionActiveTransparent(const bool val) { _TextureCacheCompressionActiveTransparent = val; } + + void setTextureCompression(TextureCompression textureCompression) { _textureCompression = textureCompression; } + TextureCompression getTextureCompression() const { return _textureCompression; } + + // modify the texture compression on the texture parameter + void setTextureCompression(osg::Texture* texture) const; + + void setEnablePointSpriteLights(bool enable) + { + _pointSpriteLights = enable; + } + bool getEnablePointSpriteLights() const + { + return _pointSpriteLights; + } + bool getEnablePointSpriteLights(unsigned contextId) const + { + if (!_pointSpriteLights) + return false; + return getHavePointSprites(contextId); + } + + void setEnableDistanceAttenuationLights(bool enable) + { + _distanceAttenuationLights = enable; + } + bool getEnableDistanceAttenuationLights(unsigned contextId) const + { + if (!_distanceAttenuationLights) + return false; + return getHavePointParameters(contextId); + } + + void setEnableShaderLights(bool enable) + { + _shaderLights = enable; + } + bool getEnableShaderLights(unsigned contextId) const + { + if (!_shaderLights) + return false; + return getHaveShaderPrograms(contextId); + } + + void setTextureFilter(int max) + { + _textureFilter = max; + } + int getTextureFilter() const + { + return _textureFilter; + } protected: - bool getHavePointSprites(unsigned contextId) const; - bool getHaveFragmentPrograms(unsigned contextId) const; - bool getHaveVertexPrograms(unsigned contextId) const; - bool getHaveShaderPrograms(unsigned contextId) const; - bool getHavePointParameters(unsigned contextId) const; + bool getHavePointSprites(unsigned contextId) const; + bool getHaveFragmentPrograms(unsigned contextId) const; + bool getHaveVertexPrograms(unsigned contextId) const; + bool getHaveShaderPrograms(unsigned contextId) const; + bool getHavePointParameters(unsigned contextId) const; private: - SGSceneFeatures(); - SGSceneFeatures(const SGSceneFeatures&); - SGSceneFeatures& operator=(const SGSceneFeatures&); + SGSceneFeatures(); + SGSceneFeatures(const SGSceneFeatures&); + SGSceneFeatures& operator=(const SGSceneFeatures&); - TextureCompression _textureCompression; - bool _shaderLights; - bool _pointSpriteLights; - bool _distanceAttenuationLights; - int _textureFilter; + TextureCompression _textureCompression; + int _MaxTextureSize; + SGPath _TextureCompressionPath; + bool _TextureCacheCompressionActive; + bool _TextureCacheCompressionActiveTransparent; + bool _TextureCacheActive; + bool _shaderLights; + bool _pointSpriteLights; + bool _distanceAttenuationLights; + int _textureFilter; }; #endif