【问题标题】:Converting ppm file from P3 to P6 using C使用 C 将 ppm 文件从 P3 转换为 P6
【发布时间】:2018-07-08 18:30:34
【问题描述】:

我正在尝试编写一个使用 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;
}

你们有什么解决办法吗?

【问题讨论】:

  • 你得到了分段错误,因为你首先释放了一个结构(图像),然后尝试访问/释放一个成员(图像->像素)
  • 我注意到了一件事。在您的头文件中,您在引号中包含标准库头("stdio.h" 等),而不是尖括号(&lt;stdio.h&gt;)。引号仅适用于这些标题与您的标题位于同一目录中的情况。

标签: c pointers segmentation-fault ppm


【解决方案1】:

读取表头时需要指定要读取的幻数的字符数:

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

要与strcmp()一起使用,数组中的最后一个字节必须设置为0:

header.MAGIC_NUMBER[2] = 0;

在写header的时候需要写成字符串:

fprintf(out_file, "%s %d %d %d", header.MAGIC_NUMBER,header.HEIGHT,header.WIDTH,header.MAX_COLOR);

【讨论】:

  • 这有助于解决很多问题,谢谢!!但是,出于某种原因,每当我检查我创建的 p6 ppm 的文本文件时,它都会显示为一个空框字符而不是 6。您对此有什么解决方案吗?
  • 你的意思是幻数的第二个字符吗? “6”字符?编辑:我想我已修复它请在编写标题时删除header.MAGIC_NUMBER 之前的“*”(已修复)
  • 谢谢!我这样做了,但它仍在发生。我非常不确定是什么原因导致它这样做,因为其他所有内容都可以正常复制。而且,它导致我的 ppm 图像无法正确打开,因为它说它的标题不正确。即使我尝试手动将其更改为 p6 它仍然无法正常工作。我的标题看起来像 P 256 256 255。编辑:哦,似乎盒子没有复制。我觉得也许我有某种字符错误,因为它可能是由于它试图将 int 复制到字符数组中引起的?但我不知道如何解决。
  • 我不太确定,但你可以尝试在 scanf 中使用 %2s 而不是 %2c 我建议尝试使用 printf 调试或输出幻数以检查它是否读取问题
【解决方案2】:
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;
}

重复分配相同的内存

【讨论】:

  • 如何让它移动到下一个内存位置,存储多个像素值?
  • 链表、共享内存、数组、文件等,选一个你擅长的
【解决方案3】:

使用 fscanf() 解析 PPM 格式标头的方式永远行不通。

就像我在this related answer 的第一点中解释的那样,PPM 标头可能包含 cmets。

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

(P7,Portable AnyMap 或 PAM,有一个额外的参数,tupletype,它是一个大写的字符串。它比其他的实现得少,虽然它是一种非常好的通用图像格式。下面的方法适用于它同样,如果您自己添加对元组类型的解析。)

似乎很多人在构造“读取一个 PNM 格式标头值”部分时遇到了麻烦。我已经看到太多适用于某些(但不是所有)PNM 格式文件的代码示例,这会损害不可操作性并给开发人员和用户带来不必要的头痛,因此我认为有必要展示一个示例如何实现它正确

读取任何 PNM 格式的标头的正确操作很简单:您使用fgetc() 来读取标头的固定部分(P,格式数字和以下空白字符,@ 之一987654326@、'\n''\v''\f''\r'' '),以及解析非负整数值的辅助函数。 (对于 P1 和 P4 格式,只有两个值,宽度和高度,按像素顺序排列;对于 P2、P3、P5 和 P6 格式,有三个值:宽度、高度和 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;
}

上述函数解析输入的方式与%dscanf() 系列函数中所做的方式非常相似,只是它正确跳过了值前面的任何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() 提供一个文件句柄,并提供指向widthheightmaxval 的指针(对于PBM 格式标头设置为1),它将返回PNM_P1PNM_P6用三个变量填充头部信息,如果头部无法正确解析,则为PNM_UNKNOWN

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    • 2018-08-05
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多