Skip to content

Instantly share code, notes, and snippets.

@selenologist
Created May 12, 2016 12:38
Show Gist options
  • Save selenologist/0072502e1206427242a74a34393396d9 to your computer and use it in GitHub Desktop.
Save selenologist/0072502e1206427242a74a34393396d9 to your computer and use it in GitHub Desktop.
/*
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