/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2020 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 OSGEARTH_IMAGEUTILS_H #define OSGEARTH_IMAGEUTILS_H #include #include #include #include #include #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 osgEarth { namespace Util { class OSGEARTH_EXPORT 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 ); /** * 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); /** * Crops the input image using the input dimension * Returns a new image, leaving the input image unaltered. *@param x * The cropping x coordinate of the input image. *@param y * The cropping y coordinate of the input image. *@param width * The width in pixels of the crop. *@param height * The height in pixels of the crop. */ static osg::Image* cropImage(osg::Image* image, unsigned int x, unsigned int y, unsigned int width, unsigned int height); /** * 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. * Returns true upon sucess, false upon failure or if r < 2 */ static bool flattenImage( const 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, unsigned int r = 1); /** * 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); /** * 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); /** * Replaces "no data" values in the target image with the corresponding * value found in the "reference" image. The images much be GL_LUMINANCE * data types. */ static bool replaceNoDataValues( osg::Image* target, const Bounds& targetBounds, const osg::Image* reference, const Bounds& referenceBounds); /** * Bicubic upsampling in a quadrant. Target image is already allocated. */ static bool bicubicUpsample( const osg::Image* source, osg::Image* target, unsigned quadrant, unsigned stride); /** * 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 osgEarth will generate them if possible. * Returns true if mipmaps were added */ //static bool generateMipmaps(osg::Texture* texture); /** * Adds mipmaps to an image. * Returns false if the image already has mipmaps, or if we cannot * generate mipmaps for some reason. */ //static bool generateMipmaps(osg::Image* image); /** * Creates a copy of the input image with added mipmaps. * @param image Input image to generate mipmaps for * @param minLevelSize The smallest mipmap level size to generate * @return image with mipmaps. If the input already had mipmaps, * just returns the input pointer (that is why it's const) */ static const osg::Image* mipmapImage( const osg::Image* image, int minLevelSize = 16); /** * Adds mipmaps to an existing image if neccessary * @param image Input image to generate mipmaps for */ static void mipmapImageInPlace( osg::Image* image); //! Returns a compressed copy of the input image. //! @param image Image to compress //! @param method Compression method to use; see ImageLayer::getCompressionMethod static const osg::Image* compressImage( const osg::Image* image, const std::string& method ="cpu"); //! Compressed an exsting image. //! @param image Image to compress //! @param method Compression method to use; see ImageLayer::getCompressionMethod static void compressImageInPlace( osg::Image* image, const std::string& method ="auto"); //! Compresses and mipmaps all textures in the given subgraph //! @param node The node to process static void compressAndMipmapTextures(osg::Node* node); /** * 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); //! Make an empty 2D texture array static osg::Texture2DArray* makeTexture2DArray(osg::Image* image); //! If a max texture size is stipulated in the options, and it //! the image supports it, return the size. static optional getMaxTextureSize(const osg::Image*, const osgDB::Options*); //! Convert u,v [0..1] into s,t [pixels] with nearest neighbor sampling. template static void nnUVtoST(NDCTYPE u, NDCTYPE v, INDEXTYPE& s, INDEXTYPE& t, unsigned width, unsigned height) { const NDCTYPE umin = 1.0 / (2.0 * width); const NDCTYPE vmin = 1.0 / (2.0 * height); s = u(1.0 - umin) ? width - 1 : (int)floor(u*(double)width); t = v(1.0 - vmin) ? height - 1 : (int)floor(v*(double)height); } /** * Convenience object to iterate over an image with a lamdba function */ struct ImageIterator { public: ImageIterator(const osg::Image* image) : _image(image) { } //ImageIterator(PixelReader& reader) : // _image(reader._image) { } //ImageIterator(PixelWriter& writer) : // _image(writer._image) { } inline int r() const { return _r; } inline int s() const { return _s; } inline int t() const { return _t; } inline int m() const { return 0; } inline double u() const { return _u; } inline double v() const { return _v; } inline void quit() { _break = true; } template inline void forEachPixel(CALLABLE&& func) { _break = false; int tt = _image->t(), ss = _image->s(), rr = _image->r(); for (_t = 0; _t < tt && !_break; ++_t) { _v = (double)_t / (double)(_image->t() - 1); for (_s = 0; _s < ss && !_break; ++_s) { _u = (double)_s / (double)(_image->s() - 1); for (_r = 0; _r < rr && !_break; ++_r) { func(*this); } } } } private: const osg::Image* _image; int _r, _s, _t; double _u, _v; bool _break = false; }; /** * Reads color data out of an image, regardles of its internal pixel format. */ class OSGEARTH_EXPORT PixelReader { public: //! Empty pixel reader. Must call setImage before use. PixelReader(); /** * 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); //! Sets a texture whose first image to read (along with texture params) void setTexture(const osg::Texture* tex); //! Whether to denormalize data upon reading or to leave it as-is void setDenormalize(bool value) { _normalized = value; } /** Whether to use bilinear interpolation when reading with u,v coords (default=true) */ void setBilinear(bool value) { _bilinear = value; } //! Whether to sample the image like a texture (GLSL) void setSampleAsTexture(bool value) { _sampleAsTexture = value; } //! Whether to sample with texture-wrapping void setSampleAsRepeatingTexture(bool value) { _sampleAsRepeatingTexture = 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() ); } //! Whether the image is valid and usable bool valid() const { return _image != nullptr && _read != nullptr; } inline int s() const { return _image->s(); } inline int t() const { return _image->t(); } inline int r() const { return _image->r(); } //! Returns an iterator into this image ImageIterator iterator() const { return ImageIterator(_image); } //! Iterator over this image with the user function CALLABLE //! with the signature void CALLABLE(ImageIterator&) template void forEachPixel(CALLABLE&& func) const { ImageIterator(_image).forEachPixel(func); } //! Returns a color from an image at pixel (s,t,r,m) inline osg::Vec4f operator()(int s, int t, int r=0, int m=0) const { osg::Vec4f temp; _read(this, temp, s, t, r, m); return temp; } inline void operator()(osg::Vec4f& output, int s, int t, int r=0, int m=0) const { _read(this, output, s, t, r, m); } //! Returns a color from an image at pixel (s,t,r,m) inline osg::Vec4f operator()(unsigned s, unsigned t, unsigned r=0, int m=0) const { osg::Vec4f temp; _read(this, temp, s, t, r, m); return temp; } inline void operator()(osg::Vec4f& output, unsigned s, unsigned t, int r=0, int m=0) const { _read(this, output, s, t, r, m); } //! composite version of pixel read operator template inline osg::Vec4f operator()(const T& composite) const { return (*this)(composite.s(), composite.t(), composite.r()); } template inline void operator()(osg::Vec4f& output, const T& composite) const { _read(this, output, composite.s(), composite.t(), composite.r(), 0); } /** Reads a color from the image by unit coords [0..1] */ osg::Vec4f operator()(float u, float v, int r=0, int m=0) const; void operator()(osg::Vec4f& output, float u, float v, int r=0, int m=0) const; osg::Vec4f operator()(double u, double v, int r=0, int m=0) const; void operator()(osg::Vec4f& output, double u, double v, int t=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*_colBytes + t*_rowBytes + r*_imageBytes : _image->getMipmapData(m-1) + (s>>m)*_colBytes + (t>>m)*(_rowBytes>>m) + r*(_imageBytes>>m); } typedef void (*ReaderFunc)(const PixelReader* ia, osg::Vec4f& output, int s, int t, int r, int m); ReaderFunc _read; const osg::Image* _image; unsigned _colBytes; unsigned _rowBytes; unsigned _imageBytes; bool _normalized; bool _bilinear; bool _sampleAsTexture; bool _sampleAsRepeatingTexture; }; /** * Writes color data to an image, regardles of its internal pixel format. */ class OSGEARTH_EXPORT PixelWriter { public: /** * Constructs a pixel writer. "Normalized" means the values are scaled to [0..1] * before writing. */ PixelWriter(osg::Image* image); //! Whether data should be normalized to [0..1] or left as-is void setNormalize(bool value) { _normalized = value; } /** 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() ); } inline int s() const { return _image->s(); } inline int t() const { return _image->t(); } inline int r() const { return _image->r(); } //! Initialize to all one value void assign(const osg::Vec4& c); //! Initialize to all one value (one layer) void assign(const osg::Vec4& c, int r); /** Writes a color to a pixel. */ inline void operator()(const osg::Vec4& c, int s, int t, int r=0, int m=0) { (*_writer)(this, c, s, t, r, m ); } inline 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); } //! composite version of pixel write operator template inline void operator()(const osg::Vec4& c, const T& composite) const { (*_writer)(this, c, composite.s(), composite.t(), composite.r(), composite.m()); } //! Iterator over this image with the user function CALLABLE //! with the signature void CALLABLE(ImageIterator&) template void forEachPixel(CALLABLE&& func) const { ImageIterator(_image).forEachPixel(func); } // internals: osg::Image* _image; unsigned _colBytes; unsigned _rowBytes; unsigned _imageBytes; bool _normalized; unsigned char* data(int s=0, int t=0, int r=0, int m=0) const; typedef void (*WriterFunc)(const PixelWriter* iw, const osg::Vec4& c, int s, int t, int r, int m); WriterFunc _writer; }; /** * Convenience object to iterate over an image, with or without * a geospatial extent, with a lambda function */ template struct ImageIteratorWithExtent { public: ImageIteratorWithExtent(const osg::Image* image, const EXTENT& extent) : _image(image), _extent(extent) { } ImageIteratorWithExtent(PixelReader& reader, const EXTENT& extent) : _image(reader._image), _extent(extent) { } ImageIteratorWithExtent(PixelWriter& writer, const EXTENT& extent) : _image(writer._image), _extent(extent) { } inline int s() const { return _s; } inline int t() const { return _t; } inline int r() const { return _r; } inline int m() const { return 0; } inline double u() const { return _u; } inline double v() const { return _v; } inline double x() const { return _x; } inline double y() const { return _y; } template inline void forEachPixel(CALLABLE&& func) { forEachPixelOnCenter(func); } template inline void forEachPixelOnCenter(CALLABLE&& func) { int tt = _image->t(), ss = _image->s(), rr = _image->r(); double _bu = 0.5 / (double)ss; double _bv = 0.5 / (double)tt; for (_t = 0; _t < tt; ++_t) { _v = _bv + ((double)_t * 2.0 * _bv); _y = _extent.yMin() + _extent.height() * _v; for (_s = 0; _s < ss; ++_s) { _u = _bu + ((double)_s * 2.0 * _bu); _x = _extent.xMin() + _extent.width() * _u; for (_r = 0; _r < rr; ++_r) { func(); } } } } private: const osg::Image* _image; const EXTENT _extent; int _s, _t, _r; double _u, _v; double _x, _y; }; template class PixelReaderWithExtent : public PixelReader { public: PixelReaderWithExtent(const osg::Image* image, const EXTENT& extent) : PixelReader(image), _extent(extent) { } void readCoord(osg::Vec4f& value, double x, double y, int r = 0, int m = 0) const { PixelReader::operator()( value, (double)(x - _extent.xMin()) / _extent.width(), (double)(y - _extent.yMin()) / _extent.height(), r, m); } bool readCoordWithoutClamping(osg::Vec4f& value, double x, double y, int r = 0, int m = 0) const { double u = (x - _extent.xMin()) / _extent.width(); double v = (y - _extent.yMin()) / _extent.height(); if (u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0) return false; PixelReader::operator()(value, u, v, r, m); return true; } private: const EXTENT _extent; }; }; /** Visitor that finds and operates on textures and images */ class OSGEARTH_EXPORT 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 //OSGEARTH_IMAGEUTILS_H