使用结构读取PGM文件

问题描述 投票:0回答:2

我正在做一个涉及编写功能以读取/写入和编码/解码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;

你们似乎还好吗?

c pointers struct dynamic-arrays pgm
2个回答
0
投票

我不知道PGM规范,但是当您在与您的平台不同的平台上编译时,您会遇到三个常见的错误,这些错误会使您的代码无法正常工作:

  1. Endianess。您必须为您的数据格式精确定义它。在您的情况下,int可能是低位优先的,将代码移植到高位优先的平台时必须考虑到这一点。另请参见https://en.wikipedia.org/wiki/Endianness

  2. 结构包装。根据平台的不同,编译器可以填充结构中的字段以加快访问速度。您可能希望对结构使用诸如pragma pack之类的结构,否则,您的代码可能还会与其他编译器发生问题(即使假设使用相同的平台)。另请参见http://www.catb.org/esr/structure-packing/#_structure_alignment_and_padding

  3. 使用固定宽度类型。例如。使用int64_t代替long等。另请参见https://en.wikipedia.org/wiki/C_data_types#Fixed-width_integer_types


0
投票

根据我先前的评论,您有一些与处理Plain PGM File Format有关的问题,这些问题将阻止您成功读取文件。

首先,不能保证fgets(PGMcheck, sizeof(PGMcheck), inFile);正确读取PGMcheckmagic-number后面可以跟有"(blanks, TABs, CRs, LFs)",因此fgets读取的内容将不仅限于magic-number,除非它后面跟有单个'\n',但格式不保证。虽然fgets()通常是进行面向行输入的正确方法,但是PGM格式不能保证按行进行格式化,因此您可以使用formatted-input函数或逐个字符角色方法。

((您可以使用fgets(),但这将需要解析结果缓冲区,并保存超出magic-number的缓冲区的任何部分,以包括为下一次读取的开始部分)]

您已使用!=而不是strcmp纠正了字符串比较的尝试,但是您仍必须将magic-number"P2"进行比较以读取Plain-PGM格式的文​​件(作为您的问题继续阅读并将magic-number读入字符串,但是使用格式化输入函数(fscanf)仅读取直到遇到第一个空格,无论该空格是什么。] >

最后,没有必要将magic-number

作为plain_pgm结构的一部分进行存储。这是您尝试填充结构之前的validate的内容。它是"P2"或不是-无需存储。

出于可移植性,在读取图像文件时,最好使用exact-width

类型进行存储。有很多好处,但是最主要的是,无论是在x86_64还是在TI-MSP432芯片上运行,程序都可以正确运行。在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;

您的分配在很大程度上是正确的,但是重新安排以返回pointer-to-pointer-to

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 bytes 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 */
}

您的阅读功能需要很多帮助。首先,您通常要打开并确认文件已打开以供在调用函数中读取,然后将open FILE *指针作为参数而不是文件名传递给读取函数。 (如果无法在调用程序中打开文件,则无需首先进行函数调用)。进行此更改并传递指向您的结构的指针后,您的read函数可能类似于:

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

[note:

此读取函数不考虑注释行,实现注释行的忽略留给您。您可以在读取文件的每个部分之前和之间对fscanf进行额外的调用。幻数,宽度,高度和最大灰度值,其值类似于" # %[^\n']",以跳过任何数量的空格,并读取并包括下一个'#'字符和行尾,或仅使用[C0 ]在循环中搜索下一个非空白字符,并检查它是否为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 bytes 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 */
}

示例使用/输出

使用示例apollonian_gasket.ascii.pgm, a 600 wide by 600 high image of an Apollonian gasket文件作为测试文件,您将获得:

$ ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
successful read of 'dat/apollonian_gasket.ascii.pgm'
600x600 pixel values.

内存使用/错误检查

在您编写的任何可动态分配内存的代码中,对于任何已分配的内存块,您都有2个职责

:(1)始终保留指向起始地址的指针,因此,( 2)当不再需要它时,可以将其freed

务必使用内存错误检查程序,以确保您不会尝试访问内存或在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,确认您释放了已分配的所有内存。

对于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)

始终确认已释放已分配的所有内存,并且没有内存错误。

查看所做的更改,如果您不明白为什么未完成某些操作,请发表评论,我们很乐意为您提供进一步的帮助。

热门问题
推荐问题
最新问题