Форматы Netpbm немного странны, поскольку многие программисты изначально неправильно их читают.
Проще говоря, "магическое число" (P1
до P7
) должнонаходиться в начале файла, за ним следуют поля заголовка, затем один символ пробела, за которым следуют данные.Хитрость заключается в том, что каждому полю заголовка может быть , которому предшествует пробел и / или комментарий, а за заголовком следует один символ пробела.
Формат P7
, файл Portable Arbitrary Map, имеет названные поля заголовка, но в любом случае это редко поддерживаемый формат, поэтому я ограничусь только общими форматами P1
до P6
.(Для поддержки полей заголовка вам нужна только другая вспомогательная функция.)
Вам нужны четыре вспомогательные функции:
Функция для преобразования десятичной цифры в ее числовое значениезначение.
static int decimal_digit(const int c)
{
switch (c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
default: return -1;
}
}
Часто вы увидите, что это сокращено до (c - '0')
, (или ((c >= '0' && c <= '9') ? (c - '0') : -1)
, если вы хотите выражение, эквивалентное функции выше), но это работает, только если операционная система используетнабор символов, где десятичные цифры являются последовательными кодовыми точками.(Они есть, за исключением машин, использующих EBCDIC или некоторые другие не ASCII-совместимые наборы символов; в настоящее время очень редко.)
Функция для чтения магического числа.
int pnm_magic(FILE *in)
{
int c;
if (!in || ferror(in))
return -2; /* Invalid file handle. */
c = getc(in);
if (c != 'P')
return -1; /* Not a NetPBM file. */
switch (getc(in)) {
case '1': return 1; /* Ascii PBM */
case '2': return 2; /* Ascii PGM */
case '3': return 3; /* Ascii PPM */
case '4': return 4; /* Binary PBM */
case '5': return 5; /* Binary PGM */
case '6': return 6; /* Binary PPM */
/* case '7': return 7; for Portable Arbitrary map file */
default: return -1; /* Unknown format */
}
}
Использование вспомогательной функции для анализа магического числа, разумеется, не является абсолютно необходимым, но его использование определенно упрощает чтение, проверку и обслуживание вашего кода.Так что использование одного - хорошая вещь.
Функция для чтения символа пробела в конце заголовка.
int pnm_endheader(FILE *in)
{
int c;
if (!in || ferror(in))
return -1; /* Invalid file handle. */
c = getc(in);
/* Whitespace? */
if (c == '\t' || c == '\n' || c == '\v' ||
c == '\f' || c == '\r' || c == ' ')
return 0;
/* Nope, error. Don't consume the bad character. */
if (c != EOF)
ungetc(c, in);
return -1;
}
Обратите внимание, что эта функциявозвращает 0 в случае успеха, ненулевое значение в случае ошибки.
Функция для анализа значения поля заголовка, неотрицательное целое число.
Обратите внимание, что эта функция пропускается ведущий пробел и комментарии, но оставляет символ, который завершил значение в потоке (через ungetc()
).
int pnm_value(FILE *in)
{
unsigned int val, old;
int c, digit;
if (!in || ferror(in))
return -1; /* Invalid file handle. */
/* Skip leading ASCII whitespace and comments. */
c = getc(in);
while (c == '\t' || c == '\n' || c == '\v' ||
c == '\f' || c == '\r' || c == ' ' || c == '#')
if (c == '#') {
/* Skip the rest of the comment */
while (c != EOF && c != '\n' && c != '\r')
c = getc(in);
} else
c = getc(in);
/* Parse initial decimal digit of value. */
val = decimal_digit(c);
if (val < 0)
return -2; /* Invalid input. */
while (1) {
c = getc(in);
/* Delimiter? End of input? */
if (c == '\t' || c == '\n' || c == '\v' ||
c == '\f' || c == '\r' || c == ' ' || c == '#') {
/* Do not consume the character following the value. */
ungetc(c, in);
return val;
} else
if (c == EOF)
return val;
/* Is it a decimal digit? */
digit = decimal_digit(c);
if (digit < 0)
return -2; /* Invalid input. */
/* Convert, checking for overflow. */
old = val;
val = (val * 10) + digit;
if (val / 10 != old)
return -3; /* Overflow. */
}
}
Помните:
*Форматы 1057 *
P1
и P4
имеют два поля заголовка: ширина и высота , в этом порядке.
Форматы
P2
, P3
, P5
и P6
имеют три поля заголовка: ширина , высота и maxval .
Вы можете использовать fscanf(handle, "%u", &value)
для чтения каждого пикселя из файлов формата P1
и P2
, предполагая unsigned int value;
.Он вернет 1 в случае успеха.Для P1
значение будет 0 или 1;для P2
это будет от 0 до maxval включительно.
Вы можете использовать fscanf(handle, "%u %u %u", &red, &green, &blue)
для чтения каждого пикселя из файлов формата P3
,при условии unsigned int red, green, blue;
.Он вернет 3 в случае успеха.Тогда каждый компонент будет иметь значение от 0 до maxval включительно.
P4
формат - самый неприятный для чтения.Лучше всего сделать одну строку пикселей за раз, используя fread(buf, width, 1, handle)
, с unsigned char buf[width];
или динамически распределенным массивом аналогичного размера.Тогда пиксель x
равен !!(buf[x/8] & (1 << (x & 7)))
(0 - белый, 1 - черный; с x от 0 до ширина -1).(!!
является оператором типа double-not или not-not: он возвращает 0, если аргумент равен 0, и 1 в противном случае.)
Для формата P5
, если maxval > = 256, тогда каждый пиксель состоит из двух байтов.Вы можете использовать
static float p5_gray(FILE *in, int maxval)
{
if (maxval >= 256 && maxval < 65536) {
int hi, lo;
hi = fgetc(in);
lo = fgetc(in);
if (lo == EOF)
return -1.0f;
return (float)(hi*256 + lo) / (float)maxval;
} else
if (maxval >= 1 && maxval < 256) {
int val;
val = fgetc(in);
if (val == EOF)
return -1.0f;
return (float)val / (float)maxval;
} else
return -2.0f;
}
, чтобы прочитать каждый пиксель из формата P5
.Функция возвращает 0.0f для белого, 1.0f для черного.
Для формата P6
, если maxval > = 256, то каждый пиксель составляет 6 байтов;в противном случае каждый пиксель составляет три байта.Вы можете использовать, например,
static int p6_rgb(FILE *in, int maxval, float *red, float *green, float *blue)
{
const float max = (float)maxval;
unsigned char buf[6];
if (maxval >= 256 && maxval < 65536) {
if (fread(buf, 6, 1, in) != 1)
return -1; /* Error! */
if (red)
*red = (float)(buf[0]*256 + buf[1]) / max;
if (green)
*green = (float)(buf[2]*256 + buf[1]) / max;
if (blue)
*blue = (float)(buf[4]*256 + buf[5]) / max;
return 0;
} else
if (maxval >= 1 && maxval < 256) {
if (fread(buf, 3, 1, in) != 1)
return -1; /* Error! */
if (red)
*red = (float)buf[0] / max;
if (green)
*green = (float)buf[1] / max;
if (blue)
*blue = (float)buf[2] / max;
return 0;
} else
return -2; /* Invalid maxval */
}
для чтения каждого пикселя из файла формата P6
.
Таким образом, если in
- дескриптор открытого файла (илискажем stdin
), и у вас есть int format, width, height, maxval;
, вы можете сделать
format = pnm_magic(in);
if (format < 1 || format > 6) {
/* Unrecognized format; fail! */
}
width = pnm_value(in);
if (width <= 0) {
/* Invalid width; fail! */
}
height = pnm_value(in);
if (height <= 0) {
/* Invalid height; fail! */
}
if (format == 2 || format == 3 || format == 5 || format == 6) {
maxval = pnm_value(in);
if (maxval < 1 || maxval > 65535) {
/* Invalid maxval; fail! */
}
}
if (pnm_endheader(in)) {
/* Bad end of header; fail! */
}
для анализа заголовка, оставив положение файла в начале данных пикселей.