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