Чтение информации заголовка ppm и вывод на консоль и вывод файла в C - PullRequest
0 голосов
/ 14 октября 2018

Начало изучения C;Я пытаюсь читать и обрабатывать символы по мере их поступления, отбрасывая пробельные символы.Мне также нужно определить, является ли это комментарием «#» или первым символом следующего входного значения.Цель состоит в том, чтобы получить и записать магическое число, ширину, высоту и maxval в новый файл.

int main (int argc, char **argv)
{

char buffer[100];
int num_chars = 0;
FILE *input;
input = fopen(argv[1], "rb");

FILE *output;
output = fopen(argv[2], "w");

if (input == NULL){
    printf("ERROR: Input file needed!");
    return -1;
}

if (output == NULL){
    printf("ERROR: Output file needed!");
    return -1;
}

for (int i = fgetc(input); i != EOF; i = fgetc(input)) {

    if (isspace(i)){
        printf("The magic number is: %c\n", i);
        ungetc(i, input);
    } else if (i == '#') {
        for (int j = i; isspace(j); j = fgetc(input)){
            buffer[num_chars++] = j;
            printf("Comment found: %c\n", j);
            ungetc(i, input);
        }
    }


}


return 0;
}

Редактировать : использовалась предложенная функция isspace, но был создан бесконечный цикл безвидя как.Я извиняюсь, но я не уверен, какие вопросы я собираюсь задавать.

Ответы [ 2 ]

0 голосов
/ 14 октября 2018

Форматы Netpbm немного странны, поскольку многие программисты изначально неправильно их читают.

Проще говоря, "магическое число" (P1 до P7) должнонаходиться в начале файла, за ним следуют поля заголовка, затем один символ пробела, за которым следуют данные.Хитрость заключается в том, что каждому полю заголовка может быть , которому предшествует пробел и / или комментарий, а за заголовком следует один символ пробела.

Формат P7, файл Portable Arbitrary Map, имеет названные поля заголовка, но в любом случае это редко поддерживаемый формат, поэтому я ограничусь только общими форматами P1 до P6.(Для поддержки полей заголовка вам нужна только другая вспомогательная функция.)

Вам нужны четыре вспомогательные функции:

  1. Функция для преобразования десятичной цифры в ее числовое значениезначение.

    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-совместимые наборы символов; в настоящее время очень редко.)

  2. Функция для чтения магического числа.

    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 */ 
        }
    }
    

    Использование вспомогательной функции для анализа магического числа, разумеется, не является абсолютно необходимым, но его использование определенно упрощает чтение, проверку и обслуживание вашего кода.Так что использование одного - хорошая вещь.

  3. Функция для чтения символа пробела в конце заголовка.

    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 в случае успеха, ненулевое значение в случае ошибки.

  4. Функция для анализа значения поля заголовка, неотрицательное целое число.

    Обратите внимание, что эта функция пропускается ведущий пробел и комментарии, но оставляет символ, который завершил значение в потоке (через 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! */
}

для анализа заголовка, оставив положение файла в начале данных пикселей.

0 голосов
/ 14 октября 2018

for (int j = i; i != ' ' || i != '\n'; j = fgetc(input))

Вы используете выражение, зависящее от i, но вы изменяете j.

Затем в цикле вы делаете:

buffer[num_chars++] = j;

, который в конечном итоге переполнится.

Возможно, вы имели в виду:

for (int j = i; j != ' ' || j != '\n'; j = fgetc(input))

Но почему бы вам не использовать стандартные функции, такие как isspace()?

Также проверьте переполнение буфера:

    for (int j = i; j != ' ' || j != '\n'; j = fgetc(input)){
        assert(num_chars < sizeof(buffer)/sizeof(*buffer);
        buffer[num_chars++] = j;
        printf("Comment found: %s\n", j);
    }
...