Created
May 12, 2016 12:38
-
-
Save selenologist/0072502e1206427242a74a34393396d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Minimalist terminal PGM viewer | |
May have bugs | |
GPLv3 | |
*/ | |
#include <iostream> | |
#include <fstream> | |
#include <sstream> | |
#include <exception> | |
#include <string> | |
#include <stdint.h> | |
#include <netinet/in.h> // htons() for binary-mode PGM | |
using namespace std; | |
typedef uint16_t GreyscalePixel; // The maximum grey value must be less than 65536 | |
class PGM{ | |
private: | |
unsigned x, y; | |
unsigned max_brightness; | |
GreyscalePixel * data; | |
public: | |
unsigned getX() const; | |
unsigned getY() const; | |
unsigned getMaxBrightness() const; | |
const GreyscalePixel * getData() const; | |
PGM(const char* filename); | |
~PGM(); | |
}; | |
unsigned PGM::getX() const{ | |
return x; | |
} | |
unsigned PGM::getY() const{ | |
return y; | |
} | |
unsigned PGM::getMaxBrightness() const{ | |
return max_brightness; | |
} | |
const GreyscalePixel* PGM::getData() const{ | |
return data; | |
} | |
bool readSkippingComments(istream& stream, string& str, bool canEof = false){ | |
while(true){ | |
getline(stream, str); | |
if(!stream.good()){ | |
if(canEof && stream.eof()) return false; | |
else throw runtime_error("Failed to read from file"); | |
} | |
if(str.compare(0, 1, "#")){ | |
// first character of the string wasn't a #, therefore string is empty or not a comment | |
// in addition to skipping comments we will also skip blank or completely whitespace lines | |
if(str.find_first_not_of(" \t\r") != string::npos){ | |
// there was a character other than whitespace, so we have a usable line. | |
// Return so we can use it. | |
return true; | |
} | |
} | |
} | |
} | |
PGM::PGM(const char* filename){ | |
ifstream file(filename, ios_base::in); | |
if(!file.good()) throw runtime_error((string)"Failed to read file " + filename); | |
const string plainMagic = "P2"; | |
const string binaryMagic = "P5"; | |
string line; | |
stringstream linestream(ios_base::in); | |
bool plain; | |
int input_data; | |
readSkippingComments(file,line); | |
if(!line.compare(0, plainMagic.length(), plainMagic)) | |
plain = true; | |
else if(!line.compare(0, binaryMagic.length(), binaryMagic)) | |
plain = false; | |
else | |
throw runtime_error((string)"File " + filename + " does not appear to be a PGM file"); | |
readSkippingComments(file,line); | |
linestream.clear(); | |
linestream.str(line); | |
linestream >> x; | |
if(linestream.fail()) throw runtime_error((string)"Failed to read from PGM file" + filename + ": bad X value"); | |
linestream >> y; | |
if(linestream.fail()) throw runtime_error((string)"Failed to read from PGM file" + filename + ": bad Y value"); | |
readSkippingComments(file,line); | |
linestream.clear(); | |
linestream.str(line); | |
linestream >> max_brightness; | |
if(linestream.fail()) throw runtime_error((string)"Failed to read from PGM file" + filename + ": bad max brightness value"); | |
data = new GreyscalePixel[x*y]; | |
unsigned size = x * y; | |
unsigned pos = 0; | |
if(plain){ | |
// plaintext format | |
while(file.good()){ | |
readSkippingComments(file, line, true); | |
linestream.clear(); | |
linestream.str(line); | |
while(pos < size && linestream.good()){ | |
linestream >> input_data; | |
if(linestream.fail()) throw runtime_error((string)"Failed to read from PGM file" + filename + ": bad format"); | |
if(input_data > (signed)max_brightness || input_data < 0) throw runtime_error((string)"Failed to read from PGM file" + filename + ": out of range pixel"); | |
data[pos] = static_cast<GreyscalePixel>(input_data); | |
pos++; | |
} | |
if(pos >= size) break; | |
} | |
} | |
else if(max_brightness >= 256){ | |
// 2-byte big-endian binary | |
file.read(reinterpret_cast<char*>(data), sizeof(GreyscalePixel) * size); | |
if(!file.good()) throw runtime_error((string)"Failed to read from PGM file" + filename + ": IO error or incomplete file"); | |
for(; pos < size; pos++){ | |
data[pos] = ntohs(data[pos]); // swap endianness from big-endian to the host format | |
} | |
} | |
else{ | |
// 1-byte binary | |
for(; pos < size && file.good(); pos++){ | |
data[pos] = file.get(); | |
} | |
if(!file.good() && !file.eof()) throw runtime_error((string)"Failed to read from PGM file " + filename + ": IO error"); | |
} | |
} | |
PGM::~PGM(){ | |
delete[] data; | |
} | |
char pixelToCharacter(GreyscalePixel pixel, unsigned max_brightness){ | |
float percentage = (float)pixel / (float)max_brightness; | |
if(percentage < 0.1) return ' '; | |
if(percentage < 0.2) return '.'; | |
if(percentage < 0.3) return ':'; | |
if(percentage < 0.4) return '-'; | |
if(percentage < 0.5) return '='; | |
if(percentage < 0.6) return '+'; | |
if(percentage < 0.7) return '*'; | |
if(percentage < 0.8) return '#'; | |
if(percentage < 0.9) return '%'; | |
else return '@'; | |
} | |
int main(int argc, char** argv){ | |
if(argc != 2){ | |
cout << "Usage: " << argv[0] << " <pgm filename>" << endl; | |
return 1; | |
} | |
PGM pgm(argv[1]); | |
for(unsigned y = 0; y < pgm.getY(); y++){ | |
for(unsigned x = 0; x < pgm.getX(); x++){ | |
cout << pixelToCharacter(pgm.getData()[y * pgm.getX() + x], pgm.getMaxBrightness()); | |
} | |
cout << endl; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment