使用C将ppm文件从P3转换为P6

时间:2018-01-29 23:42:47

标签: c pointers segmentation-fault ppm

我正在尝试编写一个使用C将p3 PPM文件转换为P6的程序。但是我遇到了两个问题。 1,我的代码中出现了分段错误。 2,标题没有被正确读入转换后的p6文件中。这是我的头文件。

#ifndef PPM_UTILS
#define PPM_UTILS

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

// First meaningful line of the PPM file
typedef struct header {
    char MAGIC_NUMBER[3];
    unsigned int HEIGHT, WIDTH, MAX_COLOR;
} header_t;

// Represents an RGB pixel with integer values between 0-255
typedef struct pixel {
    unsigned int R, G, B;
} pixel_t;

// PPM Image representation
typedef struct image {
    header_t header;
    pixel_t* pixels;
} image_t;

header_t read_header(FILE* image_file);
image_t* read_ppm(FILE* image_file);
image_t* read_p6(FILE* image_file, header_t header);
image_t* read_p3(FILE* image_file, header_t header);

void write_header(FILE* out_file, header_t header);
void write_p6(FILE* out_file, image_t* image);
void write_p3(FILE* out_file, image_t* image);

#endif

我将代码的主要部分拆分为两个文件,我在编译器中结合

#include <stdio.h>
#include "ppm_utils.h"

header_t read_header(FILE* image_file)
{

    header_t header;

    fscanf(image_file, "%c %d %d %d",header.MAGIC_NUMBER, &header.WIDTH, &header.HEIGHT, &header.MAX_COLOR);
    return header;
}

void write_header(FILE* out_file, header_t header)
{

    fprintf(out_file, "%c %d %d %d", *header.MAGIC_NUMBER,header.HEIGHT,header.WIDTH,header.MAX_COLOR);
}

image_t* read_ppm(FILE* image_file)
{

    header_t header = read_header(image_file);

    image_t* image = NULL;
    if (strcmp("P3", header.MAGIC_NUMBER) == 0)
    {
        image = read_p3(image_file, header);
    }
    else if (strcmp("P6", header.MAGIC_NUMBER) == 0)
    {
        image = read_p6(image_file, header);
    }
    return image;
}

image_t* read_p6(FILE* image_file, header_t header)
{

    int size;
    size = header.HEIGHT * header.WIDTH;
    image_t* image = (image_t*) malloc (sizeof(image_t));
    image -> header = header;
    image -> pixels = (pixel_t*) malloc (sizeof(pixel_t)* size);
    char r,g,b;
    r = 0;
    g = 0;
    b = 0;
    int i;
    for (i=0;i<size;i++){
        fscanf(image_file, "%c%c%c", &r, &g, &b);
        image -> pixels -> R = (int) r;
        image -> pixels -> G = (int) g;
        image -> pixels -> B = (int) b;
    }
    return image;
}
image_t* read_p3(FILE* image_file, header_t header)
{
    int size;
    size = header.HEIGHT * header.WIDTH;
    image_t* image = (image_t*) malloc (sizeof(image_t));
    image -> header = header;
    image -> pixels = (pixel_t*) malloc (sizeof(pixel_t)* size);
    int r,g,b;
    r = 0;
    g = 0;
    b = 0;
    int i;
    for (i=0;i<size;i++){
        fscanf(image_file, "%d %d %d ", &r, &g, &b);
        image -> pixels -> R = (int) r;
        image -> pixels -> G = (int) g;
        image -> pixels -> B = (int) b;
    }
    return image;

}

void write_p6(FILE* out_file, image_t* image)
{
    header_t header = image -> header;
    header.MAGIC_NUMBER[1]=6;
    write_header(out_file, header);
    int size = header.HEIGHT * header.WIDTH;
    int i;
    for (i=0;i<size;i++){
        fprintf(out_file,"%c%c%c", (char) image->pixels->R, (char) image->pixels->G, (char) image->pixels->B);
    }

}
void write_p3(FILE* out_file, image_t* image)
{
    header_t header = image -> header;
    header.MAGIC_NUMBER[1]=3;
    write_header(out_file, header);
    int size = header.HEIGHT * header.WIDTH;
    int i;
    for (i=0;i<size;i++){
        fprintf(out_file,"%d %d %d ",image->pixels->R,image->pixels->G,image->pixels->B);

    }

}

...

#include <stdio.h>
#include "ppm_utils.h"

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

    if (argc != 3)
    {
        printf("The program needs two arguments");
        return 1;
    }

    FILE *fr;
    fr = fopen(argv[1],"r");

    if (fr == NULL)
    {
        printf("The opening failed");
    }

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

    if (fw == NULL)
    {
        printf("The opening failed");
    }
    image_t* image = read_ppm(fr);
    write_p6(fw,image);
    free(image);
    free(image->pixels);
    fclose(fr);
    fclose(fw);
    return 0;
}

你们有什么解决方案吗?

3 个答案:

答案 0 :(得分:0)

阅读标题时,您需要指定要读取的幻数的字符数:

width: calc(100% - 60px);

要与box-sizing: border-box;一起使用,必须将数组中的最后一个字节设置为0:

fscanf(image_file, "%2c %d %d %d",header.MAGIC_NUMBER, &header.WIDTH, &header.HEIGHT, &header.MAX_COLOR);

编写标题时,需要将其写为字符串:

strcmp()

答案 1 :(得分:0)

for (i=0;i<size;i++){
    fscanf(image_file, "%c%c%c", &r, &g, &b);
    image -> pixels -> R = (int) r;
    image -> pixels -> G = (int) g;
    image -> pixels -> B = (int) b;
}

重复分配相同的内存

答案 2 :(得分:0)

使用fscanf()解析PPM格式标头的方式永远不会起作用。

就像我在this related answer的第一点解释一样,PPM标题可能包含注释。

请注意,此答案同样适用于所有PNM格式,P1至P6(分别为PBM,PGM,PPM,PBM,PGM,PPM和PAM)。标题的格式是相同的,除了标题中列出的非负整数值的数量(PBM为2,PGM和PPM为3,PAM为4)。

(P7,Portable AnyMap或PAM,有一个额外的参数,tupletype,它是一个大写的字符串。虽然它是一个非常好的通用图像格式,但实际上比其他的更少。下面的方法适用于它如果你自己为tupletype添加解析。)

似乎很多人在构建&#34;读取一个PNM格式标头值&#34;部分。我已经看到太多的代码示例适用于某些但不是所有PNM格式文件,这会损害不可操作性并导致开发人员和用户无端头痛,因此我认为有必要展示一个如何实现它的示例正确

