Dane binarne z wielu plików w jednym

0

Dzień dobry.

Podczas pisania aplikacji natknąłem się na problem, w którym moja znajomość języka C++ i umiejętności korzystania z google są niewystarczające. Próbowałem już wiele kombinacji, przez co kod wygląda jak wygląda...

No to od początku... Mam sobie nie mój kod, który dekoduje pliki PNG z podanego vector'a. Przy normalnym ładowaniu z pliku procedura wygląda tak:

int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true)
{
  ...
}

void loadFile(std::vector<unsigned char>& buffer, const std::string& filename) //designed for loading files from hard disk in an std::vector
{
  std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate);

  //get filesize
  std::streamsize size = 0;
  if(file.seekg(0, std::ios::end).good()) size = file.tellg();
  if(file.seekg(0, std::ios::beg).good()) size -= file.tellg();

  //read contents of the file into the vector
  if(size > 0)
  {
    buffer.resize((size_t)size);
    file.read((char*)(&buffer[0]), size);
  }
  else buffer.clear();
}

int main(int argc, char *argv[])
{
  const char* filename = argc > 1 ? argv[1] : "test.png";
  
  //load and decode
  std::vector<unsigned char> buffer, image;
  loadFile(buffer, filename);
  unsigned long w, h;
  int error = decodePNG(image, w, h, buffer.empty() ? 0 : &buffer[0], (unsigned long)buffer.size());

  return 0;
}

Po takim zastosowaniu funkcji decodePNG mogę sobie vector image oraz szerokość i wysokość obrazu (w, h) wykorzystać przy ładowaniu tekstur OpenGL:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, &image[0]);

Ten etap jest zamknięty, ładowanie pojedynczych plików png działa wyśmienicie. Ale teraz chcę to przerobić, aby składować dane binarne wszystkich tekstur skompresowanych PNG w jednym pliku i żeby ładowanie odbywało się przy minimalnej ingerencji w resztę kodu aplikacji. Tak więc modyfikowana będzie tylko funkcja loadFile, ale najpierw opiszę sam zapis plików png w jeden plik. Odbywa się to małą oddzielną aplikacją napisaną na potrzeby projektu:

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
/* "readdir" etc. are defined here. */
#include <dirent.h>
/* limits.h defines "PATH_MAX". */
#include <limits.h>

#include <iostream>
#include <fstream>

using namespace std;

std::ofstream outf("data.bin", std::ios::out|std::ios::binary|std::ios::trunc);

bool hasEnding (string const &fullString, string const &ending)
{
    if (fullString.length() >= ending.length()) {
        return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
    } else {
        return false;
    }
}

void decision(string dir)
{
	if (hasEnding(dir,".png"))
	{
		cout << dir << endl;
		
		outf.write(dir.c_str(),dir.length());
		outf.write("\n",1);
		ifstream file(dir.c_str(), ios::in|ios::binary);
		char buff[1];
		while (!file.eof())
		{
			file.read(buff,1);
			outf.write(buff,1);
		}
		file.close();
		outf.write("\n",1);
	}
}

/* List the files in "dir_name". */

static void
list_dir (const char * dir_name)
{
    DIR * d;

    /* Open the directory specified by "dir_name". */

    d = opendir (dir_name);

    /* Check it was opened. */
    if (! d) {
        fprintf (stderr, "Cannot open directory '%s': %s\n",
                 dir_name, strerror (errno));
        exit (EXIT_FAILURE);
    }
    char a[512];
    while (1) {
        struct dirent * entry;
        const char * d_name;

        /* "Readdir" gets subsequent entries from "d". */
        entry = readdir (d);
        if (! entry) {
            /* There are no more entries in this directory, so break
               out of the while loop. */
            break;
        }
        d_name = entry->d_name;
        /* Print the name of the file and directory. */
        //printf ("%s/%s\n", dir_name, d_name);
        
        strcpy(a,"");
        strcat(a,dir_name);
        strcat(a,"/");
        strcat(a,d_name);
        //printf ("%s\n",a);
        decision(a);
        

        /* See if "entry" is a subdirectory of "d". */

        if (entry->d_type & DT_DIR) {

            /* Check that the directory is not "d" or d's parent. */
            
            if (strcmp (d_name, "..") != 0 &&
                strcmp (d_name, ".") != 0) {
                int path_length;
                char path[PATH_MAX];
 
                path_length = snprintf (path, PATH_MAX,
                                        "%s/%s", dir_name, d_name);
                //printf ("%s\n", path);
                if (path_length >= PATH_MAX) {
                    fprintf (stderr, "Path length has got too long.\n");
                    exit (EXIT_FAILURE);
                }
                /* Recursively call "list_dir" with the new path. */
                list_dir (path);
            }
        }
    }
    /* After going through all the entries, close the directory. */
    if (closedir (d)) {
        fprintf (stderr, "Could not close '%s': %s\n",
                 dir_name, strerror (errno));
        exit (EXIT_FAILURE);
    }
}

