我正在尝试编写一个使用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;
}
你们有什么解决方案吗?
答案 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句柄,并指向width
,height
和maxval
(PBM格式标头设置为1),则会显示将PNM_P1
返回到PNM_P6
,其中三个变量用标题信息填充,如果无法正确解析标题,则返回PNM_UNKNOWN
。
我已经确认此函数可以正确解析我拥有的所有PBM,PGM和PPM文件的标题(包括我的Linux笔记本电脑上的所有210个左右图标文件)。