Commit 6b3d318e authored by Janis Born's avatar Janis Born
Browse files

TextureDataControlFilePPM:

* implement a more versatile PNM loading routine
parent fdc7c563
......@@ -7,8 +7,19 @@
#define ACGL_OPENGL_CONTROLLER_TEXTUREDATACONTROLFILEPPM_HH
/**
* A minimal ppm loader (with some bugs). No external dependencies, so this is at least
* A minimal PNM loader (with some bugs). No external dependencies, so this is at least
* one image type that can be loaded without extra libs.
*
* This loader supports the following PNM format types, as specified at:
* http://netpbm.sourceforge.net/doc/pnm.html
*
* - P1 (plain text bitmap, .pbm)
* - P2 (plain text grayscale, .pgm)
* - P3 (plain text RGB, .ppm)
* - P4 (binary bitmap, .pbm)
* - P5 (binary grayscale, .pgm)
* - P6 (binary RGB, .ppm)
*
*/
#include <ACGL/ACGL.hh>
......
......@@ -5,13 +5,49 @@
#include <ACGL/OpenGL/Controller/TextureDataControlFilePPM.hh>
#include <ACGL/OpenGL/Controller/DataControlFileFactory.hh>
#include <ACGL/OpenGL/Tools.hh>
#include <fstream>
#include <limits>
#include <cctype>
using namespace ACGL;
using namespace ACGL::OpenGL;
using namespace ACGL::Utils;
enum 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 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& filterPPMComments(std::istream& _in)
{
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');
skipWhitespace(_in);
}
return _in;
}
bool TextureDataControlFilePPM::load(SharedTextureData& texture) const
{
std::ifstream fileStream( getFullFilePath().c_str(), std::ifstream::in );
......@@ -21,35 +57,152 @@ bool TextureDataControlFilePPM::load(SharedTextureData& texture) const
return false;
}
int width, height, maxValue;
// Read the PNM header
std::string header;
filterPPMComments(fileStream) >> header;
debug() << "Header: \"" << header << "\"" << std::endl;
// The header version determines the format of the data
InputDataFormat inputDataFormat;
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 " << getFullFilePath() << ": invalid header" << std::endl;
return false;
}
// Read the width and height of the image
uint_t width, height;
filterPPMComments(fileStream) >> width;
filterPPMComments(fileStream) >> height;
if(!fileStream.good() || width == 0 || height == 0)
{
error() << "could not load " << getFullFilePath() << ": invalid image size" << std::endl;
return false;
}
// 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")
{
filterPPMComments(fileStream) >> maxValue;
if(!fileStream.good() || maxValue == 0)
{
error() << "could not load " << getFullFilePath() << ": invalid value range" << std::endl;
return false;
}
}
// 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 " << getFullFilePath() << ": max value out of bounds" << std::endl;
return false;
}
}
debug() << "Size: " << width << " x " << height << std::endl;
debug() << "Range: 0.." << maxValue << std::endl;
std::string line;
fileStream >> line; // ignore the header
// 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;
// note: we assume no comments in this file!
// a general ppm loader has to be more clever!
// The data section is separated by a single whitespace
skipWhitespace(fileStream, 1);
fileStream >> width;
fileStream >> height;
fileStream >> maxValue; // we will ignore this and assume the value range is 0..255
// 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
unsigned char *data = new unsigned char[width*height*3]; // we assume RGB
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)
{
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();
int pos = 0;
while (fileStream.good() && pos < (width*height*3)) {
int i;
fileStream >> i;
data[pos] = (unsigned char) i;
pos++;
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 " << getFullFilePath() << ": unexpected EOF" << std::endl;
return false;
}
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);
texture->setFormat(GL_RGB);
texture->setType(GL_UNSIGNED_BYTE); // == unsigned char
texture->setFormat(components == 3 ? GL_RGB : GL_RED);
texture->setType(outputDataType);
return true;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment