/*
Copyright (C) 2012 fmaj7b5.info
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "stdafx.h"
#include "ImageIO.h"
using namespace FM7b5;
struct PbmHeader
{
char magic[3];
int width;
int height;
int depth;
PbmHeader()
: width(0), height(0), depth(0)
{
magic[0] = magic[1] = magic[2] = '\0';
}
};
/* declarations for static functions */
static bool read_header(FILE* fp, PbmHeader* header);
static bool read_magic(FILE* fp, char magic[3]);
static bool read_digit(FILE* fp, int* val);
static bool read_whitespace(FILE* fp);
static bool skip_comment(FILE* fp);
static bool read_end_of_line(FILE* fp);
Image
FM7b5::loadPGM(const std::string& filename)
{
FILE* fp(nullptr);
if (fopen_s(&fp, filename.c_str(), "rb") != 0) {
throw std::runtime_error(__FUNCTION__ ": could not open a file.");
}
try {
PbmHeader header;
read_header(fp, &header);
printf("--- header ---\n");
printf("magic: %s\n", header.magic);
printf("size : %dx%d\n", header.width, header.height);
printf("depth: %d\n", header.depth);
/* check if the color format is supported */
if (header.magic[1] != '2' && header.magic[1] != '5') {
throw std::runtime_error(__FUNCTION__ ": non support format.");
}
if (header.depth != 255) {
throw std::runtime_error(__FUNCTION__ ": supported depth is 255 only.");
}
Image img(header.width, header.height);
switch (header.magic[1]) {
case '2': // Gray ASCII format
for (size_t h = 0; h < img.height(); ++h) {
for (size_t w = 0; w < img.width(); ++w) {
while (read_whitespace(fp));
int val;
if (!read_digit(fp, &val)) {
throw std::runtime_error(__FUNCTION__ ": invalid data.");
}
img(w, h) = static_cast(val);
}
}
break;
case '5': // Gray binary format
{
const size_t size(header.width * header.height);
size_t ret = fread(img.data(), sizeof(std::uint_fast8_t), size, fp);
if (ret != size) {
throw std::runtime_error(__FUNCTION__ ": insufficient data.");
}
}
break;
default:
throw std::runtime_error(__FUNCTION__ ": unexpected format was specified.");
}
printf("successfully read (^^\n");
return img;
}
catch (...)
{
fclose(fp);
throw;
}
}
bool
read_header(FILE* fp, PbmHeader* header)
{
if (!read_magic(fp, header->magic) || !read_whitespace(fp)) {
throw std::runtime_error(__FUNCTION__ ": not a netpbm file.");
}
while (read_whitespace(fp)); // skip comments and white spaces
if (!read_digit(fp, &header->width) || !read_whitespace(fp)) {
throw std::runtime_error(__FUNCTION__ ": invalid width.");
}
if (!read_digit(fp, &header->height) || !read_whitespace(fp)) {
throw std::runtime_error(__FUNCTION__ ": invalid height.");
}
if (!read_digit(fp, &header->depth)) {
throw std::runtime_error(__FUNCTION__ ": invalid depth.");
}
if (!read_whitespace(fp)) {
throw std::runtime_error(__FUNCTION__ ": just one white space is required right after the depth value.");
}
return true;
}
bool
read_magic(FILE* fp, char magic[3])
{
size_t pos(0);
magic[pos++] = fgetc(fp);
if (magic[0] != 'P') {
goto on_error;
}
magic[pos++] = fgetc(fp);
if (!isdigit(magic[1])) {
goto on_error;
}
magic[2] = '\0';
return true;
on_error:
for (size_t i = 0; i < pos; ++i) {
ungetc(magic[i], fp);
magic[i] = '\0';
}
return false;
}
bool
read_digit(FILE* fp, int* val)
{
static const size_t buf_size(16);
char buf[buf_size];
size_t pos(0);
for (pos = 0; pos < buf_size; ++pos) {
buf[pos] = fgetc(fp);
if (!isdigit(buf[pos])) {
break;
}
}
if (pos == 0) {
ungetc(buf[0], fp);
return false;
}
if (pos == buf_size) { // too long digits
for (size_t i = 0; i < buf_size; ++i) {
ungetc(buf[i], fp);
return false;
}
}
// push back the next char
ungetc(buf[pos], fp);
// read a number
buf[pos] = '\0';
*val = atoi(buf);
return true;
}
bool
read_whitespace(FILE* fp)
{
while (skip_comment(fp));
char ch = fgetc(fp);
if (!isspace(ch)) {
ungetc(ch, fp);
return false;
}
// check CR+LF
if (ch == '\r') {
ch = fgetc(fp);
if (ch != '\n') {
ungetc(ch, fp);
}
}
return true;
}
bool
skip_comment(FILE* fp)
{
char ch = fgetc(fp);
if (ch != '#') {
ungetc(ch, fp);
return false;
}
// skip to eol
while (!read_end_of_line(fp)) {
ch = fgetc(fp);
}
return true;
}
bool
read_end_of_line(FILE* fp)
{
char ch = fgetc(fp);
if (ch == '\r') {
ch = fgetc(fp);
if (ch != '\n') {
ungetc(ch, fp);
}
}
else if (ch != '\n') { // not an end_of_line
ungetc(ch, fp);
return false;
}
return true;
}