我正在做一个项目,其中涉及到编写读写和编码/解码PGM文件的函数。我使用一个在PGM文件中读取函数的结构。我对结构和它们是如何同步的非常陌生,所以我只是想知道这段代码是否会正确地将扫描数据读入我的结构中。
这是我的代码(C):
#include <stdio.h>
#include "image.h"
int **allocatePGM(int numCols, int numRows){
int ** = malloc(sizeof(int *) * numRows);
for (int i=0; i<numRows; i++)
pixels[i] = malloc(sizeof(int) * numCols);
return pixels;
}
ImagePGM *readPGM(char *filename, ImagePGM *pImagePGM){
FILE *inFile = NULL
char PGMcheck[5];
int max_value = 0;
unsigned int width = 0, height = 0;
unsigned int i = 0;
int pixeldata = 0;
inFile = fopen(filename, "r");
if (inFile == NULL)
printf("File could not be opened\n");
exit(1);
fgets(PGMcheck, sizeof(PGMcheck), inFile);
if (strcmp(version, "P5")) {
fprintf(stderr, "Wrong file type!\n");
exit(1);
}
printf("This file does not contain the PGM indicator \"P2\"");
exit(1);
}
fscanf(inFile, "%d", &width);
fscanf(inFile, "%d", &height);
fscanf(inFile, "%d", max_value);
struct ImagePGM.pImagePGM
pImagePGM.magic = PGMcheck;
pImagePGM.width = width;
pImagePGM.height = height;
pImagePGM.max_value = max_value;
pImagePGM->pixels = allocatePGM(pImagePGM->width, pImagePGM->height);
if (pImagePGM->max_value > 255) {
for (i = 0; i < height; ++i) {
for (j = 0; j < width; ++j) {
pImagePGM->pixels[i][j];
}
}
}
return pImagePGM;
}我的头文件包含如下结构..。
typedef struct _imagePGM {
char magic[3]; // magic identifier, "P2" for PGM
int width; // number of columns
int height; // number of rows
int max_value; // maximum grayscale intensity
int **pixels; // the actual grayscale pixel data, a 2D array
} ImagePGM;你们觉得还好吗?
发布于 2019-11-07 02:00:00
继续我之前的评论,您有几个与处理普通PGM文件格式有关的问题,这些问题会阻止您成功地读取文件。
首先,fgets(PGMcheck, sizeof(PGMcheck), inFile);不能保证正确读取PGMcheck。幻数后面可能是"(blanks, TABs, CRs, LFs)",所以fgets会读更多的魔术数字,除非它后面跟着一个'\n' --格式不保证。虽然fgets()通常是进行面向行的输入的正确方式,但是PGM格式并不保证以行形式格式化,所以您可以使用格式化的输入函数或逐字符的方法。
(您可以使用fgets(),但这需要解析产生的缓冲区,并保存超出魔术号的缓冲区的任何部分,作为下一次读取的开始部分)。
您已经更正了使用!=而不是strcmp进行字符串比较的尝试,但是仍然必须将神奇的数字与"P2"进行比较,以读取平原-PGM格式文件(正如您最初的问题所示),继续将魔术号读取到字符串中,但是使用格式化的输入函数(fscanf)只读取到第一个空格,而不管空格是什么。
最后,不需要将魔术数字存储为plain_pgm结构的一部分。这是在尝试填充结构之前进行验证的内容。它要么是"P2",要么不是--没有必要存储它。
对于可移植性,在读取图像文件时使用精确的宽度类型来存储是一个好主意。有许多好处,但最主要的是,您的程序将正确运行在您的x86_64或您的TI- are 432芯片上。在stdint.h中定义了精确的宽度类型,而打印和读取的宏则在inttypes.h中提供了精确的宽度类型。不是char,而是int8_t,而不是unsigned char,而是uint8_t等等,其中数值指定了类型的确切字节数。
这样,pgm的结构看起来可能如下:
typedef struct { /* struct for plain pgm image */
uint32_t w, h; /* use exact width types for portable code */
uint16_t max; /* 16-bit max */
uint16_t **pixels; /* pointer-to-pointer for pixel values */
} plain_pgm;您的分配在很大程度上是正确的,但是重新安排将指针到指针返回到uint16_t (足够maximum gray value像素值),您可以这样做:
uint16_t **alloc_pgm_pixels (uint32_t w, uint32_t h)
{
uint16_t **pixels = NULL;
/* allocate/validate height number of pointers */
if (!(pixels = malloc (h * sizeof *pixels))) {
perror ("malloc-pixels");
return NULL;
}
/* allocate/validate width number of values per-pointer */
for (uint32_t i = 0; i < h; i++)
if (!(pixels[i] = malloc (w * sizeof *pixels[i]))) {
perror ("malloc-pixels[i]");
return NULL;
}
return pixels; /* return allocated pointers & storage */
}你的阅读功能需要很多帮助。首先,您通常希望打开和验证文件是打开的,以便读取调用函数,并将打开的FILE *指针作为参数传递给读取函数,而不是文件名。(如果无法在调用方中打开文件,则不需要首先进行函数调用)。通过该更改并将指针传递给您的结构,您的读函数看起来可能如下所示:
int read_pgm (FILE *fp, plain_pgm *pgm)
{
char buf[RDBUF]; /* buffer for magic number */
uint32_t h = 0, w = 0; /* height/width counters */
if (fscanf (fp, "%s", buf) != 1) { /* read magic number */
fputs ("error: invalid format - magic\n", stderr);
return 0;
}
if (strcmp (buf, MAGIC_PLN) != 0) { /* validate magic number */
fprintf (stderr, "error: invalid magic number '%s'.\n", buf);
return 0;
}
/* read pgm width, height, max gray value */
if (fscanf (fp, "%" SCNu32 " %" SCNu32 " %" SCNu16,
&pgm->w, &pgm->h, &pgm->max) != 3) {
fputs ("error: invalid format, h, w, max or included comments.\n",
stderr);
return 0;
}
/* validate allocation of pointers and storage for pixel values */
if (!(pgm->pixels = alloc_pgm_pixels (pgm->w, pgm->h)))
return 0;
for (;;) { /* loop continually until image read */
if (fscanf (fp, "%" SCNu16, &pgm->pixels[h][w]) != 1) {
fputs ("error: stream error or short-read.\n", stderr);
return 0;
}
if (++w == pgm->w)
w = 0, h++;
if (h == pgm->h)
break;
}
return 1;
}(注意:这个读取函数不考虑注释行,实现对注释行的忽略是留给您的。您可以在读取神奇的数字、宽度、高度和最大灰度值之前和之间对fscanf进行额外调用,以跳过任意数量的空白,读取到并包含下一个'#'字符和行尾,或者只在循环中使用fgetc搜索下一个非空白字符,检查它是否为'#',如果使用ungetc,则清除行尾。)
在一个例子中,您可以这样做:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#define RDBUF 32 /* if you need a constant, #define one (or more) */
#define MAGIC_PLN "P2"
typedef struct { /* struct for plain pgm image */
uint32_t w, h; /* use exact width types for portable code */
uint16_t max; /* 16-bit max */
uint16_t **pixels; /* pointer-to-pointer for pixel values */
} plain_pgm;
uint16_t **alloc_pgm_pixels (uint32_t w, uint32_t h)
{
uint16_t **pixels = NULL;
/* allocate/validate height number of pointers */
if (!(pixels = malloc (h * sizeof *pixels))) {
perror ("malloc-pixels");
return NULL;
}
/* allocate/validate width number of values per-pointer */
for (uint32_t i = 0; i < h; i++)
if (!(pixels[i] = malloc (w * sizeof *pixels[i]))) {
perror ("malloc-pixels[i]");
return NULL;
}
return pixels; /* return allocated pointers & storage */
}
int read_pgm (FILE *fp, plain_pgm *pgm)
{
char buf[RDBUF]; /* buffer for magic number */
uint32_t h = 0, w = 0; /* height/width counters */
if (fscanf (fp, "%s", buf) != 1) { /* read magic number */
fputs ("error: invalid format - magic\n", stderr);
return 0;
}
if (strcmp (buf, MAGIC_PLN) != 0) { /* validate magic number */
fprintf (stderr, "error: invalid magic number '%s'.\n", buf);
return 0;
}
/* read pgm width, height, max gray value */
if (fscanf (fp, "%" SCNu32 " %" SCNu32 " %" SCNu16,
&pgm->w, &pgm->h, &pgm->max) != 3) {
fputs ("error: invalid format, h, w, max or included comments.\n",
stderr);
return 0;
}
/* validate allocation of pointers and storage for pixel values */
if (!(pgm->pixels = alloc_pgm_pixels (pgm->w, pgm->h)))
return 0;
for (;;) { /* loop continually until image read */
if (fscanf (fp, "%" SCNu16, &pgm->pixels[h][w]) != 1) {
fputs ("error: stream error or short-read.\n", stderr);
return 0;
}
if (++w == pgm->w)
w = 0, h++;
if (h == pgm->h)
break;
}
return 1;
}
int main (int argc, char **argv) {
plain_pgm pgm = { .w = 0 }; /* plain_pgm struct instance */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if (!read_pgm (fp, &pgm)) { /* validate/allocate/read pgm file */
fputs ("error: read_pgm failed.\n", stderr);
return 1;
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
/* output success */
printf ("successful read of '%s'\n%" PRIu32 "x%" PRIu32 " pixel values.\n",
argc > 1 ? argv[1] : "stdin", pgm.w, pgm.h);
for (uint32_t i = 0; i < pgm.h; i++) /* free pixel storage */
free (pgm.pixels[i]);
free (pgm.pixels); /* free pointers */
}示例使用/输出
使用示例gasket.ascii.pgm,一幅600宽600英尺高的阿波罗垫片图像文件作为测试文件,您将得到:
$ ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
successful read of 'dat/apollonian_gasket.ascii.pgm'
600x600 pixel values.内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有两个责任:(1)始终保留一个指向内存块起始地址的指针,这样,(2)当不再需要它时,就可以释放它。
必须使用内存错误检查程序来确保不尝试访问内存或写入超出/超出分配块界限的范围,尝试根据未初始化的值读取条件跳转或以条件跳转为基础,最后确认是否释放了已分配的所有内存。
对于Linux来说,valgrind是正常的选择。每个平台都有类似的内存检查程序。它们都很简单使用,只需运行您的程序通过它。
$ valgrind ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
==8086== Memcheck, a memory error detector
==8086== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8086== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==8086== Command: ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
==8086==
successful read of 'dat/apollonian_gasket.ascii.pgm'
600x600 pixel values.
==8086==
==8086== HEAP SUMMARY:
==8086== in use at exit: 0 bytes in 0 blocks
==8086== total heap usage: 604 allocs, 604 frees, 730,472 bytes allocated
==8086==
==8086== All heap blocks were freed -- no leaks are possible
==8086==
==8086== For counts of detected and suppressed errors, rerun with: -v
==8086== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)始终确认您已释放了已分配的所有内存,并且没有内存错误。
回顾一下所做的改变,如果你不明白为什么有些事情已经做了,请放弃评论,我很乐意提供进一步的帮助。
发布于 2019-11-07 00:56:26
我不知道PGM规范,但是您有三个常见的错误,当您在平台上编译时会出现与您不同的代码错误:
https://stackoverflow.com/questions/58739935
复制相似问题