• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Odczytywanie z pliku binarnego

Object Storage Arubacloud
+1 głos
89 wizyt
pytanie zadane 4 sierpnia 2023 w C i C++ przez Zuzan Początkujący (390 p.)

Witam, mam do napisania taką funkcję: 

Wczytanie obrazu

struct img_t* load_image(const char* fname, enum error_code_t* errorCode);

Funkcja load_image odczytuje z pliku binarnego o nazwie fname obraz w formacie PGM (Portable Gray Map) i zapisuje jego zawartość w postaci alokowanej dynamicznie tablicy dwuwymiarowej do struktury img_t. Funkcja zwraca wczytany obraz wraz z jego wymiarami zapisanymi w strukturze img_t, a w przypadku błędu zwraca NULL oraz zapisuje kod błędu do zmiennej errorCode (brak przekazania parametru errorCode powinien skutkować natychmiastowym zakończeniem działania funkcji).

Plik PGM to obraz w skali szarości zapisany w formacie przenośnej szarej mapy (PGM), gdzie każdy piksel jest zakodowany jednym bajtem (8 bitów). Obraz składa się z siatki liczb reprezentujących różne odcienie szarości, z zakresu od czerni (0) do bieli (255). Pliki PGM mogą być przechowywane w formacie tekstowym ASCII lub binarnym.

Nagłówek pliku składa się z dwóch bajtów, które zawierają informację o formacie binarnym PGM - "P2". Następnie na kolejnych 8 bajtach zapisane są informacje o szerokości i wysokości obrazu, z każdą wartością zapisaną na 4 bajtach. Po tych danych następuje jeden bajt, w którym zapisana jest maksymalna wartość, jaką piksele mogą przyjąć.

Każdy piksel obrazu jest zapisany na 1 bajcie, a wartość każdego bajtu odpowiada konkretnemu odcieniowi szarości. W ten sposób, dla obrazu o wymiarach n x m, plik PGM ma długość (2 + 8 + 1 + n x m) bajtów.

Wartość zwracana

Kody błędu

ERROR_CODE_OK - w przypadku powodzenia.
ERROR_CODE_INCORRECT_PARAMETERS - w przypadku przekazania do funkcji nieprawidłowych danych.
ERROR_CODE_FILE_NOT_EXISTS - gdy plik o podanej nazwie nie istnieje.
ERROR_CODE_FILE_CORRUPTED - gdy plik ma niepoprawny format lub zawiera błędy.
ERROR_CODE_FAILED_TO_ALLOCATE_MEMORY - w przypadku niepowodzenia alokacji pamięci.

Wymagania pamięciowe

Funkcja nie może zaalokować więcej pamięci niż jest to niezbędne do posiadania w pamięci obrazu wczytanego z pliku.

Funkcja napisana przeze mnie wygląda następująco: 

enum error_code_t{
    ERROR_CODE_OK=0,
    ERROR_CODE_INCORRECT_PARAMETERS=1,
    ERROR_CODE_FILE_NOT_EXISTS=2,
    ERROR_CODE_FILE_CORRUPTED=3,
    ERROR_CODE_COULDNT_CREATE_FILE=4,
    ERROR_CODE_FAILED_TO_ALLOCATE_MEMORY=5,
};

struct img_t
{
    uint8_t **img;
    int width;
    int height;
};
void destroy_img(struct img_t *input){
    if(input==NULL) return;
    if(input->img!=NULL){
        for(int i=0; *(input->img+i)!=NULL; i++)
            free(*(input->img+i));
        free(input->img);
    }
    free(input);
}
struct img_t *load_image(const char *fname, enum error_code_t *errorCode) {
    if (fname == NULL || errorCode == NULL) {
        *errorCode = ERROR_CODE_INCORRECT_PARAMETERS;
        return NULL;
    }
    FILE *fp = fopen(fname, "rb");
    if (fp == NULL) {
        *errorCode = ERROR_CODE_FILE_NOT_EXISTS;
        return NULL;
    }
    char *c;
    c = calloc(3, sizeof(char));
    if (fread(c, sizeof(char), 2, fp) != 2 || *c != 'P' && *(c + 1) != '2') {
        *errorCode = ERROR_CODE_FILE_CORRUPTED;
        free(c);
        fclose(fp);
        return NULL;
    }
    free(c);
    int width, height;

    if (fscanf(fp, "%d %d", &width, &height) != 2 || width < 0 || height < 0) {
        *errorCode = ERROR_CODE_FILE_CORRUPTED;
        fclose(fp);
        return NULL;
    }

    unsigned char max_value;
    int a;
    if (fscanf(fp, "%hhu", &max_value) != 1) {
        *errorCode = ERROR_CODE_FILE_CORRUPTED;
        fclose(fp);
        return NULL;
    }
    a=(int) max_value;
    if(a>255){
        *errorCode = ERROR_CODE_FILE_CORRUPTED;
        fclose(fp);
        return NULL;
    }

    struct img_t *image = NULL;
    image = (struct img_t *) calloc(1, sizeof(struct img_t *));
    if (image == NULL) {
        *errorCode = ERROR_CODE_FAILED_TO_ALLOCATE_MEMORY;
        fclose(fp);
        return NULL;
    }
    image->img = (uint8_t **) calloc(height, sizeof(uint8_t *));
    if (image->img == NULL){
        *errorCode = ERROR_CODE_FAILED_TO_ALLOCATE_MEMORY;
        fclose(fp);
        destroy_img(image);
        //free(image);
        return NULL;
    }
        for (int i = 0; i < height; i++)
        {
            *(image->img + i) = (uint8_t *) calloc(width, sizeof(uint8_t));
            if(*(image->img + i)==NULL){
                *errorCode = ERROR_CODE_FAILED_TO_ALLOCATE_MEMORY;
                fclose(fp);
                destroy_img(image);
                return NULL;

            }
        }
    image->height = height;
    image->width = width;
    unsigned char val;
    for(int i=0; i<height; i++){
        for(int j=0; j<width; j++){
                if(fscanf(fp, "%hhu", &val)!=1){
                    *errorCode = ERROR_CODE_FILE_CORRUPTED;
                    fclose(fp);
                    destroy_img(image);
                    return NULL;
                }
                a=(int)val;
                if(a<0||a>255){
                    *errorCode = ERROR_CODE_FILE_CORRUPTED;
                    fclose(fp);
                    destroy_img(image);
                    return NULL;
                }
                *((*image->img+i)+j)=(int)val;
            }
        }

    *errorCode = ERROR_CODE_OK;
    return image;
}

Mój problem polega na tym, że za każdym razem przy odczytywaniu z pliku binarnego wysokości i szerokości funkcja zwraca NULL, czyli jest to błędnie odczytane. W przypadku plików txt nie mam takiego problemu, te dwie zmienne odczytują się poprawnie. Byłabym wdzięczna, za wskazówki gdzie popełniam błąd.

Z góry dziękuję

komentarz 4 sierpnia 2023 przez adrian17 Ekspert (344,860 p.)
(BTW, jakoś sprzeczny ten artykuł na intuicję. Z tego co wszędzie kojarzę, to P5 to format binarny, a P2 to tekstowy - a tutaj piszą na odwrót?)
komentarz 5 sierpnia 2023 przez Oscar Nałogowiec (29,320 p.)
edycja 5 sierpnia 2023 przez Oscar

@Zuzan, A masz informacje w jakim endianie (czyli kolejności bajtów) zapisane są poszczególne liczby w pliku - szczególnie te z nagłowka, które mają po więcej niż jednym bajcie?

W linii 33 i kolejnych allokujesz 3 bajty, kawałek dalej to zwalniasz. Nie lepiej po prostu użyć 2 elementowej tablicy.

I tak sprawdzasz bajty pojedynczo, nie jako C-string, więc nie potrzebujesz dodatkowego znaku na kończące zero.

A jeśli chodzi o liczby to ponieważ plik jest binarny nie czytaj ich fscanf, tylko fread. Jeśli endian pasuje można to zrobić prosto:

uint32_t szerokosc;    ///< uint32_t - by mieć pewnosc, ze to 4-bajtowa liczba.
fread(&szerokosc, sizeof(szerokosc), 1, plik);

Oczywiście jeszcze trzeba sprawdzić wynik, ale to robisz OK, więc uprościłem przykład.

Można w analogiczny sposób wczytywać całe struktury, których definicje należy przygotować tak, by odpowiadały zawartości pliku. Tutaj dodatkowo trzeba uważać na puste wypełnienia w strukturach w pamięci (tzw padding). W gcc można użyć attribute ((packed)).

Gdyby kod miał by przenośny można użyć funkcji ntohs (network to host short) i ntohl (network to host long). Pierwsze konwertuje dane z tzw kolejności sieciowej (ustalona) do kolejności 'wewnętrznej' komputera. W niektórych architekturach te funkcje są puste, na innych zamieniają kolejność bajtów.

Ewentualnie można czytaj bajt po bajcie i arytmetycznie (<< i |) poskładać docelową wartość - kod wyjdzie przenośny.

Ale w każdym przypadku musisz wiedzieć jaki jest endian w pliku.

1 odpowiedź

0 głosów
odpowiedź 4 sierpnia 2023 przez adrian17 Ekspert (344,860 p.)
if (fscanf(fp, "%d %d", &width, &height)

Następnie na kolejnych 8 bajtach zapisane są informacje o szerokości i wysokości obrazu, z każdą wartością zapisaną na 4 bajtach

Z tego co rozumiem to to nie jest zapisane tekstowo, tylko binarnie.

Podobne pytania

0 głosów
1 odpowiedź 620 wizyt
0 głosów
0 odpowiedzi 121 wizyt
pytanie zadane 7 lipca 2019 w C i C++ przez DamianW Bywalec (2,080 p.)
0 głosów
1 odpowiedź 453 wizyt
pytanie zadane 17 czerwca 2019 w C i C++ przez BartekSV123 Nowicjusz (240 p.)

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...