int main ()
{
    list_dir ("data");
    outf.close();
    return 0;
}

Funkcja list_dir jest nieważna, wzięta gdzieś z neta, w typ przypadku podaje ona wszystkie pliki z drzewa katalogów (zaczynającego się od "korzenia" data) do decyzji funkcji decision, jeśli to plik ma rozszerzenie png, to będzie dopisywany do pliku data.bin (strumień outf). Zapis prezentuje się następująco:

sciezka/do/pliku.png\n
dane_binarne_pliku_png\n
sciezka/do/pliku2.png\n
dane_binarne_pliku_png_2\n
sciezka/do/pliku3.png\n
dane_binarne_pliku_png_3\n

Po zapisie wszystkich plików, rozmiar pliku data.bin ma rozmiar około taki jak wszystkie pliki png w podkatalogach folderu data.

No i teraz w głównej aplikacji chcę sobie ładować plik png znajdujący się pod "identyfikatorem", czyli ścieżką do pliku. Wszelkie próby kończą się na błędzie zwracanym przez funkcję decodePNG. Z moich prób debugowania wynika, że do vectora buffer idzie nieodpowiednia ilość danych. Oto funkcja loadFile, którą chcę podmienić:

void Texture::loadFile(std::vector<unsigned char>& buffer, const std::string& filename) //designed for loading files from hard disk in an std::vector
{
	std::ifstream file("data.bin", std::ios::in|std::ios::binary);

	char crsr;

	while (!file.eof()) {
		file >> crsr;

		if (crsr=='d') {
			bool good=true;
			for (unsigned int i=1; i<filename.length(); i++) {
				file >> crsr;
				if (filename[i]!=crsr) {
					good=false;
					break;
				}
			} // po sprawdzenu czy nazwa zaczynajaca sie na 'd' (data) jest kluczem, ktorego szukamy
			// laduje plik
			if (good) {
				while (crsr!='\n' && !file.eof()) {
					buffer.push_back(crsr);
					file >> crsr;
				}
				cout << buffer.size();
			}
		}
	}

	file.close();
}
0

Czy możesz zmienić format tego pliku "data.bin"?

0

No pewnie, że tak ;) Prosiłbym o poradę, jak to zrobić najlepiej, żeby wczytywanie działało i w miarę wyglądało. Bo zdaję sobie sprawę, że teraz to strasznie "lame".

0

Po prostu nie szukaj czegoś co może wystąpić w danych binarnych pliku, zrób np tak:

sciezka/do/pliku.png\n
ilość_bajtów_danych_binarnych\n
dane_binarne_pliku_png\n
sciezka/do/pliku2.png\n
ilość_bajtów_danych_binarnych\n
dane_binarne_pliku_png_2\n
sciezka/do/pliku3.png\n
ilość_bajtów_danych_binarnych\n
dane_binarne_pliku_png_3\n

W takim przypadku z góry wiesz ile bajtów odczytać.

1
                unsigned len=dir.length();
                outf.write((char*)&len,sizeof(unsigned));
                outf.write(dir.c_str(),len);
                ifstream file(dir.c_str(),ios::in|ios::binary);
                file.seekg(0,ios::end);
                streamsize size=file.tellg();
                file.seekg(0,ios::beg);
                outf.write((char*)&size,sizeof(streamsize));
                while(size)
                   {
                    char buff[1024];
                    unsigned cnt=file.read(buff,1024).gcount();
                    outf.write(buff,cnt);
                    size-=cnt;
                  }
                file.close();

Przy takim zapisie możesz dosyć szybko iterować zawartość pliku.

void Texture::loadFile(vector<unsigned char>& buffer,const string& filename)
  {
   ifstream file("data.bin",ios::in|ios::binary);
   unsigned len;
   while(file.read((char*)&len,sizeof(unsigned)))
     {
      string currname(len,'\0');
      file.read(&currname[0],len);
      streamsize size;
      file.read((char*)&size,sizeof(streamsize));
      if(currname==filename)
        {
         buffer.resize(size);
         buffer.read((char*)&buffer[0],size);
         return;
        }
      fille.seekg(size,ios::cur);
     }
  }

Pisano na sucho, mogą być błędy, traktować jako schemat.

0

Niesamowite, działa prawie bez ingerencji z mojej strony.

buffer.read - powinno być file.read
fille.seekg - literówka, powinno być file.seekg ;)

Ale to wszystko zrozumiałe, skoro jest pisane z palca na szybko. Jesteś super trzynasty smoku ;)

Tak z ciekawości, co cię powstrzymuje przed po prostu wsadzeniem wszystkich plików do TAR-a/ZIP-a?

Po pierwsze, żeby nie dołączać niepotrzebnie biblioteki do projektu. Po drugie, żeby pierwszy lepszy typek nie podmienił grafik w mojej grze i żeby mieć furtkę na przyszłość i sobie w jakiś prosty sposób dane zabezpieczyć, pomieszać itp.

1 użytkowników online, w tym zalogowanych: 0, gości: 1