【问题标题】:Read ppm header information and output to console and output file in C读取 ppm 头信息并输出到 C 中的控制台和输出文件
【发布时间】:2018-10-14 17:50:59
【问题描述】:

开始学习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 函数,但是创建了一个无限循环而没有看到如何。我很抱歉,但我不确定我应该问什么样的问题。

【问题讨论】:

  • 你的编译器没有告诉你出了什么问题?始终打开一组健康的警告(使用 -Wall -Wextra 编译 gcc 和 clang)。
  • 感谢您的信息!

标签: c ppm


【解决方案1】:

Netpbm 格式有点奇怪,因为许多程序员最初都错误地阅读了它们。

简单地说,“幻数”(P1P7)必须在文件的开头,然后是标题字段,然后是单个空白字符,然后是数据。诀窍是每个标题字段可能之前有空格和/或注释,并且标题后跟一个空格字符。

P7 格式,便携式任意映射文件,具有命名的标题字段,但无论如何它很少支持格式,所以我将仅限于常见的P1P6 格式。 (不过,要支持它的标头字段,您只需要另一个辅助函数。)

你需要四个辅助函数:

  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. 一个解析头域值的函数,一个非负整数。

    请注意,此函数会跳过 前导 空格和 cmets,但会在流中保留终止值的字符(通过 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. */
        }
    }
    

记住:

  • P1P4 格式有两个标题字段:widthheight,按顺序排列。

  • P2P3P5P6 格式具有三个标题字段:widthheight最大值.

  • 假设unsigned int value;,您可以使用fscanf(handle, "%u", &amp;value)P1P2 格式文件中读取每个像素。如果成功,它将返回 1。对于P1,值为 0 或 1;对于P2,它将是从 0 到 maxval(含)。

  • 假设unsigned int red, green, blue;,您可以使用fscanf(handle, "%u %u %u", &amp;red, &amp;green, &amp;blue)P3 格式文件中读取每个像素。如果成功,它将返回 3。然后,每个分量将从 0 到 maxval,包括 0。

  • P4 格式是最难读的。最好一次完成一行像素,使用fread(buf, width, 1, handle)unsigned char buf[width]; 或类似大小的动态分配数组。然后,像素x!!(buf[x/8] &amp; (1 &lt;&lt; (x &amp; 7)))(0 为白色,1 为黑色;x 从 0 到 width-1)。 (!! 是一个双重非或非非运算符:如果参数为 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! */
}

解析头部,将文件位置留在像素数据的开头。

【讨论】:

  • 感谢一步一步的澄清!
【解决方案2】:

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);
    }

【讨论】:

  • 感谢您指出这一点。我不知道该功能,但无论如何我似乎创建了一个无限循环。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
  • 2018-12-30
  • 1970-01-01
相关资源
最近更新 更多