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