Чтение файла PGM с использованием структур - PullRequest
0 голосов
/ 07 ноября 2019

Я делаю проект, который включает в себя написание функций для чтения / записи и кодирования / декодирования файлов 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;

С вами все в порядке, ребята?

Ответы [ 2 ]

0 голосов
/ 07 ноября 2019

Продолжая мой предыдущий комментарий, у вас есть несколько проблем, связанных с обработкой Обычного формата файла PGM , который помешает вашему успешному чтению файла.

Во-первых, fgets(PGMcheck, sizeof(PGMcheck), inFile); не гарантируется правильное чтение PGMcheck. За магическим числом может следовать "(blanks, TABs, CRs, LFs)", поэтому fgets будет читать больше, чем просто магическое число , если за ним не следует один '\n' - не гарантируетсяпо формату. Хотя fgets(), как правило, является правильным способом для строчно-ориентированного ввода, формат PGM не обязательно должен быть отформатирован в строках, поэтому вам остается использовать функцию отформатированный ввод или символьнуюиндивидуальный подход.

(вы можете использовать fgets(), но для этого потребуется анализ результирующего буфера и сохранение любой части буфера, расширенной за пределы magic-number , чтобы включить в качестве начальной части следующегочитать)

Вы исправили свою попытку сравнения строк, используя != вместо strcmp, но вы все равно должны сравнить магическое число с "P2" для чтения на обычном языке. -PGM формат файла (так как ваш вопрос изначально был включен) Идите вперед и прочитайте magic-number в строку, но используйте функцию форматированного ввода (fscanf), чтобы читать только до тех пор, пока первый пробел не станетвстречаются независимо от того, что это за пробел.

Наконец, нет необходимости хранить магическое число как часть структуры plain_pgm. Это то, что вы проверяете , прежде чем пытаться заполнить структуру. Это либо "P2", либо нет - не нужно его хранить.

Для переносимости рекомендуется использовать типы точной ширины для хранения при чтении изображенияфайлы. Есть ряд преимуществ, но главное, ваша программа будет работать правильно, независимо от того, запущена она на вашем 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;

Ваше распределение в значительной степени корректно, но переставляет так, чтобы возвращать указатель-на-указатель на 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 */
}

Ваша функция чтения требует большой помощи. Во-первых, вы обычно хотите открыть и проверить файл, открытый для чтения в вызывающей функции, и передать указатель open 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 до и между чтением каждой части магического числа, ширины, высоты и максимального значения серого с чем-то похожим на " # %[^\n']" дляпропустите любое количество пробелов и прочитайте до и включая следующий '#' символ и до конца строки, или просто используйте 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 */
}

Пример Использование/ Вывод

Используя пример apollonian_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.

ЯMory Использование / Проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (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)

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Просмотритевнесенные изменения, и если вы не понимаете, почему что-то не было сделано, оставьте комментарий с вопросом, и я буду рад помочь вам.

0 голосов
/ 07 ноября 2019

Я не знаю спецификацию PGM, но у вас есть три распространенные ошибки, которые могут привести к неправильной работе вашего кода при компиляции на платформе, отличной от вашей:

  1. Endianess. Вы должны точно определить его для вашего формата данных. В вашем случае целые числа, скорее всего, имеют порядок байтов, вы должны учитывать это при переносе кода на платформу с прямым порядком байтов. См. Также https://en.wikipedia.org/wiki/Endianness

  2. Структура упаковки. В зависимости от платформы, компилятор может заполнять поля в структурах для более быстрого доступа. Скорее всего, вы захотите использовать конструкцию наподобие пакета pragma для своих структур, иначе, опять же, у вашего кода могут возникнуть проблемы с другим компилятором (даже если предполагается, что эта же платформа). См. Также 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

...