Created
February 12, 2020 13:58
-
-
Save sonOfRa/f326b7815face055a569cbd28651bd68 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#include <string> | |
#include <fstream> | |
#include <sstream> | |
#include <limits> | |
#include <iterator> | |
#include <tuple> | |
#include <iostream> | |
#include "ppm.h" | |
#include "util.h" | |
ppm::ppm(std::string fileName) : fileName(fileName) { | |
std::ifstream file(fileName); | |
if (!file.is_open()) { | |
throw std::invalid_argument("File " + fileName + " does not exist"); | |
} | |
std::string line; | |
std::pair<bool, std::pair<uint64_t, std::string>> error({false, {0, ""}}); | |
state s = state::WANT_P3; | |
int64_t parse_height, parse_width; | |
std::vector<size_t> buffer; | |
while (std::getline(file, line) && !error.first) { | |
// Increment line counter for error reporting | |
error.second.first++; | |
trim(line); | |
if (line.rfind('#', 0) == 0) { | |
// Comment, ignore this line | |
continue; | |
} | |
switch (s) { | |
case state::WANT_P3: { | |
if (line.length() != 2 || (line.rfind("P3", 0) != 0)) { | |
error.first = true; | |
error.second.second = "Invalid file header"; | |
} else { | |
// Header was successfully parsed as "P3". Next step: read resolution | |
s = state::WANT_RES; | |
} | |
break; | |
} | |
case state::WANT_RES: { | |
std::stringstream ss(line); | |
// Attempt to read width and height into 32-bit signed integers | |
if (ss >> parse_width >> parse_height) { | |
if (parse_width > std::numeric_limits<uint16_t>::max() || parse_width <= 0) { | |
error.first = true; | |
error.second.second = "Invalid width, must be 1 <= width <= 2^16 - 1"; | |
} else if (parse_height > std::numeric_limits<uint16_t>::max() || parse_height <= 0) { | |
error.first = true; | |
error.second.second = "Invalid height, must be 1 <= width <= 2^16 -1"; | |
} else { | |
// Resolution successfuly parse within the bounds of uint16_t * uint16_t (around 65536*65536) | |
width = static_cast<uint16_t>(parse_width); | |
height = static_cast<uint16_t>(parse_height); | |
buffer.reserve(width * height); | |
// After resolution, we want the color depth | |
s = state::WANT_DEPTH; | |
} | |
} else { | |
// Resolution failed to parse. Probably due to non-numeric characters | |
error.first = true; | |
error.second.second = "Invalid resolution"; | |
} | |
break; | |
} | |
case state::WANT_DEPTH: { | |
if (line.length() != 3 || (line.rfind("255", 0) != 0)) { | |
error.first = true; | |
error.second.second = "Color depth must be 255"; | |
} else { | |
// Color depth is 255, we can now start reading pixel data | |
s = state::WANT_DATA; | |
} | |
break; | |
} | |
case state::WANT_DATA: { | |
// Split up the current row by spaces | |
std::istringstream iss(line); | |
std::vector<std::string> words((std::istream_iterator<std::string>(iss)), | |
std::istream_iterator<std::string>()); | |
if (words.size() % 3 != 0) { | |
error.first = true; | |
error.second.second = "Incomplete pixels"; | |
} else { | |
// We found a multiple of 3 pixel color entries, now try to read them | |
for (size_t i = 0; i < words.size() && !error.first; i += 3) { | |
int red = 0; | |
int green = 0; | |
int blue = 0; | |
try { | |
// Attempt to parse one pixel-triple as integers | |
red = std::stoi(words[i], nullptr); | |
green = std::stoi(words[i + 1], nullptr); | |
blue = std::stoi(words[i + 2], nullptr); | |
} catch (std::out_of_range &e) { | |
// Pixel value is out of range for an integer | |
error.first = true; | |
error.second.second = "Pixel data out of range"; | |
} catch (std::invalid_argument &e) { | |
// Pixel value is not an integer | |
error.first = true; | |
error.second.second = "Non-numeric pixel data"; | |
} | |
if (!error.first) { | |
if (red < 0 || green < 0 || blue < 0) { | |
error.first = true; | |
error.second.second = "Negative pixel data not allowed"; | |
} else if (red > std::numeric_limits<uint8_t>::max() | |
|| green > std::numeric_limits<uint8_t>::max() | |
|| blue > std::numeric_limits<uint8_t>::max()) { | |
error.first = true; | |
error.second.second = "Pixel data > 255 not allowed"; | |
} else { | |
// Pixel was successfully parsed. Coalesce it into a single matrix-value | |
auto real_red = static_cast<uint8_t>(red); | |
auto real_green = static_cast<uint8_t>(green); | |
auto real_blue = static_cast<uint8_t>(blue); | |
size_t pixel = real_red; | |
pixel <<= 8; | |
pixel |= real_green; | |
pixel <<= 8; | |
pixel |= real_blue; | |
buffer.push_back(pixel); | |
} | |
} | |
} | |
if (!error.first) { | |
// If there's no error here, we successfully parsed a line of pixels | |
size_t dim = width * height; | |
if (buffer.size() == dim) { | |
s = state::DONE; | |
} else if (buffer.size() > dim) { | |
error.first = true; | |
error.second.second = "Too many pixels"; | |
} | |
} | |
} | |
break; | |
} | |
case state::DONE: | |
/* | |
* Getting here means there are non-empty/non-comment lines after the last | |
* data line. Treat this file as invalid | |
*/ | |
error.first = true; | |
error.second.second = "Non-empty line after image fully handled"; | |
break; | |
} | |
} | |
if (error.first) { | |
throw "Error at line " + std::to_string(error.second.first) + ": " + error.second.second; | |
} | |
if (s != state::DONE) { | |
/* | |
* Getting here means we parsed all the lines in the file, but less than height. | |
* This means we are missing pixel data and the file is incomplete | |
*/ | |
throw "Incomplete file"; | |
} | |
/* | |
* Sanity check on buffer size. This should never be true | |
*/ | |
if (buffer.size() != (size_t) width * height) { | |
throw "Buffer size does not match width and height. This is a bug."; | |
} | |
// Parse was completed sucessfully, allocate matrix with our image dimensions | |
m = new matrix(buffer.data(), height, width); | |
} | |
ppm::~ppm() { | |
delete m; | |
} | |
const matrix *ppm::getMatrix() const { | |
return m; | |
} | |
void ppm::invert() { | |
std::vector<cl::Platform> all_platforms; | |
cl::Platform::get(&all_platforms); | |
if (all_platforms.empty()) { | |
std::cerr << "No OpenCL platforms found, falling back to CPU" << std::endl; | |
invert_cpu(); | |
} else { | |
cl::Platform default_platform = all_platforms[0]; | |
std::vector<cl::Device> all_devices; | |
default_platform.getDevices(CL_DEVICE_TYPE_ALL, &all_devices); | |
if (all_devices.empty()) { | |
std::cerr << "No OpenCL devices found, falling back to CPU" << std::endl; | |
invert_cpu(); | |
} else { | |
cl::Device default_device = all_devices[0]; | |
cl::Context context({default_device}); | |
cl::Program::Sources sources; | |
std::string code(invert_ptx); | |
sources.push_back({code.c_str(), code.length()}); | |
cl::Program program(context, sources); | |
if (program.build({default_device}) != CL_SUCCESS) { | |
throw program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(default_device); | |
} | |
cl::Buffer bufA(context, CL_MEM_READ_WRITE, m->getRows() * m->getColumns() * sizeof(size_t)); | |
cl::CommandQueue queue(context, default_device); | |
queue.enqueueWriteBuffer(bufA, CL_TRUE, 0, m->getRows() * m->getColumns() * sizeof(size_t), m->data); | |
cl::Kernel invert(program, "invert"); | |
cl_int arg_error = invert.setArg(0, m->getRows()); | |
check_arg_error(arg_error, 0); | |
arg_error = invert.setArg(1, m->getColumns()); | |
check_arg_error(arg_error, 1); | |
arg_error = invert.setArg(2, bufA); | |
check_arg_error(arg_error, 2); | |
cl_int kernel_err = queue.enqueueNDRangeKernel(invert, cl::NullRange, cl::NDRange(m->getRows()), | |
cl::NullRange); | |
check_kernel_error(kernel_err); | |
queue.enqueueReadBuffer(bufA, CL_TRUE, 0, m->getRows() * m->getColumns() * sizeof(size_t), m->data); | |
queue.finish(); | |
} | |
} | |
} | |
void ppm::invert_cpu() { | |
for (size_t i = 0; i < m->getRows(); ++i) { | |
for (size_t j = 0; j < m->getColumns(); ++j) { | |
m->data[i * m->getColumns() + j] ^= 0xFFFFFF; | |
} | |
} | |
} | |
void ppm::writeBack() { | |
std::ofstream out(fileName); | |
out << "P3" << std::endl; | |
out << width << " " << height << std::endl; | |
out << "255" << std::endl; | |
for (size_t i = 0; i < m->getRows(); ++i) { | |
for (size_t j = 0; j < m->getColumns(); ++j) { | |
size_t index = i * m->getColumns() + j; | |
size_t data = m->data[index]; | |
auto b = static_cast<uint8_t>(data); | |
data >>= 8; | |
auto g = static_cast<uint8_t>(data); | |
data >>= 8; | |
auto r = static_cast<uint8_t>(data); | |
out << +r << " " << +g << " " << +b << " "; | |
} | |
out << std::endl; | |
} | |
} | |
bool ppm::operator==(const ppm &rhs) const { | |
return *m == *rhs.m; | |
} | |
bool ppm::operator!=(const ppm &rhs) const { | |
return *m != *rhs.m; | |
} |
This file contains hidden or 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
#pragma once | |
#include <string> | |
#include "matrix.h" | |
extern const char invert_ptx[]; | |
/** | |
* PPM Image class based on matrix. | |
* | |
* Every entry in the matrix is one full pixel with RGB data. | |
* | |
* Maximum dimensions 65535*65535 pixels | |
*/ | |
class ppm { | |
matrix *m; | |
uint16_t height; | |
uint16_t width; | |
public: | |
/** | |
* @return the underlying matrix | |
*/ | |
const matrix *getMatrix() const; | |
/** | |
* Invert the image on the CPU. Fallback used if no OpenCL platform or | |
* device is available | |
*/ | |
void invert_cpu(); | |
std::string fileName; | |
/** | |
* Load and parse the given file | |
* @param fileName | |
*/ | |
explicit ppm(std::string fileName); | |
~ppm(); | |
/** | |
* Invert the image, that is xor every color of every pixel with 255 | |
*/ | |
void invert(); | |
/** | |
* Writes the file back to filename, overwriting the file. | |
*/ | |
void writeBack(); | |
bool operator==(const ppm &rhs) const; | |
bool operator!=(const ppm &rhs) const; | |
}; | |
enum class state { | |
WANT_P3, | |
WANT_RES, | |
WANT_DEPTH, | |
WANT_DATA, | |
DONE | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment