From ecbe0c85338dfaf02b953268f6a58a85614f3d7d Mon Sep 17 00:00:00 2001 From: Robert Menzel <menzel@informatik.rwth-aachen.de> Date: Fri, 5 Apr 2013 15:40:39 +0200 Subject: [PATCH] added ppm loading to new texture loading functions --- .../ACGL/OpenGL/Data/TextureDataLoadStore.hh | 7 +- src/ACGL/OpenGL/Data/GeometryDataLoadStore.cc | 2 +- src/ACGL/OpenGL/Data/TextureDataLoadStore.cc | 204 +++++++++++++++++- 3 files changed, 209 insertions(+), 4 deletions(-) diff --git a/include/ACGL/OpenGL/Data/TextureDataLoadStore.hh b/include/ACGL/OpenGL/Data/TextureDataLoadStore.hh index b0448a49..f8ecd4a3 100644 --- a/include/ACGL/OpenGL/Data/TextureDataLoadStore.hh +++ b/include/ACGL/OpenGL/Data/TextureDataLoadStore.hh @@ -47,14 +47,19 @@ inline bool saveScreenshot( const std::string _fileEnding = "png" ) { SharedTextureData loadTextureDataFromLodepng(const std::string &_filename); #ifdef ACGL_COMPILE_WITH_QT -//! loads from the QT library +//! loads various formats from the QT library SharedTextureData loadTextureDataFromQT(const std::string &_filename); #endif +//! loads RGBE aka Radiance files SharedTextureData loadTextureDataFromRGBE(const std::string &_filename); +//! loads EXR / OpenEXR files iff the library is present AT RUNTIME (linux only) SharedTextureData loadTextureDataFromEXR(const std::string &_filename); +//! loads PNM / PPM files: +SharedTextureData loadTextureDataFromPNM(const std::string &_filename); + /////////////////////////////////////////////////////////////////////////////////////////////////// // library specific save /////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/ACGL/OpenGL/Data/GeometryDataLoadStore.cc b/src/ACGL/OpenGL/Data/GeometryDataLoadStore.cc index 87d3a119..69d037a4 100644 --- a/src/ACGL/OpenGL/Data/GeometryDataLoadStore.cc +++ b/src/ACGL/OpenGL/Data/GeometryDataLoadStore.cc @@ -31,7 +31,7 @@ SharedGeometryData loadGeometryData(const std::string& _filename) } else { - error() << "file format of " << _filename << " not supported" << std::endl; + error() << "geometry file format of " << _filename << " not supported" << std::endl; } return SharedGeometryData(); diff --git a/src/ACGL/OpenGL/Data/TextureDataLoadStore.cc b/src/ACGL/OpenGL/Data/TextureDataLoadStore.cc index cfadc4e3..301048e0 100644 --- a/src/ACGL/OpenGL/Data/TextureDataLoadStore.cc +++ b/src/ACGL/OpenGL/Data/TextureDataLoadStore.cc @@ -63,6 +63,8 @@ SharedTextureData loadTextureData(const std::string &_filename) return loadTextureDataFromRGBE( _filename ); } else if (fileEnding == "exr") { return loadTextureDataFromEXR( _filename ); + } else if (fileEnding == "ppm") { + return loadTextureDataFromPNM( _filename ); } #ifdef ACGL_COMPILE_WITH_QT else if ( fileEnding == "bmp" || fileEnding == "jpg" || fileEnding == "jpeg" @@ -73,7 +75,7 @@ SharedTextureData loadTextureData(const std::string &_filename) } #endif else { - error() << "file format of " << _filename << " not supported" << std::endl; + error() << "texture file format of " << _filename << " not supported" << std::endl; } return SharedTextureData(); @@ -98,7 +100,7 @@ bool saveTextureData(const SharedTextureData &_textureData, const std::string &_ else if (fileEnding == "ppm") { return saveTextureDataToPPM( _textureData, _filename ); } else { - error() << "file format of " << _filename << " not supported" << std::endl; + error() << "texture file format of " << _filename << " not supported" << std::endl; } return false; @@ -289,6 +291,204 @@ SharedTextureData loadTextureDataFromEXR(const std::string &_filename) #endif } +enum loadTextureDataFromPNM_InputDataFormat +{ + PLAIN_TEXT_BITMAP, // used by P1 + PLAIN_TEXT_DECIMAL, // used by P2 and P3 + BINARY_PACKED, // used by P4 + BINARY_BYTES, // used by P5 and P6 + BINARY_WORDS // used by P5 and P6 +}; + +void loadTextureDataFromPNM_skipWhitespace(std::istream& _in, uint_t _max = std::numeric_limits<std::streamsize>::max()) +{ + uint_t skipped = 0; + while(isspace(_in.peek()) && skipped < _max) + { + _in.ignore(1); + skipped++; + } +} + +std::istream& loadTextureDataFromPNM_filterPNMComments(std::istream& _in) +{ + loadTextureDataFromPNM_skipWhitespace(_in); + + // When the stream starts with a #, throw away all following characters until the next newline + // TODO: this does not entirely conform to the PNM specs, where # characters may appear anywhere, not just at the beginning of fields + while(_in.peek() == '#') + { + _in.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + loadTextureDataFromPNM_skipWhitespace(_in); + } + return _in; +} + +SharedTextureData loadTextureDataFromPNM(const std::string &_filename) +{ + std::ifstream fileStream( _filename, std::ifstream::in ); + + if (!fileStream.good()) { + error() << "could not open file " << _filename << std::endl; + return SharedTextureData(); + } + + SharedTextureData texture = SharedTextureData( new TextureData() ); + + // Read the PNM header + std::string header; + loadTextureDataFromPNM_filterPNMComments(fileStream) >> header; + + // The header version determines the format of the data + loadTextureDataFromPNM_InputDataFormat inputDataFormat = BINARY_BYTES; // set it to something to prevent 'may be uninitialized' warnings (which can't occur here) + uint_t components; + if (header == "P1") { components = 1; inputDataFormat = PLAIN_TEXT_BITMAP; } + else if(header == "P2") { components = 1; inputDataFormat = PLAIN_TEXT_DECIMAL; } + else if(header == "P3") { components = 3; inputDataFormat = PLAIN_TEXT_DECIMAL; } + else if(header == "P4") { components = 1; inputDataFormat = BINARY_PACKED; } + else if(header == "P5") { components = 1; /* the data format will be determined later */ } + else if(header == "P6") { components = 3; /* the data format will be determined later */ } + else + { + error() << "could not load " << _filename << ": invalid header" << std::endl; + fileStream.close(); + return SharedTextureData(); + } + + // Read the width and height of the image + uint_t width, height; + loadTextureDataFromPNM_filterPNMComments(fileStream) >> width; + loadTextureDataFromPNM_filterPNMComments(fileStream) >> height; + if(!fileStream.good() || width == 0 || height == 0) + { + error() << "could not load " << _filename << ": invalid image size" << std::endl; + fileStream.close(); + return SharedTextureData(); + } + + // The max value is only present in the P2, P3, P5, P6 versions of the PNM format + uint_t maxValue = 1; + if(header == "P2" || header == "P3" || header == "P5" || header == "P6") + { + loadTextureDataFromPNM_filterPNMComments(fileStream) >> maxValue; + + if(!fileStream.good() || maxValue == 0) + { + error() << "could not load " << _filename << ": invalid value range" << std::endl; + fileStream.close(); + return SharedTextureData(); + } + } + + // The binary formats P5 and P6 use either bytes or words of data for each element, depending on the maxValue + if(header == "P5" || header == "P6") + { + if (maxValue < 256) { inputDataFormat = BINARY_BYTES; } + else if(maxValue < 65536) { inputDataFormat = BINARY_WORDS; } + else + { + error() << "could not load " << _filename << ": max value out of bounds" << std::endl; + fileStream.close(); + return SharedTextureData(); + } + } + + // If the image data has maxValue of 255 or 1, we return a GL_UNSIGNED_BYTE texture, + // otherwise the values are normalized and returned as a GL_FLOAT texture + GLenum outputDataType = GL_FLOAT; + if(maxValue == 255 || maxValue == 1) + outputDataType = GL_UNSIGNED_BYTE; + + // The data section is separated by a single whitespace + loadTextureDataFromPNM_skipWhitespace(fileStream, 1); + + // Now, read in the data + // No comments (#) are expected during this section + unsigned char* data = new unsigned char[width * height * components * getGLTypeSize(outputDataType)]; + uint_t elementsRead = 0; + while (fileStream.good() && elementsRead < (width * height * components)) + { + // Read in one element + // The exact procedure is based on the input data format + + uint_t elem; + if(inputDataFormat == PLAIN_TEXT_DECIMAL) + { + fileStream >> elem; + } + else if(inputDataFormat == BINARY_BYTES) + { + uint8_t byte; + fileStream.read((char*)&byte, 1); + elem = byte; + } + else if(inputDataFormat == BINARY_WORDS) + { + uint8_t word; + fileStream.read((char*)&word, 2); + elem = word; + } + else if(inputDataFormat == PLAIN_TEXT_BITMAP) + { + loadTextureDataFromPNM_skipWhitespace(fileStream); + char c; + fileStream.read(&c, 1); + if (c == '0') elem = 0; + else if(c == '1') elem = 1; + else break; + } + else if(inputDataFormat == BINARY_PACKED) + { + uint_t offset = elementsRead % 8; + uint8_t byte = fileStream.peek(); + + if(byte & 1<<(7-offset)) elem = 1; + else elem = 0; + + if(offset == 7) + fileStream.ignore(1); + } + + if(!fileStream.good()) break; + + // Store the element in the data array + if(outputDataType == GL_UNSIGNED_BYTE) + { + if(maxValue == 1) elem *= 255; // Bitmap values are normalized to 0..255 + data[elementsRead * getGLTypeSize(outputDataType)] = (GLubyte)elem; + } + else if(outputDataType == GL_FLOAT) + { + // Float values are normalized to 0..1 + GLfloat normalized = (GLfloat)elem / (GLfloat)maxValue; + GLfloat* position = (GLfloat*)&data[elementsRead * getGLTypeSize(outputDataType)]; + *position = normalized; + } + + elementsRead++; + } + fileStream.close(); + + if(elementsRead != width * height * components) + { + error() << "could not load " << _filename << ": unexpected EOF" << std::endl; + return SharedTextureData(); + } + + texture->setData(data); // data will get deleted by the TextureData destructor! + texture->setDepth(1); // 2D so, depth is 1 + texture->setHeight(height); + texture->setWidth(width); +#ifdef ACGL_OPENGL_ES + texture->setFormat(GL_RGB); +#else + texture->setFormat(components == 3 ? GL_RGB : GL_RED); +#endif + texture->setType(outputDataType); + + return texture; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// // library specific save /////////////////////////////////////////////////////////////////////////////////////////////////// -- GitLab