读取任何PNM格式标题的正确操作很简单:使用fgetc()读取标题的固定部分(P,格式数字和以下空白字符,'\t''\n''\v''\f''\r'' '之一,以及解析非负整数的辅助函数值。 (对于P1和P4格式,按顺序只有两个值,宽度和高度,以像素为单位;对于P2,P3,P5和P6格式,有三个值:width,height和maxval,其中maxval是文件中使用的最大组件值。)

我建议你去阅读上面链接的answer中的伪代码,如果你真的关心学习,并尝试先实现自己的。如果您遇到困难,可以与下面的已知工作版本进行比较。

为简单起见,让我们使用辅助函数pnm_is_whitespace(character),如果character是其中一个空白字符,则返回 true (非零):

#include <stdlib.h>
#include <stdio.h>

static inline int pnm_is_whitespace(const int c)
{
    return (c == '\t' || c == '\n' || c == '\v' ||
            c == '\f' || c == '\r' || c == ' ');
}

请注意,PNM文件不支持区域设置,因此上述内容适用于所有操作系统上的所有PNM格式。上面的函数只是一个速记辅助函数,即使在移植代码时也不需要修改。

因为PNM头中的值是非负整数,所以我们可以使用一个返回值的简单函数,如果发生错误,则使用-1

static inline int pnm_header_value(FILE *src)
{
    int  c;

    /* Skip leading whitespace and comments. */
    c = fgetc(src);
    while (1) {
        if (c == EOF) {
            /* File/stream ends before the value. */
            return -1;
        } else
        if (c == '#') {
            /* Comment. Skip the rest of the line. */
            do {
                c = fgetc(src);
            } while (c != EOF && c != '\r' && c != '\n');
        } else
        if (pnm_is_whitespace(c)) {
            /* Skip whitespace. */
            c = fgetc(src);
        } else
            break;
    }

    /* Parse the nonnegative integer decimal number. */
    if (c >= '0' && c <= '9') {
        int  result = (c - '0');

        c = fgetc(src);
        while (c >= '0' && c <= '9') {
            const int  old = result;

            /* Add digit to number. */
            result = 10*result + (c - '0');

            /* Overflow? */
            if (result < old)
                return -1;

            /* Next digit. */
            c = fgetc(src);
        }

        /* Do not consume the separator. */
        if (c != EOF)
            ungetc(c, src);

        /* Success. */
        return result;
    }

    /* Invalid character. */
    return -1;
}

上述函数以%d函数族中scanf()非常类似的方式解析输入,除了它正确地跳过值之前的任何PNM注释行。如果有错误,则返回-1,在这种情况下,文件实际上不是PNM格式。否则,它返回文件中指定的非负(0或更大)值,而不消耗该数字后面的字符。

使用上面的内容,我们可以轻松创建一个支持P1-P6格式PNM(PBM,PGM和PPM格式; ASCII和二进制变体)文件格式标题的函数:

enum {
    PNM_P1      = 1, /* ASCII PBM */
    PNM_P2      = 2, /* ASCII PGM */
    PNM_P3      = 3, /* ASCII PPM */
    PNM_P4      = 4, /* BINARY PBM */
    PNM_P5      = 5, /* BINARY PGM */
    PNM_P6      = 6, /* BINARY PPM */
    PNM_UNKNOWN = 0
};

static inline int pnm_header(FILE *src,
                             int  *width,
                             int  *height,
                             int  *maxdepth)
{
    int  format = PNM_UNKNOWN;
    int  value;

    if (!src)
        return PNM_UNKNOWN;

    /* The image always begins with a 'P'. */
    if (fgetc(src) != 'P')
        return PNM_UNKNOWN;

    /* The next character determines the format. */
    switch (fgetc(src)) {
    case '1': format = PNM_P1; break;
    case '2': format = PNM_P2; break;
    case '3': format = PNM_P3; break;
    case '4': format = PNM_P4; break;
    case '5': format = PNM_P5; break;
    case '6': format = PNM_P6; break;
    default:
        errno = 0;
        return PNM_UNKNOWN;
    }

    /* The next character must be a whitespace. */
    if (!pnm_is_whitespace(fgetc(src)))
        return PNM_UNKNOWN;

    /* Next item is the width as a decimal string. */
    value = pnm_header_value(src);
    if (value < 1)
        return PNM_UNKNOWN;
    if (width)
        *width = value;

    /* Next item is the height as a decimal string. */
    value = pnm_header_value(src);
    if (value < 1)
        return PNM_UNKNOWN;
    if (height)
        *height = value;

    /* Maxdepth, for all but P1 and P4 formats. */
    if (format == PNM_P1 || format == PNM_P4) {
        if (maxdepth)
            *maxdepth = 1;
    } else {
        value = pnm_header_value(src);
        if (value < 1)
            return PNM_UNKNOWN;
        if (maxdepth)
            *maxdepth = value;
    }

    /* The final character in the header must be a whitespace. */
    if (!pnm_is_whitespace(fgetc(src)))
        return PNM_UNKNOWN;

    /* Success. */
    return format;
}

如果您向pnm_header()提供FILE句柄,并指向widthheightmaxval(PBM格式标头设置为1),则会显示将PNM_P1返回到PNM_P6,其中三个变量用标题信息填充,如果无法正确解析标题,则返回PNM_UNKNOWN

我已经确认此函数可以正确解析我拥有的所有PBM,PGM和PPM文件的标题(包括我的Linux笔记本电脑上的所有210个左右图标文件)。