diff options
Diffstat (limited to 'src/GFX/Image/PPMParser.cpp')
| -rw-r--r-- | src/GFX/Image/PPMParser.cpp | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/src/GFX/Image/PPMParser.cpp b/src/GFX/Image/PPMParser.cpp new file mode 100644 index 0000000..7fc2359 --- /dev/null +++ b/src/GFX/Image/PPMParser.cpp @@ -0,0 +1,154 @@ +#include <cctype> +#include <string> +#include "PPMParser.hpp" + +namespace MC::GFX::Image { + +RawImage PPMParser::parse() { + auto header = parse_header(); + + if (header.max_color != 255) { + throw std::logic_error("PPM max color values other than 255 are not implemented."); + } + + if (header.type != P3) { + throw std::logic_error("Raw PPM not implemented."); + } + + auto pixel_count = header.width * header.height; + + RawImage image(pixel_count, header.width, header.height, 3); + for (uint64_t pixel_index = 0; pixel_index < pixel_count; pixel_index++) { + RawImage::Pixel pixel = parse_pixel(header.max_color); + image.add(pixel); + } + + return image; +} + +PPMParser::PPMHeader PPMParser::parse_header() { + PPMHeader header{}; + + skip_whitespace(); + + auto type_part = chomp_part(); + skip_whitespace(); + + if (type_part == "P3") { + header.type = P3; + } else if (type_part == "P6") { + header.type = P6; + } else { + throw std::runtime_error("Unknown PPM type."); + } + + auto width_part = chomp_number(); + skip_whitespace(); + header.width = width_part; + + auto height_part = chomp_number(); + skip_whitespace(); + header.height = height_part; + + auto max_color_part = chomp_number(); + skip_whitespace(); + header.max_color = max_color_part; + + return header; +} + +RawImage::Pixel PPMParser::parse_pixel(uint8_t max_color) { + auto r_sample = parse_sample(); + auto g_sample = parse_sample(); + auto b_sample = parse_sample(); + + if (r_sample > max_color || g_sample > max_color || b_sample > max_color) { + throw std::runtime_error("Sample can not be greater than Maxval."); + } + + auto map_to_range = [=](uint64_t s) -> uint8_t { return (s * 255) / max_color; }; + + RawImage::Pixel pixel{}; + pixel.r = map_to_range(r_sample); + pixel.g = map_to_range(g_sample); + pixel.b = map_to_range(b_sample); + + return pixel; +} + +uint64_t PPMParser::parse_sample() { + skip_whitespace(); + auto sample = chomp_number(); + skip_whitespace(); + return sample; +} + +uint64_t PPMParser::chomp_number() { + auto raw = chomp_part(); + + uint64_t number = 0; + for (uint8_t digit_ascii : raw) { + if (digit_ascii < '0' || digit_ascii > '9') { + throw std::runtime_error("Number contains non ASCII digits."); + } + + uint8_t digit = digit_ascii - '0'; + + number *= 10; + number += digit; + } + + return number; +} + +std::string_view PPMParser::chomp_part() { + uint64_t length = 0; + + while (!is_eof()) { + auto c = m_source[m_cursor + length]; + if (std::isspace(c)) { + break; + } + + length++; + } + + auto part = m_source.substr(m_cursor, length); + m_cursor += length; + + return part; +} + +void PPMParser::skip_whitespace() { + uint8_t c; + while (!is_eof()) { + c = m_source[m_cursor]; + if (c == '#') { + skip_comment(); + continue; + } + + if (!std::isspace(c)) { + break; + } + + m_cursor++; + } +} + +void PPMParser::skip_comment() { + uint8_t c = m_source[m_cursor]; + if (c != '#') { + return; + } + + while (c != '\n' && !is_eof()) { + c = m_source[++m_cursor]; + } +} + +bool PPMParser::is_eof() { + return m_cursor >= m_source.size(); +} + +} \ No newline at end of file |
